На наших глазах родилась новая компьютерная архитектура: WebAssembly. Я уже упоминал как-то. Спецификацию можно скачать по ссылке:
webassembly.github.io/spec/core/_download/WebAssembly.pdf
Фактически это самая массовая виртуальная машина нынче. WebAssembly реализована во всех современных веб-браузерах:
Чтобы программы, написанные на WebAssembly (в простонародии Wasm), могли общаться с внешним миром, разработан стандарт WASI. По аналогии с юниксом можно считать это набором системных вызовов.
Чтобы программировать на самом близком к "железу" уровне, для традиционных компьютеров используют язык ассемблера. Но мы же не дикари какие в 21-м веке. 😀 Для Wasm сделан высокоуровневый язык AssemblyScript. Он напоминает TypeScript, но со статической типизацией, и транслируется непосредственно в машинные команды Wasm.
В качестве примера попросим Грок реализовать на AssemblyScript традиционную юниксную команду /bin/echo.
webassembly.github.io/spec/core/_download/WebAssembly.pdf
Фактически это самая массовая виртуальная машина нынче. WebAssembly реализована во всех современных веб-браузерах:
- Google Chrome: полная поддержка с версии 57 (март 2017 г.).
- Mozilla Firefox: полная поддержка с версии 52 (март 2017 г.).
- Microsoft Edge: полная поддержка с версии 16 (октябрь 2017 г.).
- Safari: полная поддержка с версии 11 (сентябрь 2017 г.).
- Opera: полная поддержка с версии 44 (март 2017 г.).
- Brave: поддерживает WebAssembly, так как он основан на Chromium.
- Samsung Internet: полная поддержка в последних версиях.
- UC Browser: поддерживает WebAssembly в современных версиях.
- Tor Browser: поддерживает WebAssembly, так как он основан на Firefox.
Чтобы программы, написанные на WebAssembly (в простонародии Wasm), могли общаться с внешним миром, разработан стандарт WASI. По аналогии с юниксом можно считать это набором системных вызовов.
Чтобы программировать на самом близком к "железу" уровне, для традиционных компьютеров используют язык ассемблера. Но мы же не дикари какие в 21-м веке. 😀 Для Wasm сделан высокоуровневый язык AssemblyScript. Он напоминает TypeScript, но со статической типизацией, и транслируется непосредственно в машинные команды Wasm.
В качестве примера попросим Грок реализовать на AssemblyScript традиционную юниксную команду /bin/echo.
Как можно видеть, используются системные вызовы args_sizes_get, args_get, fd_write. Некоторую трудность вызывает преобразование строк из внутреннего формата UTF-16 в UTF-8 для fd_write. Компилируем, запускаем:// WASI bindings for command-line arguments and output @external("wasi_snapshot_preview1", "args_sizes_get") export declare function args_sizes_get(argc: i32, argv_buf_size: i32): i32; @external("wasi_snapshot_preview1", "args_get") export declare function args_get(argv: i32, argv_buf: i32): i32; @external("wasi_snapshot_preview1", "fd_write") export declare function fd_write(fd: i32, iovs: i32, iovs_len: i32, nwritten: i32): i32; // Function to write an ASCII string to stdout function writeAscii(str: string): void { let buffer = new ArrayBuffer(str.length); let ptr = changetype<i32>(buffer); let offset = 0; for (let i = 0; i < str.length; i++) { let ch = str.charCodeAt(i); // Copy only ASCII characters. if (ch <= 0x7F) { store<u8>(ptr + offset, ch); offset++; } } // Allocate iovec structure (ptr, len) let iovec_ptr = changetype<i32>(new ArrayBuffer(8)); store<i32>(iovec_ptr, changetype<i32>(buffer)); store<i32>(iovec_ptr + 4, offset); // Write to stdout (fd=1) let nwritten_ptr = changetype<i32>(new ArrayBuffer(4)); fd_write(1, iovec_ptr, 1, nwritten_ptr); } // Function to write UTF-8 bytes directly to stdout function writeUtf8(arg_ptr: i32): void { // Calculate length of null-terminated UTF-8 string let length = 0; while (load<u8>(arg_ptr + length) != 0) { length++; } // Allocate iovec structure (ptr, len) let iovec_ptr = changetype<i32>(new ArrayBuffer(8)); store<i32>(iovec_ptr, arg_ptr); store<i32>(iovec_ptr + 4, length); // Write to stdout (fd=1) let nwritten_ptr = changetype<i32>(new ArrayBuffer(4)); fd_write(1, iovec_ptr, 1, nwritten_ptr); } // Main function export function _start(): void { // Allocate memory for argument count and buffer size let argc_ptr = changetype<i32>(new ArrayBuffer(4)); let argv_buf_size_ptr = changetype<i32>(new ArrayBuffer(4)); // Get argument count and buffer size args_sizes_get(argc_ptr, argv_buf_size_ptr); let argc = load<i32>(argc_ptr); let argv_buf_size = load<i32>(argv_buf_size_ptr); // Allocate memory for argv pointers and buffer let argv = changetype<i32>(new ArrayBuffer(argc * 4)); let argv_buf = changetype<i32>(new ArrayBuffer(argv_buf_size)); // Get argument values args_get(argv, argv_buf); // Skip the first argument (program name) let firstArg = 1; // If no arguments (besides program name), print newline and exit if (argc <= 1) { writeAscii("\n"); return; } // Iterate through arguments for (let i = firstArg; i < argc; i++) { // Get pointer to argument string let arg_ptr = load<i32>(argv + i * 4); // Write UTF-8 bytes directly writeUtf8(arg_ptr); // Add space between arguments, but not after the last one if (i < argc - 1) { writeAscii(" "); } } // Print final newline writeAscii("\n"); }
Работает! И даже русские буквы в кодировке UTF-8 правильно показывает. Глянем, как выглядят машинные команды. Дизассемблируем с помощью wasm2wat и отыщем функцию _start().$ asc echo.ts --use abort= -O -o echo.wasm $ wasmtime echo.wasm foobar йцукенг 12 3 456 foobar йцукенг 12 3 456
Вроде ничо так выглядит. На этом вполне можно программировать. А если учесть, что в последнем WASIX появились системные вызовы fork(), exec() и wait(), можно даже попробовать портонуть юниксный /bin/sh и весь прочий набор команд BSD.(func $echo/_start (type 0) (local i32 i32 i32 i32 i32 i32) global.get $~lib/memory/__stack_pointer i32.const 4 i32.sub global.set $~lib/memory/__stack_pointer global.get $~lib/memory/__stack_pointer i32.const 1204 i32.lt_s if ;; label = @1 unreachable end global.get $~lib/memory/__stack_pointer i32.const 0 i32.store i32.const 4 call $~lib/arraybuffer/ArrayBuffer#constructor local.tee 0 i32.const 4 call $~lib/arraybuffer/ArrayBuffer#constructor local.tee 1 call $echo/args_sizes_get drop local.get 1 i32.load local.set 1 local.get 0 i32.load local.tee 5 i32.const 2 i32.shl call $~lib/arraybuffer/ArrayBuffer#constructor local.tee 4 local.get 1 call $~lib/arraybuffer/ArrayBuffer#constructor call $echo/args_get drop i32.const 1 local.set 0 local.get 5 i32.const 1 i32.le_s if ;; label = @1 global.get $~lib/memory/__stack_pointer i32.const 1136 i32.store i32.const 1136 call $echo/writeAscii global.get $~lib/memory/__stack_pointer i32.const 4 i32.add global.set $~lib/memory/__stack_pointer return end loop ;; label = @1 local.get 0 local.get 5 i32.lt_s if ;; label = @2 local.get 4 local.get 0 i32.const 2 i32.shl i32.add i32.load local.set 2 i32.const 0 local.set 1 loop ;; label = @3 local.get 1 local.get 2 i32.add i32.load8_u if ;; label = @4 local.get 1 i32.const 1 i32.add local.set 1 br 1 (;@3;) end end i32.const 8 call $~lib/arraybuffer/ArrayBuffer#constructor local.tee 3 local.get 2 i32.store local.get 3 local.get 1 i32.store offset=4 i32.const 1 local.get 3 i32.const 1 i32.const 4 call $~lib/arraybuffer/ArrayBuffer#constructor call $echo/fd_write drop local.get 0 local.get 5 i32.const 1 i32.sub i32.lt_s if ;; label = @3 global.get $~lib/memory/__stack_pointer i32.const 1168 i32.store i32.const 1168 call $echo/writeAscii end local.get 0 i32.const 1 i32.add local.set 0 br 1 (;@1;) end end global.get $~lib/memory/__stack_pointer i32.const 1136 i32.store i32.const 1136 call $echo/writeAscii global.get $~lib/memory/__stack_pointer i32.const 4 i32.add global.set $~lib/memory/__stack_pointer)

no subject
Date: 2025-04-28 07:48 (UTC)no subject
Date: 2025-04-28 12:33 (UTC)no subject
Date: 2025-04-28 17:43 (UTC)no subject
Date: 2025-04-28 18:10 (UTC)no subject
Date: 2025-04-28 18:29 (UTC)no subject
Date: 2025-04-28 18:38 (UTC)no subject
Date: 2025-04-28 13:36 (UTC)Всё уже украдено до нас: webvm.io Правда, там Linux, не BSD, но принцип тот же :-)
no subject
Date: 2025-04-28 17:43 (UTC)no subject
Date: 2025-04-28 18:22 (UTC)no subject
Date: 2025-04-28 18:37 (UTC)