از کد بومی تا WebAssembly و جاوااسکریپت: برنامه‌های سلام کجاها اجرا می‌شوند؟

یک پرونده‌ی منبع و چندین مقصد: فایل اجرایی بومی، ماژول WASI، جاوااسکریپت مرورگر، صفحه‌ی وب، Workers لبه‌ی شبکه، ویرایشگر درون مرورگر، برنامه‌ی موبایل و سخت‌افزار خام، همراه با سود و زیان هر کدام.

در این مقاله
  1. برنامه‌های سلام کجا اجرا می‌شوند؟
  2. کامپایل زبان سلام به WebAssembly با WASI
  3. زبان سلام روی Cloudflare Workers
  4. کامپایل زبان سلام به جاوااسکریپت با salam js
  5. پروژه‌های وب: salam web و زبان صفحه
  6. ویرایشگر آنلاین سلام: کامپایلر در زبانه‌ی مرورگر
  7. اندروید، iOS و سخت‌افزار خام
  8. کدام هدف را انتخاب کنیم؟
  9. پرسش‌های پرتکرار

برنامه‌های زبان برنامه‌نویسی سلام می‌توانند به شکل فایل اجرایی بومی، ماژول WebAssembly، جاوااسکریپت در مرورگر، گرداننده‌ی درخواست روی Cloudflare Workers، درون یک ویرایشگر آنلاین و روی سخت‌افزار خام بدون سیستم‌عامل اجرا شوند. برای کامپایل سلام به WebAssembly با --target=wasm32-wasi می‌سازید و برای کامپایل به جاوااسکریپت از salam js استفاده می‌کنید. زبان و کتابخانه‌ی استاندارد یکی می‌ماند، ولی هر مقصد درباره‌ی نخ‌ها، پرونده‌ها، سوکت‌ها و عددها قاعده‌های خودش را دارد، و همین قاعده‌ها تعیین می‌کنند کدام هدف برای کدام کار مناسب است.

این نوشته همه‌ی هدف‌های موجود امروز را مرور می‌کند، فرمان‌ها را نشان می‌دهد و می‌گوید هر کدام هنوز چه کاری از دستش برنمی‌آید.

برنامه‌های سلام کجا اجرا می‌شوند؟

هدففرمانمحل اجرامناسب برای
فایل اجرایی بومیsalam build app.salamلینوکس، مک، ویندوز (x86-64، ARM، ۳۲ بیتی)ابزارها، سرورها، برنامه‌های رومیزی
WebAssembly (WASI)salam build app.salam --target=wasm32-wasiهر محیط اجرای WASI preview 1ماژول‌های جعبه‌شنی، افزونه‌ها، تابع‌های لبه
Cloudflare Workersساخت WASI به‌علاوه‌ی یک worker.js کوچکلبه‌ی شبکه‌ی Cloudflareگرداننده‌های HTTP نزدیک کاربران
جاوااسکریپتsalam js app.salamمرورگرها و Node.jsصفحه‌های تعاملی، منطق سمت مرورگر
پروژه‌ی وبsalam webمرورگرهایک صفحه و اسکریپت‌های سلامش در یک ساخت
ویرایشگر آنلاینeditor.salamlang.irهر مرورگر امروزییادگیری، اشتراک‌گذاری، آزمایش سریع
محیط توسعه‌ی موبایلبرنامه‌های اندروید و iOSگوشی و تبلتنوشتن و اجرای کد بدون اینترنت
مستقل (Freestanding)--target=x86_64-unknown-noneبدون سیستم‌عاملهسته‌ها و برنامه‌های راه‌اندازی

هدف‌های بومی نوشته‌ی جداگانه‌ای دارند: کامپایل متقابل در سلام. بقیه‌ی این نوشته درباره‌ی هدف‌های دیگر است.

برنامه‌ی زیر بی هیچ تغییری روی سه تا از این هدف‌ها اجرا می‌شود. با پرچم‌های زمان کامپایل می‌گوید کجا اجرا شده است: سلام جاوااسکریپت (در کد انگلیسی SALAM_JS) فقط وقتی تعریف شده است که پشتیبان جاوااسکریپت پرونده را کامپایل کند، و سلام معماری وب‌اسمبلی (در انگلیسی SALAM_ARCH_WASM) در ساخت‌های WebAssembly درست است.

