WebAssembly چیست و از کجا آمده است؟

از زمانی که وب نه تنها به پلتفرمی برای اسناد، بلکه برای برنامه‌ها نیز تبدیل شد، برخی از پیشرفته‌ترین برنامه‌ها مرورگرهای وب را به محدودیت‌های خود رسانده‌اند. رویکرد «نزدیک‌تر شدن به فلز» از طریق ارتباط با زبان‌های سطح پایین‌تر به منظور بهبود عملکرد، در بسیاری از زبان‌های سطح بالاتر وجود دارد. به عنوان مثال، جاوا دارای رابط بومی جاوا است. برای جاوا اسکریپت، این زبان سطح پایین WebAssembly است. در این مقاله، متوجه خواهید شد که زبان اسمبلی چیست و چرا می تواند در وب مفید باشد، سپس یاد خواهید گرفت که چگونه WebAssembly از طریق راه حل موقت asm.js ایجاد شده است.

آیا تا به حال به زبان اسمبلی برنامه نویسی کرده اید؟ در برنامه نویسی کامپیوتر، زبان اسمبلی، که اغلب به سادگی اسمبلی نامیده می شود و معمولاً به اختصار ASM یا asm نامیده می شود، هر زبان برنامه نویسی سطح پایین با تطابق بسیار قوی بین دستورالعمل های موجود در زبان و دستورالعمل های کد ماشین معماری است.

برای مثال، با نگاهی به معماری Intel® 64 و IA-32 (PDF)، دستور MUL (برای ضرب ) ضرب بدون علامت از عملوند اول (عملوند مقصد) و عملوند دوم (عملوند منبع) را انجام می دهد و عملوند منبع را ذخیره می کند. منجر به عملوند مقصد می شود. بسیار ساده شده، عملوند مقصد یک عملوند ضمنی است که در ثبات AX قرار دارد، و عملوند مبدأ در یک ثبات همه منظوره مانند CX قرار دارد. نتیجه دوباره در ثبات AX ذخیره می شود. مثال کد x86 زیر را در نظر بگیرید:

mov ax, 5  ; Set the value of register AX to 5.
mov cx, 10 ; Set the value of register CX to 10.
mul cx     ; Multiply the value of register AX (5)
           ; and the value of register CX (10), and
           ; store the result in register AX.

برای مقایسه، اگر با هدف ضرب 5 و 10 وظیفه دارید، احتمالاً کدی شبیه به زیر در جاوا اسکریپت می نویسید:

const factor1 = 5;
const factor2 = 10;
const result = factor1 * factor2;

مزیت رفتن به مسیر مونتاژ این است که چنین کدهای سطح پایین و بهینه شده برای ماشین بسیار کارآمدتر از کدهای سطح بالا و بهینه شده توسط انسان است. در مورد قبلی مهم نیست، اما می توانید تصور کنید که برای عملیات پیچیده تر، تفاوت می تواند قابل توجه باشد.

همانطور که از نام آن پیداست، کد x86 به معماری x86 وابسته است. اگر روشی برای نوشتن کد اسمبلی وجود داشت که به معماری خاصی وابسته نبود، اما مزایای عملکرد اسمبلی را به ارث می برد، چه؟

asm.js

اولین گام برای نوشتن کد اسمبلی بدون وابستگی به معماری، asm.js بود، زیرمجموعه ای سخت از جاوا اسکریپت که می توانست به عنوان یک زبان هدف سطح پایین و کارآمد برای کامپایلرها استفاده شود. این زبان فرعی به طور موثر یک ماشین مجازی جعبه‌شناختی را برای زبان‌های ناامن از نظر حافظه مانند C یا C++ توصیف می‌کند. ترکیبی از اعتبارسنجی ایستا و پویا به موتورهای جاوا اسکریپت اجازه می‌دهد تا یک استراتژی کامپایل‌سازی پیش از زمان (AOT) را برای کدهای معتبر asm.js به کار بگیرند. کد نوشته شده به زبان‌هایی با تایپ استاتیک با مدیریت حافظه دستی (مانند C) توسط یک کامپایلر منبع به منبع مانند Emscripten اولیه (بر اساس LLVM) ترجمه شد.

عملکرد با محدود کردن ویژگی‌های زبان به مواردی که با AOT سازگار هستند، بهبود یافت. فایرفاکس 22 اولین مرورگری بود که از asm.js پشتیبانی کرد که با نام OdinMonkey منتشر شد. Chrome پشتیبانی asm.js را در نسخه 61 اضافه کرد. در حالی که asm.js هنوز در مرورگرها کار می کند، WebAssembly جایگزین آن شده است. دلیل استفاده از asm.js در این مرحله به عنوان جایگزینی برای مرورگرهایی است که از WebAssembly پشتیبانی نمی کنند.

WebAssembly

