- 오래된 과학·공학 계산 자산인 Fortran 수치 코드를 브라우저로 가져오려면 WebAssembly 컴파일 경로가 필요하며, webR은 패치된 LLVM
flang-new를 사용함 f2c, LFortran, Dragonegg, Classic Flang, LLVM Flang은 각각 현대 Fortran 지원·타깃 아키텍처·유지보수성에서 한계가 있어 아직 단순한 표준 해법이 없음- LLVM Flang은
wasm32-unknown-emscripten타깃을 바로 지원하지 않아TargetWasm32추가가 필요하고, 런타임 연결에서는 호스트와 타깃의long크기 차이가 문제를 일으킴 - 패치된
flang-new, Emscripten, Fortran 런타임 정적 라이브러리를 조합하면 Fortran 서브루틴을 C나 JavaScript에서 호출하고PRINT,ALLOCATE,CHARACTER인자도 처리할 수 있음 - BLAS 3.12.0과 LAPACK 3.12.0 참조 구현을 WebAssembly 정적 라이브러리로 빌드해 손글씨 숫자 분류와 다항식 보간 데모를 브라우저에서 실행함
기존 Fortran 수치 코드를 브라우저로 가져오기
- Fortran은 1957년에 등장한 오래된 언어지만, 과학·공학 계산에서 오랫동안 쓰였고 현대 Fortran은 Fortran 77의 고정 형식 제약을 대부분 벗어남
- 목표는 현대 Fortran 루틴을 WebAssembly로 컴파일해 브라우저에서 실행하고, 수치 인자를 받아 BLAS·LAPACK 루틴을 계산한 뒤 결과를 반환하거나 콘솔에 출력하는 것임
- 이 접근은 SciPy나 R처럼 BLAS·LAPACK에 의존하는 상위 프로그래밍 환경을 웹으로 가져올 수 있게 함
- JavaScript나 Rust로 수치 루틴을 다시 작성하지 않고, 이미 검증된 Fortran 기반 도구와 라이브러리를 활용할 수 있음
- webR 프로젝트는 패치된 LLVM
flang-new컴파일러로 Fortran 코드를 WebAssembly로 컴파일함 - 현재 방식은 해킹에 의존하며, 더 경험 많은 컴파일러 개발자의 도움 없이는 변경을 LLVM에 기여하기 어려움
Fortran→WebAssembly 도구 현황
- 2024년 기준 여러 도구와 툴체인이 있지만, 완전한 기능을 갖춘 단순한 해결책은 아직 없음
-
f2c -
LFortran
- LFortran은 최근 몇 년간 기능이 크게 늘었고 WebAssembly로 바로 컴파일할 수 있음
- 아직 알파 단계라 실제 코드 컴파일 이슈가 예상된다고 개발자들이 밝힘
- MINPACK 같은 일부 프로젝트는 컴파일할 수 있지만, 전체 Fortran 명세를 지원하지 않아 더 큰 프로젝트는 실패할 수 있음
- 개발 목표는 Fortran 2018 전체 지원이며, Jupyter와 비슷한 대화형 Fortran REPL이 두드러진 기능임
-
Dragonegg
- Dragonegg는 GCC 프런트엔드를 사용해 LLVM IR을 내보내는 GCC 플러그인임
- LLVM 백엔드로 WebAssembly 출력을 만들 수 있고, webR이 처음 Fortran 소스를 WebAssembly로 컴파일할 때 사용한 방식임
- 최신 지원 버전이
gcc-4.8과llvm-3.3이라 매우 오래된 GCC·LLVM이 필요함 - 대부분 사용자는 VM이나 Docker 컨테이너가 필요하고, Dragonegg가 내보내는 LLVM IR도 WebAssembly 출력을 위해 추가 후처리를 거쳐야 함
- 2020년에는 Fortran 코드를 WebAssembly로 컴파일하는 실질적인 방법이 사실상 이것뿐이었음
-
Classic Flang
- Classic Flang은 오픈소스화된 PGI/NVIDIA
pgfortran기반 LLVM 대상 Fortran 컴파일러임 - 32비트 출력을 지원하지 않아
wasm32타깃에는 사용할 수 없음 - Firefox, Chrome, Node는 작성 시점에
wasm64를 지원하지만 기능 플래그 뒤에 잠겨 있음 - 프로젝트 문서도 새 프로젝트에서 Classic Flang을 선택하는 것이 좋은 생각은 아닐 수 있다고 안내함
- Classic Flang은 오픈소스화된 PGI/NVIDIA
-
LLVM Flang
- LLVM Flang은 LLVM용 Fortran 프런트엔드를 처음부터 다시 구현한 프로젝트이며, LLVM 11부터 LLVM 프로젝트에 포함됨
- 아직 프로덕션 준비 완료로 간주되지는 않지만,
flang-new의 사전 프로덕션 버전은 실제 Fortran 코드 컴파일에 꽤 쓸 수 있는 수준이 됨 - 기본 상태에서는 WebAssembly 출력을 만들 수 없음
- LLVM의 모듈식 설계를 이용하면 Flang 프런트엔드와 LLVM WebAssembly 백엔드를 함께 사용할 수 있음
- 2020년에도 가능했지만 더 큰 LLVM 패치, 사용자 정의 수학 루틴 주입, 다단계 컴파일 과정이 필요했음
- 현재는
flang-new프런트엔드 개발 덕분에 LLVM 소스의 작은 변경만으로 Fortran→WebAssembly 컴파일러를 만들 수 있음
LLVM Flang을 WebAssembly용으로 빌드하기
- 패키지 매니저로 설치한 LLVM에는
flang-new바이너리가 포함되지 않을 수 있음- 예시로 macOS Homebrew의 LLVM v17.0.6에서는
flang-new명령을 찾지 못함
- 예시로 macOS Homebrew의 LLVM v17.0.6에서는
- LLVM Flang 소스를 수정해야 하므로 LLVM v18.1.1 소스를 직접 빌드함
- CMake 설정은 기본 타깃 트리플을
wasm32-unknown-emscripten으로 두고,WebAssembly타깃과clang;flang;mlir프로젝트를 활성화함 - 빌드가 끝나면
build/bin/flang-new --version에서 다음 정보를 확인할 수 있음- 버전:
flang-new version 18.1.1 - 타깃:
wasm32-unknown-emscripten - 스레드 모델:
posix
- 버전:
첫 번째 문제: wasm32 타깃 미구현
- 단순 Fortran 서브루틴
foo.f08를flang-new로 컴파일하면 다음 오류가 발생함not yet implemented: target not implemented
- 원인은
flang-new에wasm32-unknown-emscripten타깃 트리플이 아직 구현되어 있지 않기 때문임 - 해결책은
flang/lib/Optimizer/CodeGen/Target.cpp에TargetWasm32타깃 특성을 추가하는 패치임- 기본 폭을 32로 설정함
- 복소수 인자와 반환 타입을 WebAssembly 쪽 LLVM 타입으로 변환하는 마샬링을 정의함
llvm::Triple::ArchType::wasm32분기에TargetWasm32를 연결함
- 패치 후 다시 빌드하면 Fortran 소스가 WebAssembly 객체로 컴파일됨
file foo.o결과:WebAssembly (wasm) binary module version 0x1 (MVP)llvm-nm foo.o에서foo_심볼 확인 가능
C와 JavaScript에서 Fortran 서브루틴 호출하기
- Fortran 루틴은 일반적으로 인자를 참조로 전달하며,
INTENT()로 인자의 사용 방식을 선언할 수 있음 - 예시 Fortran 서브루틴
foo는x,y,z정수 인자를 받아z = x + y를 수행함 - 네이티브 빌드에서
gfortran -c foo.f08 -o foo.o를 실행하면 심볼 이름이foo_처럼 뒤에 밑줄이 붙을 수 있음 - C에서 호출할 때는 외부 심볼을
extern void foo_(int*, int*, int*);처럼 선언하고, 인자를 주소로 전달함 - WebAssembly에서는
flang-new로 만든foo.o를 Emscripten으로 C 코드와 연결할 수 있음emcc main.c foo.o -o main.jsnode main.js출력:1 + 1 = 2
-
JavaScript 직접 호출
- C 코드 없이 JavaScript에서 Fortran 서브루틴을 직접 호출할 수도 있음
- Emscripten 링크 단계에서
_foo_,_malloc,_free를 내보냄 emcc foo.o -sEXPORTED_FUNCTIONS=_foo_,_malloc,_free -o foo.js- JavaScript는
Module._malloc()으로 정수 저장 공간을 할당하고,Module.HEAPU32에 값을 쓴 뒤Module._foo_(x, y, z)를 호출함 - 실행 결과는 다음과 같음
x = 123y = 456x + y = 579- 브라우저에서는
foo.js와standalone.js를 HTML에서 로드해 같은 결과를 JavaScript 콘솔에서 볼 수 있음
두 번째 문제: Fortran 런타임 라이브러리
PRINT *, "Hello, World!"를 포함한 Fortran 서브루틴을 빌드하면 링크 단계에서 런타임 심볼이 없다는 오류가 발생함_FortranAioBeginExternalListOutput_FortranAioOutputAscii_FortranAioEndIoStatement
- 원인은 LLVM Fortran 런타임 라이브러리를 아직 WebAssembly용으로 컴파일하지 않았기 때문임
- 런타임 라이브러리는 LLVM 소스 트리의
llvm-project/flang/runtime에 C++로 작성되어 있음 - Emscripten의
em++와emar로 런타임 소스를 빌드하면 정적 라이브러리build/flang/runtime/libFortranRuntime.a를 만들 수 있음 - 이 라이브러리를 링크하면
Hello, World!빌드는 진행되지만, 처음에는 함수 시그니처 불일치 경고가 발생함
세 번째 문제: 호스트와 타깃의 long 크기 차이
hello.o와 Fortran 런타임 라이브러리를 연결하면_FortranAioOutputAscii시그니처 불일치 경고가 나옴- Fortran 객체 쪽은
(i32, i32, i64) -> i32를 기대함 - Emscripten으로 빌드한 런타임 쪽은
(i32, i32, i32) -> i32로 정의됨
- Fortran 객체 쪽은
- WebAssembly는 여러 컴파일 단위에 걸쳐 정의된 심볼의 인자와 반환 타입이 일관되어야 함
- 이 문제는 경고로 끝나지 않고, Node 실행 시
RuntimeError: unreachable로 실패함 - LLVM Flang의
RTBuilder.h에는sizeof사용이build == host == target을 가정한다는 TODO 주석이 있음 - 현대 64비트 Unix 계열 호스트에서는
sizeof(long)이 8바이트지만,wasm32-unknown-emscripten타깃에서는 4바이트여야 함 - Fortran의
CHARACTER타입 인자를 함수나 서브루틴에 전달할 때 문자열 길이를 넘기는 숨은 인자가 추가될 수 있음 - Fortran 런타임 라이브러리에서 이 길이 인자는
size_t로 선언되고,typedef체인을 거쳐unsigned long과 같아짐 - 이 숨은 인자의 크기 차이가
i64와i32불일치를 일으킴
임시 패치: 4바이트 값 강제
- 이상적인 해결책은
flang-new가 크로스 컴파일 시 호스트와 무관하게 타깃 아키텍처와 데이터 모델에 맞는i32또는i64를 내보내는 것임 - 현재는
wasm32와 Emscripten에 맞춰long크기를 4바이트로 하드코딩하는 패치를 사용함 - 패치 내용은 두 갈래임
RTBuilder.h에서long과unsigned long의 모델 타입을8 * sizeof(...)대신8 * 4로 강제함CodeGen.cpp에서malloc()호출 인자를 64비트 대신 32비트 정수로 생성하고, 할당 크기를i32로 캐스팅함
- 이 변경으로 Fortran 90에 도입된
ALLOCATE()기반 동적 할당도 고쳐짐 - 재빌드 후
hello.f08와hello.c를 런타임 라이브러리와 연결하면 경고 없이 빌드되고, Node에서 다음 출력이 나옴Hello, World!
BLAS를 WebAssembly로 빌드하기
- BLAS는 행렬·벡터 곱셈 등 선형대수의 공통 연산을 수행하는 저수준 루틴 집합임
- 원래 BLAS 루틴은 1979년에 공개되었고, 수치 계산 분야의 사실상 표준임
- 참조 구현 BLAS 3.12.0은 Fortran 90으로 작성되어 있으며, netlib에서 받을 수 있음
make.inc에서 다음 도구를 지정해 빌드함FC = ../build/bin/flang-newFFLAGS = -O2AR = emarRANLIB = emranlib
- 빌드 결과
blas_LINUX.a정적 라이브러리가 만들어짐 - 예시 Fortran 루틴
bar는 BLAS level 2 루틴ZGEMV()를 호출함 ZGEMV()는 복소수 행렬-벡터 연산을 수행하며, 예제는COMPLEX(KIND=8)인자와CHARACTER설정 인자'N'을 사용함- C 프로그램에서 복소수 배열을 만들고 Fortran 루틴에 전달한 뒤 결과를 출력함
- Node 실행 결과는 다음과 같음
Y[0]: 23.000000 + 6.000000iY[1]: 18.000000 + 10.000000iY[2]: 6.000000 + 16.000000i
- 이 결과로 Fortran 90 소스에서 컴파일한 BLAS가 WebAssembly 아래에서 실행됨을 확인함
브라우저 예제: 손글씨 숫자 분류기
- 데모는 다층 퍼셉트론(MLP) 인공신경망으로 손으로 그린 0–9 숫자를 분류함
- 사용자는 마우스나 터치스크린으로 숫자를 그릴 수 있고, 네트워크가 예측한 상대 확률이 오른쪽 플롯에 표시됨
- 모델 가중치는 Python으로 사전 학습되었지만, 분류는 실행 시점에 JavaScript와 WebAssembly로 브라우저에서 수행됨
- MLP 분류 과정은 본질적으로 행렬-벡터 덧셈과 곱셈의 반복임
- 무거운 계산은 Fortran 서브루틴 하나가 BLAS level 2 루틴
DGEMV()를 사용해 처리함
LAPACK을 WebAssembly로 빌드하기
- LAPACK은 선형대수 문제를 수치적으로 푸는 소프트웨어 라이브러리이며, BLAS 위에 구축됨
- 참조 구현 LAPACK 3.12.0은 netlib에서 제공되며 수정 BSD 라이선스로 배포됨
make.inc.example을make.inc로 복사한 뒤 다음 설정을 변경함FC를 빌드한flang-new의 전체 경로로 지정함FFLAGS = -O2AR = emarRANLIB = emranlibTIMER = INT_CPU_TIME
make lib명령으로 WebAssembly 정적 라이브러리liblapack.a를 생성함- 이후 LAPACK 루틴은 BLAS 예제와 비슷한 방식으로 호출할 수 있음
브라우저 예제: 선형대수를 이용한 다항식 보간
- 데모는 점들의 집합에 대해 보간 다항식을 찾으며, LAPACK 루틴이 브라우저에서 실행됨을 보여줌
- 사용자가 플롯을 클릭해 새 점을 추가하면, 모든 점을 지나는 보간 다항식을 찾음
- 방법은 Vandermonde’s method를 사용함
- 이 방식으로 얻은 선형대수 방정식은 LAPACK의
DGELS()루틴으로 수치적으로 풂 n개의 데이터 포인트를 정확히 포함하는n-1차 다항식은 항상 찾을 수 있음n이 커지면 다항식이 연속한 데이터 포인트 사이에서 크게 요동치는 Runge 현상이 생길 수 있고, 스플라인 보간으로 피할 수 있음
남은 과제와 제공 도구
- 패치된 LLVM Flang을 사용하면 현대 Fortran 코드를 WebAssembly 객체로 컴파일할 수 있음
- 이 방식의 장점은 웹용 수치 알고리듬을 JavaScript로 새로 작성하지 않고, 이미 존재하는 Fortran 도구와 라이브러리를 사용할 수 있다는 점임
flang-new가 WebAssembly를 공식 지원하면 webR와 R 패키지를 위해 LLVM 포크를 유지하는 부담이 줄어듦- 현재는 LLVM Flang과 크로스 컴파일 문제를 모든 타깃에서 제대로 고칠 더 나은 경로가 필요함
- LLVM Flang을 직접 빌드하기 어렵거나 원하지 않는 사용자를 위해 GitHub Container Registry에 패치된 LLVM Flang 바이너리 Docker 컨테이너가 제공됨