سلام
تابع اول است(ن: صحیح): منطقی:
    اگر ن < ۲: برگشت نادرست پایان
    ناپایا مقسوم‌علیه := ۲
    تا مقسوم‌علیه * مقسوم‌علیه <= ن:
        اگر ن % مقسوم‌علیه == ۰: برگشت نادرست پایان
        مقسوم‌علیه++
    پایان
    برگشت درست
پایان

تابع ریشه:
    ناپایا یافته‌ها := ""
    ناپایا شمار := ۰
    تکرار ۲ تا ۵۰ در ن:
        اگر اول است(ن):
            یافته‌ها += " " + ن
            شمار++
        پایان
    پایان
    سرچاپ "اعداد اول کمتر از ۵۰:" + یافته‌ها
    سرچاپ "تعداد:"، شمار
    اگر تعریف‌شده(سلام جاوااسکریپت):
        سرچاپ "در حال اجرا به شکل جاوااسکریپت"
    وگرنه سلام معماری وب‌اسمبلی:
        سرچاپ "در حال اجرا به شکل وب‌اسمبلی"
    وگرنه:
        سرچاپ "در حال اجرا به شکل کد بومی"
    پایان
پایان
خروجیاعداد اول کمتر از ۵۰: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 تعداد: 15 در حال اجرا به شکل کد بومی

اگر با salam js کامپایل و با Node اجرا شود، خط آخر در حال اجرا به شکل جاوااسکریپت است؛ اگر با --target=wasm32-wasi ساخته و با پشتیبانی WASI در Node اجرا شود، در حال اجرا به شکل وب‌اسمبلی. دو خط اول همه‌جا یکسان است.

کامپایل زبان سلام به WebAssembly با WASI

WebAssembly یک قالب دودویی قابل‌حمل است که درون یک جعبه‌شنی (Sandbox) اجرا می‌شود. به‌تنهایی هیچ راهی برای چاپ کردن، خواندن پرونده یا نگاه کردن به ساعت ندارد؛ WASI (میانجی سیستمی WebAssembly) این‌ها را به شکل مجموعه‌ی کوچکی از تابع‌های واردشده اضافه می‌کند. سلام از راه پشتیبان LLVM خود، WASI preview 1 را هدف می‌گیرد:

ترمینال
salam build primes.salam --target=wasm32-wasi --release --output=primes.wasm

نسخه‌های رسمی کامپایلر یک sysroot برای WASI با خود دارند و پیونددهنده‌ی LLD را درون همان فرایند اجرا می‌کنند، پس برای برنامه‌ای مانند برنامه‌ی بالا نصب هیچ چیز دیگری لازم نیست. گردش‌کار انتشار در هر نسخه این را می‌سنجد: یک برنامه‌ی کوچک را در حالی می‌سازد که WASI_SDK_PATH تعریف نشده و PATH کمینه است، و سپس آن را با ماژول درونی node:wasi در Node اجرا می‌کند. اسکریپت اجرای آن در Node چند خط بیشتر نیست:

JavaScript
const { WASI } = require('node:wasi');
const fs = require('node:fs');

const wasi = new WASI({
  version: 'preview1',
  args: ['primes'],
  returnOnExit: true,
});
WebAssembly.instantiate(
  fs.readFileSync('primes.wasm'),
  wasi.getImportObject(),
).then(({ instance }) => process.exit(wasi.start(instance)));

ماژول برنامه‌ی اعداد اول حدود ۳۰ کیلوبایت است. برنامه‌هایی که بخش بیشتری از محیط اجرا را به کار می‌گیرند به بخش بیشتری از کتابخانه‌ی C نیاز دارند: برنامه‌ای که پشته‌ی سرور HTTP را به کار می‌برد پشتیبانی نخ هم لازم دارد، و sysroot مربوط به WASI که در نسخه‌ی ۰٫۶٫۲ جاسازی شده بود نمی‌توانست چنین برنامه‌ای را پیوند دهد (پیونددهنده نبود libpthread را گزارش می‌کرد). برای این برنامه‌ها wasi-sdk را نصب کنید؛ کامپایلر آن را از راه WASI_SDK_PATH، ~/.salam/wasi-sdk یا /opt/wasi-sdk پیدا می‌کند.

