Fortran chạy trong WebAssembly
(gws.phd)- Để đưa mã số trị Fortran, một tài sản tính toán khoa học/kỹ thuật lâu đời, lên trình duyệt, cần có đường dẫn biên dịch WebAssembly; webR dùng LLVM
flang-newđã được vá f2c, LFortran, Dragonegg, Classic Flang và LLVM Flang đều có hạn chế riêng về hỗ trợ Fortran hiện đại, kiến trúc đích và khả năng bảo trì, nên hiện vẫn chưa có một giải pháp chuẩn đơn giản- LLVM Flang không hỗ trợ trực tiếp đích
wasm32-unknown-emscripten, nên cần thêmTargetWasm32; khi liên kết runtime, khác biệt về kích thướclonggiữa host và target gây ra vấn đề - Kết hợp
flang-newđã vá, Emscripten và thư viện tĩnh runtime Fortran cho phép gọi subroutine Fortran từ C hoặc JavaScript, đồng thời xử lý được cả đối sốPRINT,ALLOCATE,CHARACTER - Đã build các triển khai tham chiếu BLAS 3.12.0 và LAPACK 3.12.0 thành thư viện tĩnh WebAssembly để chạy demo phân loại chữ số viết tay và nội suy đa thức trong trình duyệt
Đưa mã số trị Fortran hiện có lên trình duyệt
- Fortran là một ngôn ngữ cũ xuất hiện từ năm 1957, nhưng đã được dùng trong thời gian dài cho tính toán khoa học/kỹ thuật, và Fortran hiện đại phần lớn đã thoát khỏi các ràng buộc fixed-form của Fortran 77
- Mục tiêu là biên dịch các routine Fortran hiện đại sang WebAssembly để chạy trong trình duyệt, nhận các đối số số học, tính toán bằng các routine BLAS/LAPACK rồi trả kết quả hoặc in ra console
- Cách tiếp cận này cho phép đưa các môi trường lập trình cấp cao phụ thuộc vào BLAS/LAPACK như SciPy hoặc R lên web
- Có thể tận dụng các công cụ và thư viện dựa trên Fortran đã được kiểm chứng, thay vì viết lại các routine số trị bằng JavaScript hoặc Rust
- Dự án webR biên dịch mã Fortran sang WebAssembly bằng trình biên dịch LLVM
flang-newđã được vá - Cách làm hiện tại dựa vào các bản hack, và nếu không có sự trợ giúp của các nhà phát triển compiler giàu kinh nghiệm hơn thì khó đóng góp các thay đổi này ngược lại cho LLVM
Tình hình công cụ Fortran→WebAssembly
- Tính đến năm 2024 có nhiều công cụ và toolchain, nhưng vẫn chưa có giải pháp đơn giản với đầy đủ tính năng
-
f2cf2cchuyển Fortran 77 thành mã C, để Emscripten có thể biên dịch nó sang WebAssembly- Pyodide dùng cách này khi biên dịch các package Python có chứa mã Fortran
- Ngay cả trong lộ trình của Pyodide, cách này cũng được đánh giá là “không hoạt động tốt”
- Không phù hợp với mã Fortran hiện đại, và sau khi chuyển đổi vẫn cần vá rất rộng cùng các lỗi nghiêm trọng
-
LFortran
- LFortran đã tăng đáng kể tính năng trong vài năm gần đây và có thể biên dịch trực tiếp sang WebAssembly
- Các nhà phát triển cho biết vì vẫn đang ở giai đoạn alpha, có thể sẽ gặp vấn đề khi biên dịch mã thực tế
- Một số dự án như MINPACK có thể biên dịch được, nhưng vì chưa hỗ trợ toàn bộ đặc tả Fortran nên các dự án lớn hơn có thể thất bại
- Mục tiêu phát triển là hỗ trợ toàn bộ Fortran 2018, và một tính năng nổi bật là REPL Fortran tương tác giống Jupyter
-
Dragonegg
- Dragonegg là plugin GCC dùng frontend GCC để xuất LLVM IR
- Có thể tạo đầu ra WebAssembly bằng backend LLVM, và đây là cách webR dùng lúc đầu để biên dịch mã nguồn Fortran sang WebAssembly
- Phiên bản mới nhất được hỗ trợ là
gcc-4.8vàllvm-3.3, nên cần GCC/LLVM rất cũ - Phần lớn người dùng cần VM hoặc container Docker, và LLVM IR do Dragonegg xuất ra cũng phải qua bước hậu xử lý bổ sung để tạo đầu ra WebAssembly
- Vào năm 2020, thực tế đây gần như là cách khả thi duy nhất để biên dịch mã Fortran sang WebAssembly
-
Classic Flang
- Classic Flang là trình biên dịch Fortran nhắm LLVM, dựa trên PGI/NVIDIA
pgfortranđã được mã nguồn mở - Không hỗ trợ đầu ra 32-bit nên không thể dùng cho target
wasm32 - Tại thời điểm viết, Firefox, Chrome và Node hỗ trợ
wasm64, nhưng tính năng này bị khóa sau feature flag - Tài liệu dự án cũng lưu ý rằng chọn Classic Flang cho dự án mới có thể không phải là ý hay
- Classic Flang là trình biên dịch Fortran nhắm LLVM, dựa trên PGI/NVIDIA
-
LLVM Flang
- LLVM Flang là dự án triển khai lại từ đầu frontend Fortran cho LLVM, và được đưa vào dự án LLVM từ LLVM 11
- Dù chưa được xem là sẵn sàng cho production, phiên bản tiền production của
flang-newđã khá dùng được để biên dịch mã Fortran thực tế - Ở trạng thái mặc định, nó không thể tạo đầu ra WebAssembly
- Nhờ thiết kế mô-đun của LLVM, có thể dùng frontend Flang cùng backend WebAssembly của LLVM
- Điều này cũng đã khả thi vào năm 2020, nhưng cần bản vá LLVM lớn hơn, chèn routine toán học tùy chỉnh và quy trình biên dịch nhiều bước
- Hiện nay, nhờ sự phát triển của frontend
flang-new, chỉ cần thay đổi nhỏ trong mã nguồn LLVM là có thể tạo compiler Fortran→WebAssembly
Build LLVM Flang cho WebAssembly
- LLVM cài bằng package manager có thể không bao gồm binary
flang-new- Ví dụ, trên LLVM v17.0.6 của macOS Homebrew không tìm thấy lệnh
flang-new
- Ví dụ, trên LLVM v17.0.6 của macOS Homebrew không tìm thấy lệnh
- Vì cần sửa mã nguồn LLVM Flang, tác giả tự build mã nguồn LLVM v18.1.1
- Cấu hình CMake đặt target triple mặc định là
wasm32-unknown-emscripten, đồng thời bật targetWebAssemblyvà các projectclang;flang;mlir - Sau khi build xong, có thể kiểm tra các thông tin sau bằng
build/bin/flang-new --version- Phiên bản:
flang-new version 18.1.1 - Target:
wasm32-unknown-emscripten - Thread model:
posix
- Phiên bản:
Vấn đề đầu tiên: target wasm32 chưa được triển khai
- Khi biên dịch subroutine Fortran đơn giản
foo.f08bằngflang-new, lỗi sau xuất hiệnnot yet implemented: target not implemented
- Nguyên nhân là target triple
wasm32-unknown-emscriptenchưa được triển khai trongflang-new - Cách khắc phục là bản vá thêm đặc tính target
TargetWasm32vàoflang/lib/Optimizer/CodeGen/Target.cpp- Đặt độ rộng mặc định là 32
- Định nghĩa marshalling để chuyển đổi đối số số phức và kiểu trả về sang kiểu LLVM phía WebAssembly
- Nối nhánh
llvm::Triple::ArchType::wasm32vớiTargetWasm32
- Sau khi vá và build lại, mã nguồn Fortran được biên dịch thành object WebAssembly
- Kết quả
file foo.o:WebAssembly (wasm) binary module version 0x1 (MVP) - Có thể thấy symbol
foo_trongllvm-nm foo.o
- Kết quả
Gọi subroutine Fortran từ C và JavaScript
- Routine Fortran thường truyền đối số theo tham chiếu, và có thể khai báo cách dùng đối số bằng
INTENT() - Subroutine Fortran ví dụ
foonhận các đối số số nguyênx,y,zvà thực hiệnz = x + y - Khi build native bằng
gfortran -c foo.f08 -o foo.o, tên symbol có thể được thêm dấu gạch dưới ở cuối nhưfoo_ - Khi gọi từ C, khai báo symbol bên ngoài như
extern void foo_(int*, int*, int*);và truyền địa chỉ của đối số - Trong WebAssembly, có thể dùng Emscripten để liên kết
foo.odoflang-newtạo với mã Cemcc main.c foo.o -o main.js- Đầu ra
node main.js:1 + 1 = 2
-
Gọi trực tiếp từ JavaScript
- Cũng có thể gọi trực tiếp subroutine Fortran từ JavaScript mà không cần mã C
- Ở bước liên kết Emscripten, export
_foo_,_malloc,_free emcc foo.o -sEXPORTED_FUNCTIONS=_foo_,_malloc,_free -o foo.js- JavaScript cấp phát vùng lưu số nguyên bằng
Module._malloc(), ghi giá trị vàoModule.HEAPU32, rồi gọiModule._foo_(x, y, z) - Kết quả chạy như sau
x = 123y = 456x + y = 579- Trong trình duyệt, tải
foo.jsvàstandalone.jstừ HTML để thấy cùng kết quả trong JavaScript console
Vấn đề thứ hai: thư viện runtime Fortran
- Khi build subroutine Fortran có chứa
PRINT *, "Hello, World!", bước liên kết báo lỗi thiếu runtime symbol_FortranAioBeginExternalListOutput_FortranAioOutputAscii_FortranAioEndIoStatement
- Nguyên nhân là thư viện runtime LLVM Fortran chưa được biên dịch cho WebAssembly
- Thư viện runtime được viết bằng C++ trong
llvm-project/flang/runtimecủa cây mã nguồn LLVM - Nếu build mã nguồn runtime bằng
em++vàemarcủa Emscripten, có thể tạo thư viện tĩnhbuild/flang/runtime/libFortranRuntime.a - Liên kết thư viện này giúp bản build
Hello, World!tiếp tục, nhưng ban đầu phát sinh cảnh báo không khớp signature hàm
Vấn đề thứ ba: khác biệt kích thước long giữa host và target
- Khi liên kết
hello.ovới thư viện runtime Fortran, xuất hiện cảnh báo không khớp signature của_FortranAioOutputAscii- Phía object Fortran kỳ vọng
(i32, i32, i64) -> i32 - Phía runtime build bằng Emscripten định nghĩa
(i32, i32, i32) -> i32
- Phía object Fortran kỳ vọng
- WebAssembly yêu cầu kiểu đối số và kiểu trả về của các symbol được định nghĩa qua nhiều đơn vị biên dịch phải nhất quán
- Vấn đề này không chỉ dừng ở cảnh báo; khi chạy bằng Node, nó thất bại với
RuntimeError: unreachable - Trong
RTBuilder.hcủa LLVM Flang có chú thích TODO rằng việc dùngsizeofgiả địnhbuild == host == target - Trên các host Unix-like 64-bit hiện đại,
sizeof(long)là 8 byte, nhưng trên targetwasm32-unknown-emscriptennó phải là 4 byte - Khi truyền đối số kiểu
CHARACTERcủa Fortran vào hàm hoặc subroutine, có thể có đối số ẩn bổ sung để truyền độ dài chuỗi - Trong thư viện runtime Fortran, đối số độ dài này được khai báo là
size_t, và qua chuỗitypedefnó tương đươngunsigned long - Khác biệt kích thước của đối số ẩn này gây ra sự không khớp
i64vài32
Bản vá tạm thời: ép giá trị 4 byte
- Giải pháp lý tưởng là khi cross-compile,
flang-newxuấti32hoặci64phù hợp với kiến trúc đích và data model, độc lập với host - Hiện tại, tác giả dùng bản vá hard-code kích thước
longlà 4 byte chowasm32và Emscripten - Nội dung bản vá gồm hai hướng
- Trong
RTBuilder.h, ép kiểu mô hình củalongvàunsigned longthành8 * 4thay vì8 * sizeof(...) - Trong
CodeGen.cpp, tạo đối số gọimalloc()dưới dạng số nguyên 32-bit thay vì 64-bit, và cast kích thước cấp phát sangi32
- Trong
- Thay đổi này cũng sửa được cấp phát động dựa trên
ALLOCATE()được giới thiệu trong Fortran 90 - Sau khi build lại, liên kết
hello.f08vàhello.cvới thư viện runtime không còn cảnh báo, và Node in raHello, World!
Build BLAS sang WebAssembly
- BLAS là tập hợp các routine cấp thấp thực hiện các phép toán tuyến tính phổ biến như nhân ma trận/vector
- Các routine BLAS ban đầu được công bố năm 1979 và là chuẩn de facto trong lĩnh vực tính toán số trị
- Triển khai tham chiếu BLAS 3.12.0 được viết bằng Fortran 90 và có thể tải từ netlib
- Build bằng cách chỉ định các công cụ sau trong
make.incFC = ../build/bin/flang-newFFLAGS = -O2AR = emarRANLIB = emranlib
- Kết quả build tạo thư viện tĩnh
blas_LINUX.a - Routine Fortran ví dụ
bargọi routine BLAS level 2ZGEMV() ZGEMV()thực hiện phép toán ma trận-vector số phức, và ví dụ dùng đối sốCOMPLEX(KIND=8)cùng đối số thiết lậpCHARACTERlà'N'- Chương trình C tạo mảng số phức, truyền cho routine Fortran rồi in kết quả
- Kết quả chạy Node như sau
Y[0]: 23.000000 + 6.000000iY[1]: 18.000000 + 10.000000iY[2]: 6.000000 + 16.000000i
- Kết quả này xác nhận BLAS biên dịch từ mã nguồn Fortran 90 chạy được dưới WebAssembly
Ví dụ trình duyệt: bộ phân loại chữ số viết tay
- Demo phân loại chữ số 0–9 vẽ tay bằng mạng nơ-ron nhân tạo perceptron đa tầng (MLP)
- Người dùng có thể vẽ chữ số bằng chuột hoặc màn hình cảm ứng, và xác suất tương đối do mạng dự đoán được hiển thị trong biểu đồ bên phải
- Trọng số mô hình được huấn luyện trước bằng Python, nhưng việc phân loại được thực hiện trong trình duyệt lúc chạy bằng JavaScript và WebAssembly
- Quy trình phân loại MLP về bản chất là lặp lại các phép cộng và nhân ma trận-vector
- Phần tính toán nặng được xử lý bởi một subroutine Fortran dùng routine BLAS level 2
DGEMV()
Build LAPACK sang WebAssembly
- LAPACK là thư viện phần mềm giải số trị các bài toán đại số tuyến tính, được xây dựng trên BLAS
- Triển khai tham chiếu LAPACK 3.12.0 do netlib cung cấp và được phân phối theo giấy phép BSD sửa đổi
- Sau khi copy
make.inc.examplethànhmake.inc, thay đổi các thiết lập sau- Đặt
FClà đường dẫn đầy đủ tớiflang-newđã build FFLAGS = -O2AR = emarRANLIB = emranlibTIMER = INT_CPU_TIME
- Đặt
- Lệnh
make libtạo thư viện tĩnh WebAssemblyliblapack.a - Sau đó, có thể gọi các routine LAPACK theo cách tương tự ví dụ BLAS
Ví dụ trình duyệt: nội suy đa thức bằng đại số tuyến tính
- Demo tìm đa thức nội suy cho một tập điểm, cho thấy routine LAPACK chạy trong trình duyệt
- Khi người dùng bấm vào plot để thêm điểm mới, nó tìm đa thức nội suy đi qua tất cả các điểm
- Phương pháp dùng phương pháp Vandermonde
- Hệ phương trình đại số tuyến tính thu được bằng cách này được giải số trị bằng routine
DGELS()của LAPACK - Luôn có thể tìm được đa thức bậc
n-1chứa chính xácnđiểm dữ liệu - Khi
nlớn, đa thức có thể dao động mạnh giữa các điểm dữ liệu liên tiếp, tạo hiện tượng Runge; có thể tránh bằng nội suy spline
Những việc còn lại và công cụ được cung cấp
- Với LLVM Flang đã vá, có thể biên dịch mã Fortran hiện đại thành object WebAssembly
- Ưu điểm của cách này là có thể dùng các công cụ và thư viện Fortran hiện có, thay vì viết mới thuật toán số trị cho web bằng JavaScript
- Nếu
flang-newchính thức hỗ trợ WebAssembly, gánh nặng duy trì fork LLVM cho webR và các package R sẽ giảm - Hiện vẫn cần một hướng tốt hơn để sửa đúng các vấn đề của LLVM Flang và cross-compilation trên mọi target
- Với người dùng khó hoặc không muốn tự build LLVM Flang, GitHub Container Registry cung cấp container Docker chứa binary LLVM Flang đã vá
1 bình luận
Các ý kiến trên Hacker News
Bổ sung một chút bối cảnh, cuộc khám phá Fortran này là một phần trong công việc WebR rất tuyệt mà George đang thực hiện để chạy R trong trình duyệt
Trong mã nguồn R có khá nhiều mã Fortran, và theo tôi biết, WebR ban đầu dùng f2c để biên dịch Fortran sang C trước, rồi biên dịch C đó sang wasm
Nhờ bản vá LLVM Flang, giờ có thể build WebR bằng một trình biên dịch Fortran thật sự
George không nói trực tiếp trong bài blog, nhưng từng nói rằng anh ấy hy vọng Flang sẽ chấp nhận bản vá này hoặc triển khai theo cách tốt hơn
Khi đó sẽ không cần duy trì bản vá riêng nữa, và Flang không chỉnh sửa cũng có thể biên dịch sang wasm, mang lại lợi ích cho các dự án khác dùng Fortran
https://docs.r-wasm.org/webr/latest/
Tuy nhiên tôi đang tập trung vào những việc cần thiết để hoàn tất phát triển sản phẩm Fortran của Nvidia, nên không còn thời gian dành cho công việc kiểu này
20 năm trước tôi từng làm việc về biên dịch FORTRAN tại Xilinx, và điều duy nhất tôi nhớ là trong file header f2c.h có định nghĩa
barf/* f2c.h -- Standard Fortran to C header file //* barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed."(https://www.netlib.org/clapack/f2c.h)
Tôi rất thích cách giải thích bằng ví dụ không tầm thường đơn giản nhất
Bài viết dựa trên một vấn đề cụ thể là “gọi hàm BLAS từ JavaScript”, nên tôi có cảm giác học được rất nhiều; đúng là một bài viết hay
[1] https://en.m.wikipedia.org/wiki/The_Theoretical_Minimum
Tôi không biết nên thán phục hay kinh hãi, và có lẽ là cả hai
Khi build f18, tôi khuyên nên dùng mã nguồn mới nhất của llvm-project/main
Dự án đang thay đổi nhanh, nên sẽ rất lãng phí thời gian nếu debug những vấn đề đã được sửa hoặc bỏ lỡ những tính năng đã được triển khai
Có phải là họ đang làm bản port WebAssembly để đi đến điểm mà mã trung gian hoạt động được tới tận Fortran không?
Tôi không rành về phát triển WebAssembly
Từ góc nhìn người dùng, WebAssembly hiện tại có đem lại gì ngay lúc này không? Hay nó giống công việc đặt nền móng cho một tương lai nơi chương trình trở nên thật sự di động hơn?
Tôi có nghe nói thiết bị WebAssembly giúp áp đặt hạn chế truy cập như mạng hay file dễ hơn, nhưng không rõ đó là lý thuyết hay đã được triển khai rồi
Khác biệt cốt lõi là bản thân Wasm không có thư viện chuẩn và cũng không phơi bày hàm nhập/xuất nào
Vì vậy host, tức là VM, có thể được bạn tự tạo và phơi bày các hàm để binary Wasm sử dụng; binary Wasm chỉ có thể truy cập thế giới bên ngoài thông qua đúng các hàm đó
Một ưu điểm nữa là định dạng binary không độc quyền và có đặc tả, nên bất kỳ ai cũng có thể triển khai một VM Wasm
Tuy nhiên hiện giờ chưa thể nói là đã ở trạng thái tốt; vẫn còn quá sớm, có nhiều tính năng mới đang được chuẩn hóa bởi các nhóm kiểu như W3C, và quy trình thì rất chậm
Cũng có cách để triển khai hoặc cross-compile cho hầu hết các mục tiêu
Nó giống như việc người ta thường không biết và cũng không bận tâm lắm CPU máy tính là ARM hay x86
Vì vậy nếu không quan tâm đến các chi tiết như mã chạy native hay chạy trên VM như JVM, .NET, WASM, thì khó nói nó cung cấp thêm điều gì so với các giải pháp khác
Thường thì chỉ các trường hợp tệ mới nổi bật, rồi chúng biến thành meme kiểu “mọi chương trình Electron đều là quái vật phình to ngốn tài nguyên, còn mọi ứng dụng native thì tự động là kỳ quan kỹ nghệ phần mềm hiệu quả”
Tiếc là nếu tôi còn giữ mã Fortran 78 viết hồi 1981/82 thì đã có thể thử xem nó có chạy ở đây không
Đó là formatter mã nguồn cho ngôn ngữ lập trình Jovial, và thật ra không phải việc nên làm bằng Fortran, nhưng lúc đó chỉ có lựa chọn ấy
Có hệ sinh thái đại số tuyến tính ở mức tương đối sẵn sàng cho production nào dùng được từ JavaScript không?
Khi tìm kiếm thì thường chỉ thấy các bản port JavaScript của những thư viện quen thuộc đã khoảng 10 năm tuổi, chẳng hạn port qua emscripten; tôi tự hỏi liệu mình có bỏ sót gì không
Thật lạ là bài không đi sâu hơn vào LFortran
Cũng có một ví dụ WASM tuyệt vời và đáng kinh ngạc chạy trực tuyến
https://dev.lfortran.org/
Năm 2020, nó còn thiếu nhiều tính năng và chỉ hỗ trợ một tập con rất nhỏ của Fortran, nhưng hiện nay đã hỗ trợ phạm vi tính năng ngôn ngữ rộng hơn nhiều và có thể biên dịch khá nhiều mã Fortran
Cũng có thể biên dịch native sang WebAssembly
Tuy nhiên, các nhà phát triển cho biết LFortran vẫn còn những chỗ chưa hoàn thiện khi sử dụng, dự án hiện được xem là ở giai đoạn alpha và có thể gặp vấn đề khi biên dịch mã thực tế
Một số dự án như MINPACK có thể biên dịch thành công, nhưng vì chưa hỗ trợ toàn bộ đặc tả Fortran nên khá nhiều dự án lớn hơn vẫn chưa thể biên dịch
Các nhà phát triển LFortran đặt mục tiêu hỗ trợ đầy đủ Fortran 2018, và một tính năng đáng chú ý là REPL Fortran tương tác kiểu Jupyter
Sau vài năm phát triển nữa, nó được kỳ vọng sẽ trở thành một lựa chọn tuyệt vời để biên dịch mã Fortran cho WebAssembly
Ngoài ra, bài cũng bảo hãy xem demo LFortran tại https://dev.lfortran.org; rất ấn tượng, nhưng thao tác đầu tiên tôi thử là đổi
x * 2thànhx * 3thì bộ sinh mã hiện chưa hỗ trợCũng có Fortran trên .NET và Java
https://www.silverfrost.com/14/ftn95/ftn95_fortran_95_for_microsoft_dotnet_features.aspx
https://dl.acm.org/doi/10.1145/376656.376833
Khi làm https://medium.com/@tomasreimers/compiling-tensorflow-for-the-browser-f3387b8e1e1c, tôi đã nghĩ thật may là TensorFlow dùng Eigen chứ không phải BLAS/Lapack, thư viện toán học phổ biến viết bằng Fortran
Nếu không thì khối lượng công việc đã lớn hơn nhiều