├── .gitignore ├── LICENSE ├── Lean2Wasm.lean ├── Main.lean ├── README.md ├── lake-manifest.json ├── lakefile.lean └── lean-toolchain /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build 3 | /lean_packages/* 4 | !/lean_packages/manifest.json 5 | /.lake 6 | /toolchains 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Theodora Brick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lean2Wasm.lean: -------------------------------------------------------------------------------- 1 | import ImportGraph.Imports 2 | import Std.Lean.Util.Path 3 | import Lake 4 | 5 | open Lean System 6 | 7 | -- This is what we want to compile and should contain `main` 8 | def root : Name := `Main 9 | -- Is this going to be ran on a webpage (i.e. should we MODULARIZE). 10 | def web : Bool := false 11 | 12 | unsafe def main : IO UInt32 := do 13 | let outdir : FilePath := ".lake" / "build" / "wasm" -- todo generalise 14 | if ¬ (←FilePath.pathExists outdir) then 15 | IO.FS.createDirAll outdir 16 | 17 | let wasm_tc := s!"lean-{Lean.versionString}-linux_wasm32" 18 | let toolchain : FilePath := "toolchains" / wasm_tc -- should this be moved into `.lake`? 19 | if ¬ (←FilePath.pathExists toolchain) then 20 | IO.println "Couldn't find toolchain (should be in './toolchains') will try downloading." 21 | 22 | let lean4_link := "https://github.com/leanprover/lean4/releases/download" 23 | let link := s!"{lean4_link}/v{Lean.versionString}/{wasm_tc}.tar.zst" 24 | let local_tar : FilePath := s!"{toolchain}.tar.zst" 25 | 26 | let _ ← Lake.LogIO.captureLog do 27 | let () ← Lake.download wasm_tc link local_tar 28 | let () ← Lake.untar wasm_tc local_tar "toolchains" 29 | 30 | IO.println "Finding relevant dependencies..." 31 | /- Find relevant C-files to compile, we don't want to compile everything 32 | if possible because there could be unexpected name collisions and having 33 | less files to process significantly speeds up compilation. 34 | -/ 35 | -- based on mathlib's import graph 36 | searchPathRef.set compile_time_search_path% 37 | let c_array ← 38 | withImportModules #[{module := root}] {} (trustLevel := 1024) 39 | fun env => do 40 | let graph := env.importGraph.filter (fun n _ => 41 | -- already included in the toolchain 42 | ¬Name.isPrefixOf `Lean n ∧ ¬Name.isPrefixOf `Init n 43 | ) 44 | let sp ← searchPathRef.get 45 | let sp : Lean.SearchPath := sp.map (fun p => (p / ".." / "ir")) 46 | 47 | let cfiles ← graph.toList.mapM (fun (mod, _) => do 48 | -- copied/modified hacked from Lean.findOLean 49 | if let some fname ← sp.findWithExt "c" mod then 50 | return fname 51 | else 52 | -- todo: make error msg better : ) 53 | throw <| IO.userError s!"Could not find C for {mod}" 54 | ) 55 | return cfiles.map toString |>.toArray 56 | 57 | IO.println s!"Found {c_array.size} files." 58 | IO.println "Compiling (this can take a while)..." 59 | 60 | -- should this just be moved into a bash script that takes the files? 61 | let out ← IO.Process.output { 62 | stdin := .piped 63 | stdout := .piped 64 | stderr := .piped 65 | cmd := "emcc" 66 | args := 67 | #[ "-o" 68 | , outdir / "main.js" |>.toString 69 | , "-I" 70 | , toolchain / "include" |>.toString 71 | , "-L" 72 | , toolchain / "lib" / "lean" |>.toString 73 | ] ++ c_array ++ 74 | #[ "-lInit" 75 | , "-lLean" 76 | , "-lleancpp" 77 | , "-lleanrt" 78 | , "-sFORCE_FILESYSTEM" 79 | ] ++ (if web 80 | then #["-sMODULARIZE", "-sEXPORT_NAME=" ++ root.toString] 81 | else #["-sNODERAWFS"] -- allows node to directly interact w/ local FS 82 | ) ++ 83 | #[ "-lnodefs.js" 84 | , "-sEXIT_RUNTIME=0" 85 | , "-sMAIN_MODULE=2" -- use 2 to reduce exports to a usable amount 86 | , "-sLINKABLE=0" 87 | , "-sEXPORT_ALL=0" 88 | , "-sALLOW_MEMORY_GROWTH=1" 89 | , "-fwasm-exceptions" 90 | , "-pthread" 91 | , "-flto" 92 | -- , "-Oz" -- takes longer to compile but optimises for size 93 | ] 94 | } 95 | 96 | IO.println out.stdout 97 | IO.println out.stderr 98 | 99 | return out.exitCode 100 | -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | import Std 2 | 3 | def using_std_test : Std.HashMap Nat String := 4 | Std.HashMap.empty |>.insert 5 "this was compiled into WASM!" 5 | 6 | def msg := using_std_test.find! 5 7 | 8 | def main (args : List String) : IO UInt32 := do 9 | IO.println s!"Hello, {msg}!" 10 | IO.println s!"{args}" 11 | 12 | /- Compiling with and without `-sNODERAWFS` changes behaviour here. With it, 13 | the current directory should be the directory that the calling shell is 14 | in but `home/web_user` will not exist (unless you have that directory 15 | on your system). Without that flag, the current directory should be `/` 16 | and `/home/web_user` should exist. 17 | 18 | Relevant emscripten docs: 19 | - https://emscripten.org/docs/porting/files/file_systems_overview.html 20 | - https://emscripten.org/docs/api_reference/Filesystem-API.html 21 | -/ 22 | IO.println s!"current directory: {← IO.currentDir}" 23 | 24 | let webuser : System.FilePath := "" / "home" / "web_user" 25 | if ←(webuser.pathExists) then 26 | IO.println s!"{webuser} does exist" 27 | else 28 | IO.println s!"{webuser} doesn't exist" 29 | 30 | return 0 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lean2Wasm 2 | 3 | Tool to compile Lean4 code to WASM. Right now this is really just for me to test 4 | things out. So actually using it is a little scuffed right now :) 5 | 6 | This is largely a translation of [these instructions](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/wasm.20build/near/402692669) 7 | into something that can be executed. 8 | 9 | This requires [emcc](https://emscripten.org/docs/getting_started/downloads.html) 10 | to already be installed. 11 | 12 | ## Usage 13 | 14 | Run `lake build test` to build the test program. Then run `lake build` to build 15 | the utility. Running `lake exe lean2wasm` will compile the `Main` program. 16 | 17 | Once compiled, you can run `node .lake/build/wasm/main.js` to run the program. 18 | Alternatively you can use `lake run js`. 19 | 20 | If you want to change what is being compiled, in `Lean2Wasm.lean` just change 21 | the `root` variable. 22 | 23 | ## Example 24 | 25 | [Here](https://github.com/T-Brick/c0_web_driver) is an example of embedding lean 26 | into a webpage. Importantly, we have to use the `MODULARIZE` flag so that we 27 | can invoke the `main` function multiple times since there are issues with doing 28 | so without resetting the emscripten runtime (specifically, emscripten generates 29 | a factory function which can then be invoked to initialise the runtime again 30 | and call `main`). 31 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover/lean4-cli", 5 | "type": "git", 6 | "subDir": null, 7 | "rev": "a751d21d4b68c999accb6fc5d960538af26ad5ec", 8 | "name": "Cli", 9 | "manifestFile": "lake-manifest.json", 10 | "inputRev": "main", 11 | "inherited": true, 12 | "configFile": "lakefile.lean"}, 13 | {"url": "https://github.com/leanprover/std4", 14 | "type": "git", 15 | "subDir": null, 16 | "rev": "2a1b7546b2df0f8e65a70d3b228c30d91752acc4", 17 | "name": "std", 18 | "manifestFile": "lake-manifest.json", 19 | "inputRev": "main", 20 | "inherited": true, 21 | "configFile": "lakefile.lean"}, 22 | {"url": "https://github.com/leanprover-community/import-graph", 23 | "type": "git", 24 | "subDir": null, 25 | "rev": "bbcffbcc19d69e13d5eea716283c76169db524ba", 26 | "name": "importGraph", 27 | "manifestFile": "lake-manifest.json", 28 | "inputRev": "v4.6.1", 29 | "inherited": false, 30 | "configFile": "lakefile.lean"}], 31 | "name": "lean2wasm", 32 | "lakeDir": ".lake"} 33 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package lean2wasm { 5 | -- add package configuration options here 6 | } 7 | 8 | lean_lib Lean2Wasm { 9 | -- add library configuration options here 10 | } 11 | 12 | @[default_target] 13 | lean_exe lean2wasm { 14 | root := `Lean2Wasm 15 | } 16 | 17 | lean_exe test where 18 | root := `Main 19 | 20 | require importGraph from git "https://github.com/leanprover-community/import-graph" @ "v4.6.1" 21 | 22 | script js (args : List String) do 23 | let out ← IO.Process.output { 24 | stdin := .piped 25 | stdout := .piped 26 | stderr := .piped 27 | cmd := "node" 28 | args := (".lake/build/wasm/main.js" :: args).toArray 29 | } 30 | IO.print out.stdout 31 | IO.print out.stderr 32 | return out.exitCode 33 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.6.1 2 | --------------------------------------------------------------------------------