این‌که یک ماژول WASI چه کارهایی می‌تواند بکند را میزبانش تعیین می‌کند. خروجی و خطای استاندارد، آرگومان‌ها، متغیرهای محیطی، ساعت‌ها و عددهای تصادفی بخشی از preview 1 هستند. پرونده‌ها فقط وقتی دیده می‌شوند که میزبان یک پوشه در اختیار ماژول بگذارد، و ماژول نمی‌تواند خودش اتصال شبکه باز کند.

زبان سلام روی Cloudflare Workers

Cloudflare Workers می‌تواند ماژول‌های WebAssembly را اجرا کند و سلام از همین برای بردن یک برنامه‌ی کامل net/http به لبه‌ی شبکه استفاده می‌کند. خود برنامه یک سرور وب معمولی سلام است که با net/router ساخته شده:

سلام
واردسازی شبکه.اچ تی تی پی
واردسازی شبکه.مسیریاب

تابع سلام کن(زمینه: صحیح۶۴):
    کجا := اچ تی تی پی.لبه است() ? "در لبه‌ی شبکه" : "روی یک سرور"
    اچ تی تی پی.متن(زمینه، "سلام " + اچ تی تی پی.پارامتر(زمینه، "name") + "، از سلام " + کجا + "\n")
پایان

تابع ریشه:
    ناپایا م := مسیریاب.مسیریاب جدید()
    مسیریاب.دریافت(م، "/hello/:name"، سلام کن)
    ناپایا س := اچ تی تی پی.سرور جدید(۸۰۸۰)
    اچ تی تی پی.استفاده(س، م)
    اچ تی تی پی.اجرا(س)
پایان

اگر به شکل بومی ساخته شود، روی درگاه ۸۰۸۰ گوش می‌دهد. اگر با --target=wasm32-wasi ساخته شود، رفتار اچ تی تی پی.اجرا (در انگلیسی http.Run) عوض می‌شود: در هر فرایند دقیقاً یک درخواست را پاسخ می‌دهد. اسکریپت میزبان std/net/http/cloudflare/worker.js درخواست خام HTTP/1.1 را در ورودی استاندارد ماژول می‌نویسد و پاسخ را از توصیف‌گر پرونده‌ی ۳ می‌خواند؛ خروجی سرچاپ به لاگ‌های Worker می‌رود و os.Env متغیرها و رازهای wrangler را می‌خواند. نقطه‌ی ورود Worker سه خط است:

JavaScript
import { createWorker } from './worker-host.js';
import app from './build/app.wasm';
export default createWorker(app);

محدودیت‌ها از خود این مدل می‌آیند. هر درخواست یک نمونه‌ی تازه می‌گیرد، پس متغیرهای سراسری میان درخواست‌ها باقی نمی‌مانند. سیستم پرونده‌ای در کار نیست، پس router.Static چیزی برای سرو کردن ندارد (از دارایی‌های ایستای Workers استفاده کنید). سوکت خروجی وجود ندارد، پس http.Get نمی‌تواند از درون ماژول سرور دیگری را صدا بزند؛ به جای آن چند خط fetch در worker.js این کار را انجام می‌دهد. فضای ذخیره‌سازی Workers مانند KV، D1 و R2 ناهمگام است و باید از راه اسکریپت میزبان رد و بدل شود.

مخزن چهار برنامه‌ی کامل Workers را در tests/en/apps/ نگه می‌دارد: cloudflare-worker-hello، -router، -telegram-bot و -pure، که آخری برنامه‌ای به سبک CGI است که درخواست را از متغیرهای محیطی می‌خواند و اصلاً به net/http نیازی ندارد. برنامه‌ی سبک CGI با sysroot جاسازی‌شده‌ی WASI پیوند می‌خورد؛ برنامه‌های net/http، همان‌طور که بالاتر گفته شد، wasi-sdk لازم دارند.

کامپایل زبان سلام به جاوااسکریپت با salam js