WebAssembly یک زبان اسمبلی سطح پایین با فرمت باینری فشرده است که با عملکرد تقریباً بومی اجرا می شود و زبان هایی مانند C/C++ و Rust و بسیاری دیگر را با هدف کامپایل ارائه می دهد تا در وب اجرا شوند. پشتیبانی از زبان‌های مدیریت‌شده با حافظه مانند جاوا و دارت در حال انجام است و باید به زودی در دسترس قرار گیرد، یا مانند مورد Kotlin/Wasm قبلاً ارائه شده است. WebAssembly طوری طراحی شده است که در کنار جاوا اسکریپت اجرا شود و به هر دو اجازه می دهد با هم کار کنند.

به غیر از مرورگر، برنامه های WebAssembly به لطف WASI ، رابط سیستم WebAssembly، یک رابط سیستم مدولار برای WebAssembly، در زمان های اجرا دیگری نیز اجرا می شوند. WASI برای قابل حمل بودن در سراسر سیستم عامل ها با هدف ایمن بودن و قابلیت اجرا در یک محیط sandbox ساخته شده است.

کد WebAssembly (کد باینری، یعنی بایت کد) برای اجرا بر روی یک ماشین پشته مجازی قابل حمل (VM) در نظر گرفته شده است. بایت کد به گونه ای طراحی شده است که تجزیه و اجرا سریعتر از جاوا اسکریپت باشد و یک نمایش کد فشرده داشته باشد.

اجرای مفهومی دستورالعمل ها از طریق یک شمارنده برنامه سنتی که از طریق دستورالعمل ها پیش می رود، انجام می شود. در عمل، اکثر موتورهای Wasm بایت کد Wasm را در کد ماشین کامپایل می کنند و سپس آن را اجرا می کنند. دستورالعمل ها به دو دسته تقسیم می شوند:

  • دستورالعمل‌های کنترلی که ساختارهای کنترلی را تشکیل می‌دهند و مقدار(های) آرگومان آن‌ها را از پشته خارج می‌کنند، ممکن است شمارنده برنامه را تغییر داده و مقدار(های) نتیجه را به پشته فشار دهند.
  • دستورالعمل های ساده ای که مقدار(های) آرگومان خود را از پشته خارج می کند، یک عملگر را روی مقادیر اعمال می کند، و سپس مقدار(های) نتیجه را روی پشته فشار می دهد، و به دنبال آن یک پیشروی ضمنی شمارنده برنامه.

با بازگشت به مثال قبل، کد WebAssembly زیر معادل کد x86 از ابتدای مقاله خواهد بود:

i32.const 5  ; Push the integer value 5 onto the stack.
i32.const 10 ; Push the integer value 10 onto the stack.
i32.mul      ; Pop the two most recent items on the stack,
             ; multiply them, and push the result onto the stack.

در حالی که asm.js تماماً در نرم‌افزار پیاده‌سازی می‌شود، یعنی کد آن می‌تواند در هر موتور جاوا اسکریپت اجرا شود (حتی اگر بهینه نشده باشد)، WebAssembly به عملکرد جدیدی نیاز داشت که همه فروشندگان مرورگر روی آن توافق کردند. WebAssembly که در سال 2015 معرفی شد و برای اولین بار در مارس 2017 منتشر شد، در 5 دسامبر 2019 به توصیه W3C تبدیل شد. W3C استاندارد را با مشارکت همه فروشندگان اصلی مرورگرها و سایر طرف های علاقه مند حفظ می کند. از سال 2017، پشتیبانی مرورگر جهانی است.

WebAssembly دارای دو نمایش است: متنی و باینری . آنچه در بالا می بینید، بازنمایی متنی است.

بازنمایی متنی

نمایش متنی بر اساس عبارات S است و معمولاً از پسوند فایل .wat (برای فرمت W eb A ssembly t ext) استفاده می کند. اگر واقعاً می خواهید، می توانید آن را با دست بنویسید. با در نظر گرفتن مثال ضرب از بالا و مفیدتر کردن آن با عدم کدگذاری فاکتورها، احتمالاً می توانید کد زیر را درک کنید:

(module
  (func $mul (param $factor1 i32) (param $factor2 i32) (result i32)
    local.get $factor1
    local.get $factor2
    i32.mul)
  (export "mul" (func $mul))
)

نمایش باینری

فرمت باینری که از پسوند فایل .wasm استفاده می کند برای مصرف انسان در نظر گرفته نشده است، چه برسد به خلقت انسان. با استفاده از ابزاری مانند wat2wasm ، می توانید کد بالا را به نمایش باینری زیر تبدیل کنید. (کامنت‌ها معمولاً بخشی از نمایش باینری نیستند، اما برای درک بهتر توسط ابزار wat2wasm اضافه می‌شوند.)

