Binaryen, bir derleyici ve araç zinciridir
WebAssembly için altyapı kitaplığı olan bir web sitesi. Amaçlanan
sezgisel, hızlı ve etkili WebAssembly derlemesi
yapmaya hazırdır. Bu yayında,
adlı sentetik bir oyuncak dili örneği olarak yazılım örneklerine bakalım. Nasıl yazılacağını
Binaryen.js API'sini kullanan JavaScript'teki WebAssembly modülleri. Hem
modül oluşturma, modüle işlev ekleme ve dışa aktarma konularında temel bilgiler
fonksiyonlarının nasıl kullanıldığını gösterir. Bu size genel olarak
gerçek programlama dillerini WebAssembly'de derleme mekaniği. Ayrıca,
Wasm modüllerini hem Binaryen.js ile hem de
komut satırından wasm-opt
ekleyin.
Binaryen ile ilgili arka plan
Binaryen, C API tek bir başlıkta gösterilebilir. Ayrıca JavaScript'ten kullanılır. Giriş kabul eder WebAssembly formu, ama aynı zamanda genel kontrol akışı grafiği kullanabilirsiniz.
Ara temsil (IR), kullanılan veri yapısı veya kodudur . Binaryen'in dahili IR, kompakt veri yapılarını kullanır ve tamamen paralel kullanım için tasarlanmıştır. kod oluşturma ve optimizasyon. Binaryen IR WebAssembly'nin bir alt kümesi olduğundan WebAssembly'de derler.
Binaryen'in optimize edicisi, kod boyutunu ve hızını artırabilecek birçok karta sahip. Bu optimizasyonlar, Binaryen'i derleyici olarak kullanılabilecek kadar güçlü yapmayı amaçlar arka uçta çalışır. WebAssembly'ye özel optimizasyonlar içerir. derleyiciler bunu yapmayabilir) [Wasm] olarak düşünebilirsiniz. basitleştirir.
Binaryen'in örnek kullanıcısı olarak AssemblyScript
Binaryen çeşitli projelerde kullanılıyor. Örneğin, AssemblyScript, Binaryen'i TypeScript benzeri bir dilden doğrudan WebAssembly'ye derleyin. Örneği deneyin komut dosyasını çalıştırın.
AssemblyScript girişi:
export function add(a: i32, b: i32): i32 {
return a + b;
}
Binaryen tarafından oluşturulan metin biçiminde, karşılık gelen WebAssembly kodu:
(module
(type $0 (func (param i32 i32) (result i32)))
(memory $0 0)
(export "add" (func $module/add))
(export "memory" (memory $0))
(func $module/add (param $0 i32) (param $1 i32) (result i32)
local.get $0
local.get $1
i32.add
)
)
Binaryen araç zinciri
Binaryen araç zinciri, JavaScript'in hem JavaScript hem de dönüşüm işlemleri için
geliştiriciler ve komut satırı kullanıcıları için geçerlidir. Bu araçların bir kısmı
takip etme; "the"
içerilen araçların tam listesi
projenin README
dosyasında kullanılabilir.
binaryen.js
: Binaryen yöntemlerini sunan bağımsız bir JavaScript kitaplığı şunun için: Wasm modüllerini oluşturun ve optimize edin. Derlemeler için npm'de binaryen.js bölümüne bakın (veya doğrudan GitHub veya unpkg) belirtin.wasm-opt
: WebAssembly'yi yükleyen ve Binaryen IR'yi çalıştıran komut satırı aracı devreye girer.wasm-as
vewasm-dis
: monte eden ve ayıran komut satırı araçları WebAssembly.wasm-ctor-eval
: İşlevleri (veya öğeleri) yürütebilen komut satırı aracı işlevleri) sunar.wasm-metadce
: Wasm dosyalarının bölümlerini esnek bir şekilde kaldırmak için komut satırı aracı Bu, modülün nasıl kullanıldığına göre değişir.wasm-merge
: Birden fazla Wasm dosyasını tek bir dosyada birleştiren komut satırı aracı dosyası oluşturun. Bu işlem, karşılık gelen içe aktarmaları dışa aktarma işlemlerine bağlar. Beğen paketleyiciyi JavaScript için kullanırken Wasm için olduğunu varsayalım.
WebAssembly için derleme
Bir dili başka bir dilde derlemek için genellikle birkaç adım vardır. önemli olanları aşağıdaki listede belirtilmiştir:
- Sözlük analizi: Kaynak kodu jetonlara ayırma.
- Söz dizimi analizi: Soyut bir söz dizimi ağacı oluşturun.
- Anlamsal analiz: Hataları kontrol edin ve dil kurallarını uygulayın.
- Orta seviye kod oluşturma: Daha soyut bir temsil oluşturun.
- Kod oluşturma: Hedef dile çevirin.
- Hedefe özel kod optimizasyonu: Hedef için optimize edin.
Unix dünyasında, derleme için en sık kullanılan araçlar şunlardır:
lex
ve
yacc
:
lex
(Lexical Analyzer Generator):lex
, kelimeler üreten bir araçtır analiz araçları (lexer veya tarayıcı olarak da bilinir). Bir dizi düzenli ve karşılık gelen işlemleri girdi olarak kullanır ve bir giriş kaynak kodundaki kalıpları algılayan sözlük analizcisi.yacc
(Başka Bir Derleyici):yacc
, isimlerin doğru bir şekilde ayrıştırıcılar dahil edilmiştir. Bir kelimenin resmi dilbilgisi açıklamasını programlama dilini kullanır ve ayrıştırıcı için kod oluşturur. Ayrıştırıcılar üretmek için soyut söz dizimi ağaçları (AST'ler) olarak adlandırılır.
Başarılı bir örnek
Bu gönderinin kapsamı göz önünde bulundurulduğunda bir programlamayı baştan sona ele almak mümkün değil. Öyleyse sadelik açısından, çok sınırlı ve kullanışlı olmayan ve ifade ederek çalışan exampleScript adlı sentetik programlama dili somut örnekler üzerinden genel işlemler yapmasına olanak tanır.
- Bir
add()
işlevi yazmak için herhangi bir eklemenin bir örneğini kodlayın. Örneğin,2 + 3
. multiply()
işlevi yazmak için örneğin6 * 12
yazarsınız.
Ön uyarıya göre tamamen işe yaramaz, ancak sözcük açısından
analiz edici, tek bir normal ifade olur: /\d+\s*[\+\-\*\/]\s*\d+\s*/
.
Daha sonra, bir ayrıştırıcı olmalıdır. Aslında çok basitleştirilmiş bir sürümü
soyut bir söz dizimi ağacı,
adlandırılmış yakalama grupları:
/(?<first_operand>\d+)\s*(?<operator>[\+\-\*\/])\s*(?<second_operand>\d+)/
.
ExampleScript komutları her satıra bir tane gelecektir, böylece ayrıştırıcı kodu işleyebilir satır bazında ikiye ayrılır. Bu, ilk adımı kontrol etmek için şu üç adımdan oluşur: sözdizimi analiz, söz dizimi analiz ve anlamsal analiz. Bu adımların kodunu aşağıdaki girişte bulabilirsiniz.
export default class Parser {
parse(input) {
input = input.split(/\n/);
if (!input.every((line) => /\d+\s*[\+\-\*\/]\s*\d+\s*/gm.test(line))) {
throw new Error('Parse error');
}
return input.map((line) => {
const { groups } =
/(?<first_operand>\d+)\s*(?<operator>[\+\-\*\/])\s*(?<second_operand>\d+)/gm.exec(
line,
);
return {
firstOperand: Number(groups.first_operand),
operator: groups.operator,
secondOperand: Number(groups.second_operand),
};
});
}
}
Ara kod oluşturma
Artık ExampleScript programları soyut bir söz dizimi ağacı olarak temsil edilebiliyor. (oldukça basit olsa da) bir sonraki adım soyut bir soyut orta düzey temsildir. İlk adım Binaryen'de yeni bir modül oluşturun:
const module = new binaryen.Module();
Soyut söz dizimi ağacının her satırı
firstOperand
, operator
ve secondOperand
. Olası dört durumun her biri için
exampleScript'teki operatörler, yani +
, -
, *
, /
, yeni bir
modüle eklenmesi gerekir
yöntemini Binaryen'in Module#addFunction()
yöntemiyle belirleriz. Etiketin parametreleri
Module#addFunction()
yöntemleri şunlardır:
name
:string
, işlevin adını temsil eder.functionType
:Signature
, işlevin imzasını temsil eder.varTypes
:Type[]
, belirtilen sırada ek yerelleri gösterir.body
:Expression
, işlevin içeriği.
Biraz gevşeyip kafanızı dağıtacağınız ayrıntılar
Binaryen belgeleri
yardımcı olabilir, ancak sonunda ExampleScript'in +
operatörüne göre, birden fazla yöntemden biri olarak Module#i32.add()
yöntemine
mevcut
tamsayı işlemler.
Toplama için birinci ve ikinci toplam olmak üzere iki işlenen gerekir.
fonksiyonunun gerçekten çağrılabilmesi için,
dışa aktarıldı
Module#addFunctionExport()
ile.
module.addFunction(
'add', // name: string
binaryen.createType([binaryen.i32, binaryen.i32]), // params: Type
binaryen.i32, // results: Type
[binaryen.i32], // vars: Type[]
// body: ExpressionRef
module.block(null, [
module.local.set(
2,
module.i32.add(
module.local.get(0, binaryen.i32),
module.local.get(1, binaryen.i32),
),
),
module.return(module.local.get(2, binaryen.i32)),
]),
);
module.addFunctionExport('add', 'add');
Soyut söz dizimi ağacı işlendikten sonra modül dört yöntem içerir:
tam sayılarla çalışan üç tanesi, yani Module#i32.add()
değerine göre add()
,
Module#i32.sub()
- subtract()
/ multiply()
Module#i32.mul()
ve Module#f64.div()
bazında aykırı değer divide()
çünkü ExampleScript, kayan nokta sonuçlarıyla da çalışır.
for (const line of parsed) {
const { firstOperand, operator, secondOperand } = line;
if (operator === '+') {
module.addFunction(
'add', // name: string
binaryen.createType([binaryen.i32, binaryen.i32]), // params: Type
binaryen.i32, // results: Type
[binaryen.i32], // vars: Type[]
// body: ExpressionRef
module.block(null, [
module.local.set(
2,
module.i32.add(
module.local.get(0, binaryen.i32),
module.local.get(1, binaryen.i32)
)
),
module.return(module.local.get(2, binaryen.i32)),
])
);
module.addFunctionExport('add', 'add');
} else if (operator === '-') {
module.subtractFunction(
// Skipped for brevity.
)
} else if (operator === '*') {
// Skipped for brevity.
}
// And so on for all other operators, namely `-`, `*`, and `/`.
Gerçek kod tabanlarıyla uğraşıyorsanız bazen hiçbir zaman aranır. Ölü kodu yapay olarak sunmak (bunun için optimize edilmiş sonraki bir adımda ele alınacaktır) derleme işlemini Wasm'e yükleyin, dışa aktarılmayan bir işlev ekleyerek işi halleder.
// This function is added, but not exported,
// so it's effectively dead code.
module.addFunction(
'deadcode', // name: string
binaryen.createType([binaryen.i32, binaryen.i32]), // params: Type
binaryen.i32, // results: Type
[binaryen.i32], // vars: Type[]
// body: ExpressionRef
module.block(null, [
module.local.set(
2,
module.i32.div_u(
module.local.get(0, binaryen.i32),
module.local.get(1, binaryen.i32),
),
),
module.return(module.local.get(2, binaryen.i32)),
]),
);
Derleyici neredeyse hazır. Çok gerekli olmasa da kesinlikle
iyi bir uygulamadır
modülü doğrulama
Module#validate()
yöntemiyle işlem yapabilirsiniz.
if (!module.validate()) {
throw new Error('Validation error');
}
Ortaya çıkan Wasm kodunu elde etme
Alıcı:
oluşturan Wasm kodunu elde edin,
Binaryen'de verileri almak için
metinsel temsil
S ifadesinde .wat
dosyası olarak
bir biçimde sunmasını sağlamak ve
ikili temsil
doğrudan tarayıcıda çalıştırılabilen bir .wasm
dosyası olarak. İkili program kodu,
doğrudan tarayıcıda çalıştırılması gerekir. Çalıştığından emin olmak için dışa aktarma işlemlerini günlüğe kaydederek
yardım.
const textData = module.emitText();
console.log(textData);
const wasmData = module.emitBinary();
const compiled = new WebAssembly.Module(wasmData);
const instance = new WebAssembly.Instance(compiled, {});
console.log('Wasm exports:\n', instance.exports);
ExampleScript programının dördü de içeren eksiksiz metin gösterimi
işlemleri aşağıda listelenmiştir. Ölü kodun nasıl hâlâ orada olduğuna dikkat edin.
ancak ekran görüntüsüne göre
WebAssembly.Module.exports()
.
(module
(type $0 (func (param i32 i32) (result i32)))
(type $1 (func (param f64 f64) (result f64)))
(export "add" (func $add))
(export "subtract" (func $subtract))
(export "multiply" (func $multiply))
(export "divide" (func $divide))
(func $add (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local.set $2
(i32.add
(local.get $0)
(local.get $1)
)
)
(return
(local.get $2)
)
)
(func $subtract (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local.set $2
(i32.sub
(local.get $0)
(local.get $1)
)
)
(return
(local.get $2)
)
)
(func $multiply (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local.set $2
(i32.mul
(local.get $0)
(local.get $1)
)
)
(return
(local.get $2)
)
)
(func $divide (param $0 f64) (param $1 f64) (result f64)
(local $2 f64)
(local.set $2
(f64.div
(local.get $0)
(local.get $1)
)
)
(return
(local.get $2)
)
)
(func $deadcode (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local.set $2
(i32.div_u
(local.get $0)
(local.get $1)
)
)
(return
(local.get $2)
)
)
)
WebAssembly'yi optimize etme
Binaryen, Wasm kodunu optimize etmek için iki yol sunar. Birincisi, Binaryen.js, diğeri de komut satırı için. İlk yöntemde, standart optimizasyon grubu uygulanır göre ayarlar ve optimizasyon ve daraltma düzeylerini ayarlamanıza olanak tanır. ikincisi varsayılan olarak hiçbir kural kullanmaz, bunun yerine tam özelleştirme sağlar, Diğer bir deyişle, yeterli denemeyle ayarları duruma göre en iyi sonuçları alabilirsiniz.
Binaryen.js ile optimizasyon
Binaryen ile bir Wasm modülünü optimize etmenin en basit yolu
doğrudan Binaryen.js'nin Module#optimize()
yöntemini çağırabilir ve isteğe bağlı olarak
ayarlamak
optimize etme ve küçültme seviyesini ayarlayın.
// Assume the `wast` variable contains a Wasm program.
const module = binaryen.parseText(wast);
binaryen.setOptimizeLevel(2);
binaryen.setShrinkLevel(1);
// This corresponds to the `-Os` setting.
module.optimize();
Bu işlem, daha önce yapay olarak eklenen ölü kodu siler. Dolayısıyla,
ExampleScript oyuncak örneğinin Wasm sürümünün metinsel gösterimi
otomatik olarak oluşturur. local.set/get
çiftlerinin
optimizasyon adımları
SimplifyLocals
(yerellikle ilgili çeşitli optimizasyonlar) ve
Elektrik süpürgesi
(açıkça ihtiyaç duyulmayan kodu kaldırır) ve return
,
RemoveUnusedBrs
(gerekmeyen konumlardaki araları kaldırır).
(module
(type $0 (func (param i32 i32) (result i32)))
(type $1 (func (param f64 f64) (result f64)))
(export "add" (func $add))
(export "subtract" (func $subtract))
(export "multiply" (func $multiply))
(export "divide" (func $divide))
(func $add (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(local.get $0)
(local.get $1)
)
)
(func $subtract (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
(i32.sub
(local.get $0)
(local.get $1)
)
)
(func $multiply (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
(i32.mul
(local.get $0)
(local.get $1)
)
)
(func $divide (; has Stack IR ;) (param $0 f64) (param $1 f64) (result f64)
(f64.div
(local.get $0)
(local.get $1)
)
)
)
Pek çok
optimizasyon kartları,
Module#optimize()
, özel optimize etme ve daraltma seviyelerini kullanıyor. varsayılan
belirler. Tam özelleştirme için wasm-opt
komut satırı aracını kullanmanız gerekir.
Wasm-opt komut satırı aracıyla optimizasyon
Binaryen, kullanılacak kartların tamamen özelleştirilmesi için
wasm-opt
komut satırı aracı. Bir
olası optimizasyon seçeneklerinin tam listesini
aracın yardım mesajına bakın. wasm-opt
aracı muhtemelen en popüler
vardır ve Wasm kodunu optimize etmek için çeşitli derleyici araç zincirleri tarafından kullanılır.
Emscripten,
J2CL,
Kotlin/Wasm,
dart2wasm,
Wasm-pack ve diğerleri.
wasm-opt --help
Pasajlar hakkında bir fikir vermek için, en başarılı olan ve anlaşılmaz olmasına dikkat edin.
- Kod Katlama: Birleştirme yaparak yinelenen kodu önler (örneğin, iki
if
kodu varsa) ilişkin bazı ortak talimatlar vardır). - DeadArgumentEliification: Bağımsız değişkenleri kaldırmak için zaman optimizasyon geçişini bağlayın bir işleve dönüştürür.
- MinifyImportsAndExports: Bunları
"a"
,"b"
olarak küçültür. - DeadCodeEliification: Ölü kodu kaldırın.
Bir
optimizasyon kılavuzu
çeşitli işaretler arasından hangisinin daha önemli olduğunu belirlemeye yönelik
ve ilk olarak denemeye değer. Örneğin, bazen wasm-opt
çalıştırılıyor
sürekli tekrar tekrar, girişi daha da küçültür. Böyle durumlarda
şununla:
--converge
işaret
tekrar bir optimizasyon yapılmaya devam edene ve
ulaştı.
Demo
Bu gönderide ele alınan kavramları uygulamalı olarak görmek için, yerleştirilmiş demo yapabilirsiniz. Bunun için aklınıza gelebilecek herhangi bir örnek Ayrıca şunlara dikkat edin: demonun kaynak kodunu görüntüleyin.
Sonuçlar
Binaryen, dilleri WebAssembly ve web derlemelerinde derleyen güçlü bir araç seti en iyi uygulamaları paylaşacağız. JavaScript kitaplığı ve komut satırı araçları esnek ve kullanım kolaylığını sunar. Bu gönderi, Çevik’in temel ilkelerini Binaryen'in etkinliğini ve potansiyelini vurgulayan Wasm derlemesi Maksimum optimizasyon. Binaryen'in teklifini özelleştirme seçeneklerinin çoğu optimizasyon için Wasm'ın dahili bileşenleri hakkında varsayılan ayarlar zaten sorunsuz çalışıyor. Böylece, başarılı derleme ve optimizasyonlar yapabilirsiniz. bu video hazır!
Teşekkür
Bu yayın Alon Zakai tarafından incelendi, Thomas Lively ve Rıza Ahmet