برای کدی که باید در زبانه‌ی مرورگر اجرا شود و با صفحه کار کند، سلام یک پشتیبان جاوااسکریپت دارد. این پشتیبان برنامه را به یک پرونده‌ی جاوااسکریپت تبدیل می‌کند که به طور پیش‌فرض فشرده (Minified) است، یا اگر نام خروجی به .html ختم شود، به یک صفحه‌ی HTML خودکفا:

ترمینال
salam js counter.salam --output=counter.js
salam js counter.salam --output=counter.html

بسته‌ی dom (به فارسی دام) دسترسی کد سلام به صفحه را فراهم می‌کند: ساختن عنصرها، تعیین متن و ویژگی‌ها، سبک‌ها، کلاس‌ها، رویدادها، زمان‌سنج‌ها و قاب‌های پویانمایی.

سلام
واردسازی دام

ناپایا کلیک‌ها := ۰
ناپایا برچسب := ۰ برگردان صحیح۶۴

تابع ریشه:
    برچسب = دام.بساز("p")
    دام.تنظیم متن(برچسب، "هنوز کلیکی نشده است")
    دکمه := دام.بساز("button")
    دام.تنظیم متن(دکمه، "کلیک کنید")
    دام.هنگام رویداد(دکمه، "click"، ():
        کلیک‌ها++
        دام.تنظیم متن(برچسب، کلیک‌ها + " بار کلیک شد")
    پایان)
    دام.افزودن(دام.بدنه()، دکمه)
    دام.افزودن(دام.بدنه()، برچسب)
پایان

اگر به counter.html کامپایل شود، کل صفحه همراه با اسکریپتش کمتر از ۱ کیلوبایت است. کد تولیدشده جاوااسکریپت ساده است: نه WebAssembly، نه کتابخانه‌ی زمان اجرایی که باید دانلود شود، و فراخوانی‌هایی مانند دام.تنظیم متن مستقیم به عملیات DOM تبدیل می‌شوند. پوشه‌ی tests/en/js حدود صد برنامه دارد که با salam js کامپایل و اجرا می‌شوند و خروجی‌شان با خروجی مورد انتظار مقایسه می‌شود.

پشتیبان جاوااسکریپت محدودیت‌هایی دارد که از خود جاوااسکریپت می‌آیند:

  • عددهای صحیح ۶۴ بیتی. عددهای جاوااسکریپت فقط تا 2^53 عدد صحیح را دقیق نگه می‌دارند. روی این پشتیبان یک صحیح۶۴ بزرگ‌تر از این حد دقتش را از دست می‌دهد: 9007199254740993 + 2 به شکل بومی 9007199254740995 و در جاوااسکریپت 9007199254740994 چاپ می‌کند. صحیح۱۲۸ و طبیعی۱۲۸ دقیق‌اند، چون به BigInt تبدیل می‌شوند.
  • نخ‌ها. نخ‌ساز (در انگلیسی spawn) روی این پشتیبان پشتیبانی نمی‌شود و تابع‌های اتمی به خواندن و نوشتن ساده تبدیل می‌شوند، چون کد تولیدشده روی تنها نخ صفحه اجرا می‌شود.
  • دسترسی به سیستم. پرونده‌ها، سوکت‌ها و فرایندها مال هدف‌های بومی و WASI هستند. در مرورگر، بسته‌ی dom صفحه، رویدادها، زمان‌سنج‌ها، ذخیره‌سازی محلی، بوم (Canvas) و دسترسی عمومی به ویژگی‌ها و متدهای هر شیء جاوااسکریپت را در اختیارتان می‌گذارد.

پروژه‌های وب: salam web و زبان صفحه

سلام یک زبان صفحه هم دارد: یک بلوک صفحه: (در انگلیسی page:) عنصرهای HTML، سبک‌های CSS و رویدادها را با نحو سلام توصیف می‌کند و salam page build آن را به HTML، CSS و جاوااسکریپت تبدیل می‌کند. salam web یک پروژه‌ی وب کامل، یعنی یک صفحه به‌علاوه‌ی برنامه‌های سلامی که با script: src="app.salam" بار می‌کند، را در یک پرونده‌ی .html می‌سازد، یا با --split در پرونده‌های جداگانه‌ی .html، .css و .js. نوشته‌ی توسعه‌ی وب با سلام و درس‌های آموزش از متن صفحه به بعد زبان صفحه را آموزش می‌دهند.