0000000: 0061 736d                             ; WASM_BINARY_MAGIC
0000004: 0100 0000                             ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01                                    ; section code
0000009: 00                                    ; section size (guess)
000000a: 01                                    ; num types
; func type 0
000000b: 60                                    ; func
000000c: 02                                    ; num params
000000d: 7f                                    ; i32
000000e: 7f                                    ; i32
000000f: 01                                    ; num results
0000010: 7f                                    ; i32
0000009: 07                                    ; FIXUP section size
; section "Function" (3)
0000011: 03                                    ; section code
0000012: 00                                    ; section size (guess)
0000013: 01                                    ; num functions
0000014: 00                                    ; function 0 signature index
0000012: 02                                    ; FIXUP section size
; section "Export" (7)
0000015: 07                                    ; section code
0000016: 00                                    ; section size (guess)
0000017: 01                                    ; num exports
0000018: 03                                    ; string length
0000019: 6d75 6c                          mul  ; export name
000001c: 00                                    ; export kind
000001d: 00                                    ; export func index
0000016: 07                                    ; FIXUP section size
; section "Code" (10)
000001e: 0a                                    ; section code
000001f: 00                                    ; section size (guess)
0000020: 01                                    ; num functions
; function body 0
0000021: 00                                    ; func body size (guess)
0000022: 00                                    ; local decl count
0000023: 20                                    ; local.get
0000024: 00                                    ; local index
0000025: 20                                    ; local.get
0000026: 01                                    ; local index
0000027: 6c                                    ; i32.mul
0000028: 0b                                    ; end
0000021: 07                                    ; FIXUP func body size
000001f: 09                                    ; FIXUP section size
; section "name"
0000029: 00                                    ; section code
000002a: 00                                    ; section size (guess)
000002b: 04                                    ; string length
000002c: 6e61 6d65                       name  ; custom section name
0000030: 01                                    ; name subsection type
0000031: 00                                    ; subsection size (guess)
0000032: 01                                    ; num names
0000033: 00                                    ; elem index
0000034: 03                                    ; string length
0000035: 6d75 6c                          mul  ; elem name 0
0000031: 06                                    ; FIXUP subsection size
0000038: 02                                    ; local name type
0000039: 00                                    ; subsection size (guess)
000003a: 01                                    ; num functions
000003b: 00                                    ; function index
000003c: 02                                    ; num locals
000003d: 00                                    ; local index
000003e: 07                                    ; string length
000003f: 6661 6374 6f72 31            factor1  ; local name 0
0000046: 01                                    ; local index
0000047: 07                                    ; string length
0000048: 6661 6374 6f72 32            factor2  ; local name 1
0000039: 15                                    ; FIXUP subsection size
000002a: 24                                    ; FIXUP section size

کامپایل به WebAssembly

همانطور که می بینید، نه .wat و نه .wasm به خصوص بسیار انسان دوست نیستند. اینجاست که کامپایلری مانند Emscripten وارد عمل می شود. این به شما امکان می دهد از زبان های سطح بالاتر مانند C و C++ کامپایل کنید. کامپایلرهای دیگری برای زبان های دیگر مانند Rust و بسیاری دیگر وجود دارد. کد C زیر را در نظر بگیرید:

#include <stdio.h>

int main() {
  printf("Hello World\n");
  return 0;
}

معمولاً این برنامه C را با کامپایلر gcc کامپایل می کنید.

$ gcc hello.c -o hello

با نصب Emscripten ، با استفاده از دستور emcc و تقریباً همان آرگومان‌ها، آن را در WebAssembly کامپایل می‌کنید:

$ emcc hello.c -o hello.html

با این کار یک فایل hello.wasm و فایل HTML wrapper hello.html ایجاد می شود. هنگامی که فایل hello.html را از یک سرور وب ارائه می کنید، "Hello World" را خواهید دید که در کنسول DevTools چاپ شده است.

همچنین راهی برای کامپایل در WebAssembly بدون پوشش HTML وجود دارد:

$ emcc hello.c -o hello.js

مانند قبل، این یک فایل hello.wasm ایجاد می کند، اما این بار یک فایل hello.js به جای پوشش HTML. برای آزمایش، فایل جاوا اسکریپت حاصل hello.js با استفاده از Node.js اجرا کنید:

$ node hello.js
Hello World

بیشتر بدانید

این معرفی مختصر WebAssembly فقط نوک کوه یخ است. درباره WebAssembly در اسناد WebAssembly در MDN بیشتر بیاموزید و به اسناد Emscripten مراجعه کنید. در حقیقت، کار با WebAssembly می تواند کمی شبیه نحوه ترسیم میم جغد باشد، به خصوص که توسعه دهندگان وب آشنا با HTML، CSS و جاوا اسکریپت لزوماً به زبان هایی مانند C که باید از آنها کامپایل شود، مسلط نیستند. خوشبختانه کانال هایی مانند برچسب webassembly StackOverflow وجود دارد که در آن کارشناسان اغلب خوشحال می شوند اگر خوب بپرسید به شما کمک کنند.

قدردانی ها

این مقاله توسط Jakob Kummerow , Derek Schuff و Rachel Andrew بررسی شده است .