ویرایشگر آنلاین سلام: کامپایلر در زبانه‌ی مرورگر

ویرایشگر آنلاین سلام را بدون سرور و بدون نصب اجرا می‌کند و نمونه‌ی خوبی از همکاری این هدف‌ها با هم است:

  • کامپایلر سلام (تحلیلگر واژگانی، تجزیه‌گر، تحلیل معنایی، مفسر و موتور چیدمان) با --backend=c --emit-c --target=wasm32-unknown-emscripten به C تبدیل و با Emscripten به WebAssembly کامپایل می‌شود، همراه با نسخه‌ی فشرده‌ای از کتابخانه‌ی استاندارد.
  • رابط کاربری خود ویرایشگر به زبان سلام نوشته شده است: page.salam ساختار و سبک‌ها را نگه می‌دارد و با salam web ساخته می‌شود، و app.salam که وضعیت، رنگ‌آمیزی نحوی، میان‌برهای صفحه‌کلید و راه‌اندازی WebAssembly را بر عهده دارد، با salam js کامپایل می‌شود.
  • برنامه‌هایی که در ویرایشگر نوشته می‌شوند در مفسرِ درون ماژول WebAssembly اجرا می‌شوند، چون مرورگر نمی‌تواند کامپایلر C اجرا کند یا یک فایل اجرایی بومی تازه‌ساخته را بار کند.

چون برنامه‌ها در مفسر اجرا می‌شوند، ویرایشگر برای یادگیری و بررسی منطق ایده‌آل است، ولی کدی که نخ، FFI یا سرعت کامل بومی لازم دارد باید با کامپایلر روی دستگاه خودتان ساخته شود. نوشته‌ی تفاوت salam run و salam build مفسر را توضیح می‌دهد.

اندروید، iOS و سخت‌افزار خام

پروژه‌های android/ و ios/ در مخزن، برنامه‌های محیط توسعه‌ی بومی برای گوشی‌اند. هر دو کل کامپایلر را در خود دارند که از یک نقطه‌ی ورود کتابخانه‌ای به C تبدیل شده است، پس رنگ‌آمیزی، بررسی و اجرا بدون اینترنت کار می‌کنند. برنامه‌ها در مفسر اجرا می‌شوند: نه اندروید و نه iOS اجازه نمی‌دهند یک برنامه فایل اجرایی‌ای را که همین الان ساخته اجرا کند، پس salam build فرمانی برای رایانه‌ی رومیزی می‌ماند. برنامه‌ی اندروید از اندروید ۵٫۰ به بعد و برنامه‌ی iOS از iOS 15 به بعد را پشتیبانی می‌کند. فایل APK اندروید فقط وقتی به یک نسخه پیوست می‌شود که آن نسخه یکی منتشر کرده باشد.

در سوی دیگر، هدف *-unknown-none برنامه‌ای بدون سیستم‌عامل، بدون libc و بدون فراخوانی سیستمی می‌سازد، برای هسته‌ها و برنامه‌های راه‌اندازی. کامپایلر یک محیط اجرای کوچک با یک تخصیص‌دهنده‌ی حافظه و خروجی سریال، VGA و کنسول خطایابی QEMU را پیوند می‌دهد، و tests/kernel/ زیر QEMU بالا می‌آید. این هدف نوشته‌ی جداگانه‌ای دارد: سلام مستقل و سخت‌افزار خام.

برای برنامه‌های رومیزی با رابط وب، سلام به جای WebAssembly از WebView سیستم استفاده می‌کند؛ نوشته‌ی برنامه‌های رومیزی و گرافیکی با سلام را ببینید.

کدام هدف را انتخاب کنیم؟

نیاز شماانتخاب
بیشترین سرعت، نخ‌ها، پرونده‌ها، سوکت‌ها، کتابخانه‌های Cساخت بومی
ماژولی جعبه‌شنی که میزبان بتواند جاسازی و محدودش کندwasm32-wasi
یک گرداننده‌ی HTTP در لبه‌ی شبکه بدون نگه‌داری سرورwasm32-wasi روی Cloudflare Workers
منطقی که در مرورگر بازدیدکننده اجرا شود و صفحه را تغییر دهدsalam js
وب‌سایتی ایستا یا تعاملی که به زبان سلام نوشته شده باشدزبان صفحه با salam page build یا salam web
یادگیری یا اشتراک‌گذاری یک تکه کدویرایشگر آنلاین
یک سیستم‌عامل یا برنامه‌ی سخت‌افزار خامیک هدف مستقل *-none

الگوی معقول برای یک محصول وب این است که منطق مشترک (اعتبارسنجی، قالب‌بندی، محاسبه‌های خالص) را در بسته‌های ساده‌ی سلام و بدون فراخوانی سیستمی نگه دارید، آن را برای سرور به شکل بومی و برای مرورگر با salam js کامپایل کنید، و کد وابسته به سکو را در لبه‌ها و پشت اگر تعریف‌شده(سلام جاوااسکریپت): یا اگر سلام معماری وب‌اسمبلی: بگذارید.

پرسش‌های پرتکرار

آیا زبان سلام به WebAssembly کامپایل می‌شود؟

بله. salam build app.salam --target=wasm32-wasi یک ماژول WASI preview 1 می‌سازد. نسخه‌های رسمی کامپایلر sysroot و پیونددهنده‌ی لازم برای برنامه‌های ساده را دارند؛ برنامه‌هایی که پشته‌ی سرور HTTP را به کار می‌برند به نصب wasi-sdk نیاز دارند.

آیا سلام به جاوااسکریپت کامپایل می‌شود؟

بله. salam js app.salam یک پرونده‌ی جاوااسکریپت فشرده می‌نویسد، یا اگر خروجی به .html ختم شود، یک صفحه‌ی HTML کامل. بسته‌ی dom دسترسی به عنصرها، رویدادها و زمان‌سنج‌ها را فراهم می‌کند. روی این پشتیبان، عددهای صحیح۶۴ بالاتر از 2^53 دقتشان را از دست می‌دهند.

آیا می‌شود بدون نصب هیچ چیز، سلام را در مرورگر اجرا کرد؟

بله. ویرایشگر آنلاین در editor.salamlang.ir کامپایلر و مفسر را به شکل WebAssembly درون صفحه اجرا می‌کند و رابط انگلیسی، فارسی و عربی دارد. برنامه‌ها آن‌جا در مفسر اجرا می‌شوند، پس محاسبه‌های خیلی سنگین را بهتر است به شکل بومی بسازید.

آیا زبان سلام روی Cloudflare Workers اجرا می‌شود؟

بله. برنامه‌ای با net/http و net/router که برای wasm32-wasi ساخته شده، از راه یک اسکریپت میزبان که همراه کتابخانه‌ی استاندارد عرضه می‌شود، در هر نمونه یک درخواست را پاسخ می‌دهد. متغیرهای سراسری میان درخواست‌ها نمی‌مانند و فراخوانی‌های بیرونی از راه fetch در اسکریپت میزبان انجام می‌شوند.

کامپایلر و سرعت

از کد منبع تا فایل اجرایی: کامپایلر زبان برنامه‌نویسی سلام مرحله به مرحله چگونه کار می‌کند

یک برنامه‌ی سلام را در مسیر کامپایلر دنبال کنید: توکن‌ها، درخت نحو، بررسی گونه‌ها و قاعده‌ها، کد C یا LLVM IR یا کد ماشینِ تولیدشده و پیوند نهایی، با خروجی واقعی هر مرحله.

خواندن مقاله
کامپایلر و سرعت

وقتی یک زبان خودش را می‌سازد: درون کامپایلر خودمیزبان زبان برنامه‌نویسی سلام

کامپایلر سلام به زبان سلام نوشته شده است. این روایت چگونگی رسیدن به این نقطه است: هر نسخه چطور از نسخه‌ی پیشین بوت‌استرپ می‌شود، خودمیزبانی برای یک زبان جوان چه سودی دارد و چه هزینه‌ای.

خواندن مقاله