├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cmd └── gravity │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── main.rs ├── examples ├── .gitignore ├── README.md ├── basic │ ├── Cargo.toml │ ├── basic_test.go │ ├── src │ │ └── lib.rs │ └── wit │ │ └── basic.wit ├── generate.go └── iface-method-returns-string │ ├── Cargo.toml │ ├── example_test.go │ ├── src │ └── lib.rs │ └── wit │ └── example.wit ├── go.mod ├── go.sum └── rust-toolchain.toml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo. Unless a 2 | # later match takes precedence, @arcjet/js-sdk-team will be required to approve 3 | # a pull request before it can be merged. 4 | * @arcjet/rust-team 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: daily 7 | groups: 8 | # WIT packages need to be updated together 9 | wit: 10 | patterns: 11 | - wit-bindgen-core 12 | - wit-bindgen 13 | - wit-component 14 | 15 | - package-ecosystem: gomod 16 | directory: / 17 | schedule: 18 | interval: daily 19 | 20 | - package-ecosystem: github-actions 21 | directory: / 22 | schedule: 23 | interval: daily 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | gravity: 16 | name: Gravity tests 17 | runs-on: ubuntu-latest 18 | steps: 19 | # Environment security 20 | - name: Step Security 21 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 22 | with: 23 | disable-sudo-and-containers: true 24 | egress-policy: block 25 | allowed-endpoints: > 26 | api.github.com:443 27 | objects.githubusercontent.com:443 28 | github.com:443 29 | index.crates.io:443 30 | static.crates.io:443 31 | static.rust-lang.org:443 32 | proxy.golang.org:443 33 | 34 | # Checkout 35 | # Most toolchains require checkout first 36 | - name: Checkout 37 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 38 | 39 | # Language toolchains 40 | - name: Install Go 41 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 42 | with: 43 | go-version: stable 44 | 45 | - name: Install Rust 46 | uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 47 | with: 48 | # We start by installing the stable toolchain but the 49 | # `rust-toolchain.toml` file takes precendence 50 | toolchain: stable 51 | 52 | - name: Inspect toolchain versions 53 | run: | 54 | cargo -V 55 | go version 56 | 57 | # Workflow 58 | 59 | - name: cargo build 60 | run: cargo build --locked --verbose 61 | env: 62 | CARGO_TERM_COLOR: always 63 | 64 | - name: cargo test 65 | run: cargo test --locked --verbose 66 | env: 67 | CARGO_TERM_COLOR: always 68 | 69 | - name: go test 70 | run: | 71 | go generate ./... 72 | go test ./... 73 | 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # misc 4 | .DS_Store 5 | *.pem 6 | 7 | # Generated by Cargo 8 | # will have compiled files and executables 9 | debug/ 10 | target/ 11 | 12 | # Redis 13 | *.rdb 14 | 15 | # VS Code 16 | .vscode* 17 | 18 | # These are backup files generated by rustfmt 19 | **/*.rs.bk 20 | 21 | # MSVC Windows builds of rustc generate these, which store debugging information 22 | *.pdb 23 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.95" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 60 | 61 | [[package]] 62 | name = "arcjet-gravity" 63 | version = "0.0.2" 64 | dependencies = [ 65 | "clap", 66 | "genco", 67 | "rustversion", 68 | "wit-bindgen", 69 | "wit-bindgen-core", 70 | "wit-component", 71 | ] 72 | 73 | [[package]] 74 | name = "autocfg" 75 | version = "1.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "2.8.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 84 | 85 | [[package]] 86 | name = "clap" 87 | version = "4.5.39" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" 90 | dependencies = [ 91 | "clap_builder", 92 | ] 93 | 94 | [[package]] 95 | name = "clap_builder" 96 | version = "4.5.39" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" 99 | dependencies = [ 100 | "anstream", 101 | "anstyle", 102 | "clap_lex", 103 | "strsim", 104 | ] 105 | 106 | [[package]] 107 | name = "clap_lex" 108 | version = "0.7.4" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 111 | 112 | [[package]] 113 | name = "colorchoice" 114 | version = "1.0.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 117 | 118 | [[package]] 119 | name = "equivalent" 120 | version = "1.0.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 123 | 124 | [[package]] 125 | name = "example-basic" 126 | version = "0.0.2" 127 | dependencies = [ 128 | "wit-bindgen", 129 | "wit-component", 130 | ] 131 | 132 | [[package]] 133 | name = "example-iface-method-returns-string" 134 | version = "0.0.2" 135 | dependencies = [ 136 | "wit-bindgen", 137 | "wit-component", 138 | ] 139 | 140 | [[package]] 141 | name = "foldhash" 142 | version = "0.1.4" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 145 | 146 | [[package]] 147 | name = "futures" 148 | version = "0.3.31" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 151 | dependencies = [ 152 | "futures-channel", 153 | "futures-core", 154 | "futures-executor", 155 | "futures-io", 156 | "futures-sink", 157 | "futures-task", 158 | "futures-util", 159 | ] 160 | 161 | [[package]] 162 | name = "futures-channel" 163 | version = "0.3.31" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 166 | dependencies = [ 167 | "futures-core", 168 | "futures-sink", 169 | ] 170 | 171 | [[package]] 172 | name = "futures-core" 173 | version = "0.3.31" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 176 | 177 | [[package]] 178 | name = "futures-executor" 179 | version = "0.3.31" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 182 | dependencies = [ 183 | "futures-core", 184 | "futures-task", 185 | "futures-util", 186 | ] 187 | 188 | [[package]] 189 | name = "futures-io" 190 | version = "0.3.31" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 193 | 194 | [[package]] 195 | name = "futures-macro" 196 | version = "0.3.31" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 199 | dependencies = [ 200 | "proc-macro2", 201 | "quote", 202 | "syn", 203 | ] 204 | 205 | [[package]] 206 | name = "futures-sink" 207 | version = "0.3.31" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 210 | 211 | [[package]] 212 | name = "futures-task" 213 | version = "0.3.31" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 216 | 217 | [[package]] 218 | name = "futures-util" 219 | version = "0.3.31" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 222 | dependencies = [ 223 | "futures-channel", 224 | "futures-core", 225 | "futures-io", 226 | "futures-macro", 227 | "futures-sink", 228 | "futures-task", 229 | "memchr", 230 | "pin-project-lite", 231 | "pin-utils", 232 | "slab", 233 | ] 234 | 235 | [[package]] 236 | name = "genco" 237 | version = "0.17.10" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a35958104272e516c2a5f66a9d82fba4784d2b585fc1e2358b8f96e15d342995" 240 | dependencies = [ 241 | "genco-macros", 242 | "relative-path", 243 | "smallvec", 244 | ] 245 | 246 | [[package]] 247 | name = "genco-macros" 248 | version = "0.17.10" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" 251 | dependencies = [ 252 | "proc-macro2", 253 | "quote", 254 | "syn", 255 | ] 256 | 257 | [[package]] 258 | name = "hashbrown" 259 | version = "0.15.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 262 | dependencies = [ 263 | "foldhash", 264 | ] 265 | 266 | [[package]] 267 | name = "heck" 268 | version = "0.5.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 271 | 272 | [[package]] 273 | name = "id-arena" 274 | version = "2.2.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 277 | 278 | [[package]] 279 | name = "indexmap" 280 | version = "2.7.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 283 | dependencies = [ 284 | "equivalent", 285 | "hashbrown", 286 | "serde", 287 | ] 288 | 289 | [[package]] 290 | name = "is_terminal_polyfill" 291 | version = "1.70.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 294 | 295 | [[package]] 296 | name = "itoa" 297 | version = "1.0.14" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 300 | 301 | [[package]] 302 | name = "leb128fmt" 303 | version = "0.1.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 306 | 307 | [[package]] 308 | name = "log" 309 | version = "0.4.25" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 312 | 313 | [[package]] 314 | name = "memchr" 315 | version = "2.7.4" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 318 | 319 | [[package]] 320 | name = "once_cell" 321 | version = "1.20.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 324 | 325 | [[package]] 326 | name = "pin-project-lite" 327 | version = "0.2.16" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 330 | 331 | [[package]] 332 | name = "pin-utils" 333 | version = "0.1.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 336 | 337 | [[package]] 338 | name = "prettyplease" 339 | version = "0.2.29" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 342 | dependencies = [ 343 | "proc-macro2", 344 | "syn", 345 | ] 346 | 347 | [[package]] 348 | name = "proc-macro2" 349 | version = "1.0.93" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 352 | dependencies = [ 353 | "unicode-ident", 354 | ] 355 | 356 | [[package]] 357 | name = "quote" 358 | version = "1.0.38" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 361 | dependencies = [ 362 | "proc-macro2", 363 | ] 364 | 365 | [[package]] 366 | name = "relative-path" 367 | version = "1.9.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 370 | 371 | [[package]] 372 | name = "rustversion" 373 | version = "1.0.21" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 376 | 377 | [[package]] 378 | name = "ryu" 379 | version = "1.0.19" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 382 | 383 | [[package]] 384 | name = "semver" 385 | version = "1.0.25" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" 388 | 389 | [[package]] 390 | name = "serde" 391 | version = "1.0.217" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 394 | dependencies = [ 395 | "serde_derive", 396 | ] 397 | 398 | [[package]] 399 | name = "serde_derive" 400 | version = "1.0.217" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 403 | dependencies = [ 404 | "proc-macro2", 405 | "quote", 406 | "syn", 407 | ] 408 | 409 | [[package]] 410 | name = "serde_json" 411 | version = "1.0.138" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 414 | dependencies = [ 415 | "itoa", 416 | "memchr", 417 | "ryu", 418 | "serde", 419 | ] 420 | 421 | [[package]] 422 | name = "slab" 423 | version = "0.4.9" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 426 | dependencies = [ 427 | "autocfg", 428 | ] 429 | 430 | [[package]] 431 | name = "smallvec" 432 | version = "1.13.2" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 435 | 436 | [[package]] 437 | name = "strsim" 438 | version = "0.11.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 441 | 442 | [[package]] 443 | name = "syn" 444 | version = "2.0.98" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 447 | dependencies = [ 448 | "proc-macro2", 449 | "quote", 450 | "unicode-ident", 451 | ] 452 | 453 | [[package]] 454 | name = "unicode-ident" 455 | version = "1.0.16" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 458 | 459 | [[package]] 460 | name = "unicode-xid" 461 | version = "0.2.6" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 464 | 465 | [[package]] 466 | name = "utf8parse" 467 | version = "0.2.2" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 470 | 471 | [[package]] 472 | name = "wasm-encoder" 473 | version = "0.230.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "d4349d0943718e6e434b51b9639e876293093dca4b96384fb136ab5bd5ce6660" 476 | dependencies = [ 477 | "leb128fmt", 478 | "wasmparser", 479 | ] 480 | 481 | [[package]] 482 | name = "wasm-metadata" 483 | version = "0.230.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "1a52e010df5494f4289ccc68ce0c2a8c17555225a5e55cc41b98f5ea28d0844b" 486 | dependencies = [ 487 | "anyhow", 488 | "indexmap", 489 | "wasm-encoder", 490 | "wasmparser", 491 | ] 492 | 493 | [[package]] 494 | name = "wasmparser" 495 | version = "0.230.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" 498 | dependencies = [ 499 | "bitflags", 500 | "hashbrown", 501 | "indexmap", 502 | "semver", 503 | ] 504 | 505 | [[package]] 506 | name = "windows-sys" 507 | version = "0.59.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 510 | dependencies = [ 511 | "windows-targets", 512 | ] 513 | 514 | [[package]] 515 | name = "windows-targets" 516 | version = "0.52.6" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 519 | dependencies = [ 520 | "windows_aarch64_gnullvm", 521 | "windows_aarch64_msvc", 522 | "windows_i686_gnu", 523 | "windows_i686_gnullvm", 524 | "windows_i686_msvc", 525 | "windows_x86_64_gnu", 526 | "windows_x86_64_gnullvm", 527 | "windows_x86_64_msvc", 528 | ] 529 | 530 | [[package]] 531 | name = "windows_aarch64_gnullvm" 532 | version = "0.52.6" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 535 | 536 | [[package]] 537 | name = "windows_aarch64_msvc" 538 | version = "0.52.6" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 541 | 542 | [[package]] 543 | name = "windows_i686_gnu" 544 | version = "0.52.6" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 547 | 548 | [[package]] 549 | name = "windows_i686_gnullvm" 550 | version = "0.52.6" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 553 | 554 | [[package]] 555 | name = "windows_i686_msvc" 556 | version = "0.52.6" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 559 | 560 | [[package]] 561 | name = "windows_x86_64_gnu" 562 | version = "0.52.6" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 565 | 566 | [[package]] 567 | name = "windows_x86_64_gnullvm" 568 | version = "0.52.6" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 571 | 572 | [[package]] 573 | name = "windows_x86_64_msvc" 574 | version = "0.52.6" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 577 | 578 | [[package]] 579 | name = "wit-bindgen" 580 | version = "0.42.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "fa5b79cd8cb4b27a9be3619090c03cbb87fe7b1c6de254b4c9b4477188828af8" 583 | dependencies = [ 584 | "wit-bindgen-rt", 585 | "wit-bindgen-rust-macro", 586 | ] 587 | 588 | [[package]] 589 | name = "wit-bindgen-core" 590 | version = "0.42.1" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "e35e550f614e16db196e051d22b0d4c94dd6f52c90cb1016240f71b9db332631" 593 | dependencies = [ 594 | "anyhow", 595 | "heck", 596 | "wit-parser", 597 | ] 598 | 599 | [[package]] 600 | name = "wit-bindgen-rt" 601 | version = "0.42.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "051105bab12bc78e161f8dfb3596e772dd6a01ebf9c4840988e00347e744966a" 604 | dependencies = [ 605 | "bitflags", 606 | "futures", 607 | "once_cell", 608 | ] 609 | 610 | [[package]] 611 | name = "wit-bindgen-rust" 612 | version = "0.42.1" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "cb1e0a91fc85f4ef70e0b81cd86c2b49539d3cd14766fd82396184aadf8cb7d7" 615 | dependencies = [ 616 | "anyhow", 617 | "heck", 618 | "indexmap", 619 | "prettyplease", 620 | "syn", 621 | "wasm-metadata", 622 | "wit-bindgen-core", 623 | "wit-component", 624 | ] 625 | 626 | [[package]] 627 | name = "wit-bindgen-rust-macro" 628 | version = "0.42.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "ce69f52c5737705881d5da5a1dd06f47f8098d094a8d65a3e44292942edb571f" 631 | dependencies = [ 632 | "anyhow", 633 | "prettyplease", 634 | "proc-macro2", 635 | "quote", 636 | "syn", 637 | "wit-bindgen-core", 638 | "wit-bindgen-rust", 639 | ] 640 | 641 | [[package]] 642 | name = "wit-component" 643 | version = "0.230.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "b607b15ead6d0e87f5d1613b4f18c04d4e80ceeada5ffa608d8360e6909881df" 646 | dependencies = [ 647 | "anyhow", 648 | "bitflags", 649 | "indexmap", 650 | "log", 651 | "serde", 652 | "serde_derive", 653 | "serde_json", 654 | "wasm-encoder", 655 | "wasm-metadata", 656 | "wasmparser", 657 | "wit-parser", 658 | ] 659 | 660 | [[package]] 661 | name = "wit-parser" 662 | version = "0.230.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "679fde5556495f98079a8e6b9ef8c887f731addaffa3d48194075c1dd5cd611b" 665 | dependencies = [ 666 | "anyhow", 667 | "id-arena", 668 | "indexmap", 669 | "log", 670 | "semver", 671 | "serde", 672 | "serde_derive", 673 | "serde_json", 674 | "unicode-xid", 675 | "wasmparser", 676 | ] 677 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["cmd/*", "examples/*"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This is a very early release of Gravity. Many WIT features are not yet implemented 3 | > and the project is is likely to change significantly as we develop it further. 4 | 5 | # Gravity 6 | 7 | Gravity is a host generator for WebAssembly Components. It currently targets [Wazero][wazero], a zero dependency WebAssembly runtime for Go. 8 | 9 | ## What? 10 | 11 | This crate provides the `gravity` tool—a code generator that produces Wazero 12 | host code for WebAssembly Components. Currently, we only process Wasm core 13 | modules with a WIT metadata custom section. 14 | 15 | ## Why? 16 | 17 | Much of Arcjet's protection rules are written in Rust & compiled to WebAssembly. 18 | To allow us to use rich types at the Wasm boundary, we leverage the [WebAssembly 19 | Interface Type][wit] format (or WIT). Our Rust code consumes the 20 | [wit-bindgen][wit-bindgen] project which generates the lifting and lowering of 21 | these types inside the "guest" WebAssembly module. However, the only way to 22 | "host" one of these WebAssembly Components is via [Wasmtime][wasmtime] or 23 | [jco][jco]. 24 | 25 | We were able to leverage `jco transpile` to translate our WebAssembly Components 26 | to Core Wasm that runs in a JavaScript environment, but we don't have easy 27 | access to Wasmtime in our server environment. Most of our server logic is 28 | written in Go, which has fantastic Core Wasm support via [Wazero][wazero]. 29 | Wazero has [rejected the Component Model][wazero-component-model], but we can 30 | still translate Components to Core today. 31 | 32 | By adopting a similar strategy as `jco transpile`, we've built this tool to 33 | produced Wazero output that adhere's to the Component Model's [Canonical 34 | ABI][canonical-abi]. 35 | 36 | ## Installation 37 | 38 | To produce Go files with good indentation, this tool should be installed with a 39 | Rust nightly toolchain. You can install one with: 40 | 41 | ```bash 42 | rustup toolchain install nightly-2025-01-01 43 | ``` 44 | 45 | From inside this directory, you can install using the command: 46 | 47 | ```bash 48 | cargo +nightly-2025-01-01 install --path . 49 | ``` 50 | 51 | Or alternatively, you can install the latest published version from crates.io 52 | using this command: 53 | 54 | ```bash 55 | cargo +nightly-2025-01-01 install arcjet-gravity 56 | ``` 57 | 58 | ## Usage 59 | 60 | To generate the bindings, you run something like: 61 | 62 | ```bash 63 | gravity example/example.wasm --world example --output example/example.go 64 | ``` 65 | 66 | After you generate the code, you'll want to ensure you have all the necessary 67 | dependencies. You can run: 68 | 69 | ```bash 70 | go mod tidy 71 | ``` 72 | 73 | ## Example 74 | 75 | An runnable example in our [examples/](./examples/) directory. Please see the 76 | [README](./examples/README.md) for instructions on running it. 77 | 78 | ## Status 79 | 80 | 81 | Currently, that means we support: 82 | 83 | - `string` 84 | - `u32` 85 | - `result` 86 | - `result<_, string>` 87 | - `option` 88 | 89 | This list is likely to grow quickly, as one of our goals is to avoid working 90 | with JSON serialized as a string and instead leverage more concrete types that 91 | we can codegen. 92 | 93 | ## Output 94 | 95 | The generated output consists of a bindings file and a Wasm file which 96 | is placed next to it. The bindings file loads the Wasm file using `go:embed`. 97 | 98 | Alternatively, if you set the `inline-wasm` flag Gravity will output the Wasm 99 | file contents encoded as hex if you wish to avoid using `go:embed`. This will likely 100 | result in much larger file sizes. 101 | 102 | We produce a "factory" and "instance" per world. Given an `example` world: 103 | 104 | ```txt 105 | package arcjet:example; 106 | 107 | interface logger { 108 | debug: func(msg: string); 109 | log: func(msg: string); 110 | warn: func(msg: string); 111 | error: func(msg: string); 112 | } 113 | 114 | world example { 115 | import logger; 116 | 117 | export foobar: func() -> result; 118 | } 119 | ``` 120 | 121 | The generated code will define the `ExampleFactory` and `ExampleInstance`. Generally, 122 | the factory is constructed once upon startup because it prepares all of the 123 | imports and compiles the WebAssembly, which can take a long time. In the example 124 | above, the `ExampleFactory` can be constructed with `NewExampleFactory` which is 125 | provided with a `context.Context` and a type implementing the `IExampleLogger` 126 | interface. 127 | 128 | Any interfaces defined as imports to the world will have a corresponding 129 | interface definition in Go, as we saw the `IExampleLogger` above. This defines the 130 | high-level functions that must be available to call from Wasm. The `logger` 131 | interface was translated to: 132 | 133 | ```go 134 | type IExampleLogger interface { 135 | Debug(ctx context.Context, msg string) 136 | Log(ctx context.Context, msg string) 137 | Warn(ctx context.Context, msg string) 138 | Error(ctx context.Context, msg string) 139 | } 140 | ``` 141 | 142 | Factories can produce instances using the `Instantiate` function, which only 143 | takes a `context.Context`. This function prepares the WebAssembly to be executed 144 | but is generally very fast, since the factory pre-compiles the Wasm module. 145 | 146 | Exported functions are called on an instance, such as our `foobar` function. You 147 | would call this like 148 | `inst.Foobar(ctx)`. Since 149 | the return value is defined as a `result`, it is translated into 150 | the idiomatic Go return type `(string, error)`. 151 | 152 | When you are done with an instance, you are expected to call `Close` but you'll 153 | probably just want to `defer` it, like `defer inst.Close(ctx)`. 154 | 155 | ### Testing 156 | 157 | Consuming the generated bindings should be pretty straightforward. As such, 158 | writing a test for the above would look something like: 159 | 160 | ```go 161 | package example 162 | 163 | import ( 164 | "context" 165 | "testing" 166 | 167 | "github.com/stretchr/testify/require" 168 | ) 169 | 170 | func Test_Generated_Example(t *testing.T) { 171 | // Assuming you've generated mocks with Mockery 172 | logger := NewMockIBotsLogger(t) 173 | ctx := context.Background() 174 | factory, err := NewExampleFactory(ctx, logger) 175 | require.NoError(t, err) 176 | 177 | instance, err := factory.Instantiate(ctx) 178 | require.NoError(t, err) 179 | defer instance.Close(ctx) 180 | 181 | result, err := instance.Foobar(ctx) 182 | require.NoError(t, err) 183 | require.NotEqual(t, result, "") 184 | } 185 | ``` 186 | 187 | [wit]: https://github.com/WebAssembly/component-model/blob/a74225c12c152df59f745cfc0fbde79b5310ccd9/design/mvp/WIT.md 188 | [wit-bindgen]: https://github.com/bytecodealliance/wit-bindgen 189 | [wasmtime]: https://wasmtime.dev/ 190 | [jco]: https://github.com/bytecodealliance/jco 191 | [wazero]: https://github.com/tetratelabs/wazero 192 | [canonical-abi]: https://github.com/WebAssembly/component-model/blob/a74225c12c152df59f745cfc0fbde79b5310ccd9/design/mvp/CanonicalABI.md 193 | [wazero-component-model]: https://github.com/tetratelabs/wazero/issues/2200 194 | -------------------------------------------------------------------------------- /cmd/gravity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["The Arcjet team"] 3 | categories = ["wasm"] 4 | default-run = "gravity" 5 | name = "arcjet-gravity" 6 | version = "0.0.2" 7 | edition = "2024" 8 | license = "Apache-2.0" 9 | repository = "https://github.com/arcjet/gravity" 10 | description = """ 11 | Gravity is a host generator for WebAssembly Components. It currently targets Wazero, a zero dependency WebAssembly runtime for Go. 12 | """ 13 | build = "build.rs" 14 | 15 | [[bin]] 16 | name = "gravity" 17 | path = "src/main.rs" 18 | 19 | [dependencies] 20 | clap = "=4.5.39" 21 | genco = "=0.17.10" 22 | wit-bindgen-core = "=0.42.1" 23 | wit-component = "=0.230.0" 24 | 25 | [dev-dependencies] 26 | wit-bindgen = "=0.42.1" 27 | 28 | [build-dependencies] 29 | rustversion = "=1.0.21" 30 | -------------------------------------------------------------------------------- /cmd/gravity/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | assert!( 3 | rustversion::cfg!(nightly), 4 | "Gravity must be compiled with the nightly release of Rust" 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /cmd/gravity/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, fs, mem, path::Path, process::ExitCode, str::Chars}; 2 | 3 | use clap::{Arg, ArgAction, Command}; 4 | use genco::{ 5 | Tokens, 6 | lang::{Go, go}, 7 | quote, quote_in, 8 | tokens::{FormatInto, ItemStr, quoted, static_literal}, 9 | }; 10 | use wit_bindgen_core::{ 11 | abi::{AbiVariant, Bindgen, Instruction, LiftLower, WasmType}, 12 | wit_parser::{ 13 | Alignment, ArchitectureSize, Record, Resolve, Result_, SizeAlign, Type, TypeDef, 14 | TypeDefKind, WorldItem, 15 | }, 16 | }; 17 | 18 | struct Embed(T); 19 | impl FormatInto for Embed 20 | where 21 | T: Into, 22 | { 23 | fn format_into(self, tokens: &mut Tokens) { 24 | // TODO(#13): Submit patch to genco that will allow aliases for go imports 25 | // tokens.register(go::import("embed", "")); 26 | tokens.push(); 27 | tokens.append(static_literal("//go:embed")); 28 | tokens.space(); 29 | tokens.append(self.0.into()); 30 | } 31 | } 32 | 33 | fn go_embed(comment: T) -> Embed 34 | where 35 | T: Into, 36 | { 37 | Embed(comment) 38 | } 39 | 40 | // Format a comment where each line is preceeded by `//`. 41 | // Based on https://github.com/udoprog/genco/blob/1ec4869f458cf71d1d2ffef77fe051ea8058b391/src/lang/csharp/comment.rs 42 | struct Comment(T); 43 | 44 | impl FormatInto for Comment 45 | where 46 | T: IntoIterator, 47 | T::Item: Into, 48 | { 49 | fn format_into(self, tokens: &mut Tokens) { 50 | for line in self.0 { 51 | tokens.push(); 52 | tokens.append(static_literal("//")); 53 | tokens.space(); 54 | tokens.append(line.into()); 55 | } 56 | } 57 | } 58 | 59 | fn comment(comment: T) -> Comment 60 | where 61 | T: IntoIterator, 62 | T::Item: Into, 63 | { 64 | Comment(comment) 65 | } 66 | 67 | #[derive(Debug, Clone)] 68 | enum GoType { 69 | Bool, 70 | Uint8, 71 | Uint16, 72 | Uint32, 73 | Uint64, 74 | Int8, 75 | Int16, 76 | Int32, 77 | Int64, 78 | Float32, 79 | Float64, 80 | String, 81 | Error, 82 | Interface, 83 | // Pointer(Box), 84 | ValueOrOk(Box), 85 | ValueOrError(Box), 86 | Slice(Box), 87 | // MultiReturn(Vec), 88 | UserDefined(String), 89 | Nothing, 90 | } 91 | 92 | impl FormatInto for &GoType { 93 | fn format_into(self, tokens: &mut Tokens) { 94 | match self { 95 | GoType::Bool => tokens.append(static_literal("bool")), 96 | GoType::Uint8 => tokens.append(static_literal("uint8")), 97 | GoType::Uint16 => tokens.append(static_literal("uint16")), 98 | GoType::Uint32 => tokens.append(static_literal("uint32")), 99 | GoType::Uint64 => tokens.append(static_literal("uint64")), 100 | GoType::Int8 => tokens.append(static_literal("int8")), 101 | GoType::Int16 => tokens.append(static_literal("int16")), 102 | GoType::Int32 => tokens.append(static_literal("int32")), 103 | GoType::Int64 => tokens.append(static_literal("int64")), 104 | GoType::Float32 => tokens.append(static_literal("float32")), 105 | GoType::Float64 => tokens.append(static_literal("float64")), 106 | GoType::String => tokens.append(static_literal("string")), 107 | GoType::Error => tokens.append(static_literal("error")), 108 | GoType::Interface => tokens.append(static_literal("interface{}")), 109 | GoType::ValueOrOk(value_typ) => { 110 | value_typ.as_ref().format_into(tokens); 111 | tokens.append(static_literal(",")); 112 | tokens.space(); 113 | tokens.append(static_literal("bool")) 114 | } 115 | GoType::ValueOrError(value_typ) => { 116 | value_typ.as_ref().format_into(tokens); 117 | tokens.append(static_literal(",")); 118 | tokens.space(); 119 | tokens.append(static_literal("error")) 120 | } 121 | GoType::Slice(typ) => { 122 | tokens.append(static_literal("[]")); 123 | typ.as_ref().format_into(tokens); 124 | } 125 | // GoType::MultiReturn(typs) => { 126 | // tokens.append(quote!($(for typ in typs join (, ) => $typ))) 127 | // } 128 | // GoType::Pointer(typ) => { 129 | // tokens.append(static_literal("*")); 130 | // typ.as_ref().format_into(tokens); 131 | // } 132 | GoType::UserDefined(name) => { 133 | let id = GoIdentifier::Public { name }; 134 | id.format_into(tokens) 135 | } 136 | GoType::Nothing => (), 137 | } 138 | } 139 | } 140 | 141 | impl FormatInto for GoType { 142 | fn format_into(self, tokens: &mut Tokens) { 143 | (&self).format_into(tokens) 144 | } 145 | } 146 | 147 | #[derive(Clone)] 148 | enum GoResult { 149 | Empty, 150 | Anon(GoType), 151 | } 152 | 153 | impl FormatInto for GoResult { 154 | fn format_into(self, tokens: &mut Tokens) { 155 | (&self).format_into(tokens) 156 | } 157 | } 158 | impl FormatInto for &GoResult { 159 | fn format_into(self, tokens: &mut Tokens) { 160 | match &self { 161 | GoResult::Anon(typ @ GoType::ValueOrError(_) | typ @ GoType::ValueOrOk(_)) => { 162 | // Be cautious here as there are `(` and `)` surrounding the type 163 | tokens.append(quote!(($typ))) 164 | } 165 | GoResult::Anon(typ) => typ.format_into(tokens), 166 | GoResult::Empty => (), 167 | } 168 | } 169 | } 170 | enum Direction { 171 | Export, 172 | Import { interface_name: String }, 173 | } 174 | 175 | struct Func { 176 | direction: Direction, 177 | args: Vec, 178 | result: GoResult, 179 | tmp: usize, 180 | body: Tokens, 181 | block_storage: Vec>, 182 | blocks: Vec<(Tokens, Vec)>, 183 | sizes: SizeAlign, 184 | } 185 | 186 | #[derive(Clone, Copy)] 187 | enum GoIdentifier<'a> { 188 | Public { name: &'a str }, 189 | Private { name: &'a str }, 190 | Local { name: &'a str }, 191 | } 192 | 193 | impl<'a> GoIdentifier<'a> { 194 | fn chars(&self) -> Chars<'a> { 195 | match self { 196 | GoIdentifier::Public { name } => name.chars(), 197 | GoIdentifier::Private { name } => name.chars(), 198 | GoIdentifier::Local { name } => name.chars(), 199 | } 200 | } 201 | } 202 | 203 | impl From> for String { 204 | fn from(value: GoIdentifier) -> Self { 205 | let mut tokens: Tokens = Tokens::new(); 206 | value.format_into(&mut tokens); 207 | tokens.to_string().expect("to format correctly") 208 | } 209 | } 210 | 211 | impl FormatInto for &GoIdentifier<'_> { 212 | fn format_into(self, tokens: &mut Tokens) { 213 | let mut chars = self.chars(); 214 | 215 | // TODO(#12): Check for invalid first character 216 | 217 | if let GoIdentifier::Public { .. } = self { 218 | // https://stackoverflow.com/a/38406885 219 | match chars.next() { 220 | Some(c) => tokens.append(ItemStr::from(c.to_uppercase().to_string())), 221 | None => panic!("No function name"), 222 | }; 223 | }; 224 | 225 | while let Some(c) = chars.next() { 226 | match c { 227 | ' ' | '-' | '_' => { 228 | if let Some(c) = chars.next() { 229 | tokens.append(ItemStr::from(c.to_uppercase().to_string())); 230 | } 231 | } 232 | _ => tokens.append(ItemStr::from(c.to_string())), 233 | } 234 | } 235 | } 236 | } 237 | 238 | impl FormatInto for GoIdentifier<'_> { 239 | fn format_into(self, tokens: &mut Tokens) { 240 | (&self).format_into(tokens) 241 | } 242 | } 243 | 244 | impl Func { 245 | fn export(result: GoResult, sizes: SizeAlign) -> Self { 246 | Self { 247 | direction: Direction::Export, 248 | args: Vec::new(), 249 | result, 250 | tmp: 0, 251 | body: Tokens::new(), 252 | block_storage: Vec::new(), 253 | blocks: Vec::new(), 254 | sizes, 255 | } 256 | } 257 | 258 | fn import(interface_name: String, result: GoResult, sizes: SizeAlign) -> Self { 259 | Self { 260 | direction: Direction::Import { interface_name }, 261 | args: Vec::new(), 262 | result, 263 | tmp: 0, 264 | body: Tokens::new(), 265 | block_storage: Vec::new(), 266 | blocks: Vec::new(), 267 | sizes, 268 | } 269 | } 270 | 271 | fn tmp(&mut self) -> usize { 272 | let ret = self.tmp; 273 | self.tmp += 1; 274 | ret 275 | } 276 | 277 | fn args(&self) -> &[String] { 278 | &self.args 279 | } 280 | 281 | fn result(&self) -> &GoResult { 282 | &self.result 283 | } 284 | 285 | fn push_arg(&mut self, value: &str) { 286 | self.args.push(value.into()) 287 | } 288 | 289 | fn pop_block(&mut self) -> (Tokens, Vec) { 290 | self.blocks.pop().expect("should have block to pop") 291 | } 292 | } 293 | 294 | impl FormatInto for Func { 295 | fn format_into(self, tokens: &mut Tokens) { 296 | self.body.format_into(tokens) 297 | } 298 | } 299 | 300 | #[derive(Debug, Clone)] 301 | enum Operand { 302 | Literal(String), 303 | SingleValue(String), 304 | MultiValue((String, String)), 305 | } 306 | 307 | impl FormatInto for &Operand { 308 | fn format_into(self, tokens: &mut Tokens) { 309 | match self { 310 | Operand::Literal(val) => tokens.append(ItemStr::from(val)), 311 | Operand::SingleValue(val) => tokens.append(ItemStr::from(val)), 312 | Operand::MultiValue((val1, val2)) => { 313 | tokens.append(ItemStr::from(val1)); 314 | tokens.append(static_literal(",")); 315 | tokens.space(); 316 | tokens.append(ItemStr::from(val2)); 317 | } 318 | } 319 | } 320 | } 321 | impl FormatInto for &mut Operand { 322 | fn format_into(self, tokens: &mut Tokens) { 323 | let op: &Operand = self; 324 | op.format_into(tokens) 325 | } 326 | } 327 | 328 | impl Bindgen for Func { 329 | type Operand = Operand; 330 | 331 | fn emit( 332 | &mut self, 333 | resolve: &wit_bindgen_core::wit_parser::Resolve, 334 | inst: &wit_bindgen_core::abi::Instruction<'_>, 335 | operands: &mut Vec, 336 | results: &mut Vec, 337 | ) { 338 | let errors_new = &go::import("errors", "New"); 339 | let iter_element = "e"; 340 | let iter_base = "base"; 341 | 342 | // println!("instruction: {inst:?}, operands: {operands:?}"); 343 | 344 | match inst { 345 | Instruction::GetArg { nth } => { 346 | let arg = &format!("arg{nth}"); 347 | self.push_arg(arg); 348 | results.push(Operand::SingleValue(arg.into())); 349 | } 350 | Instruction::ConstZero { tys } => { 351 | for _ in tys.iter() { 352 | results.push(Operand::Literal("0".into())) 353 | } 354 | } 355 | Instruction::StringLower { realloc: None } => todo!("implement instruction: {inst:?}"), 356 | Instruction::StringLower { 357 | realloc: Some(realloc_name), 358 | } => { 359 | let tmp = self.tmp(); 360 | let ptr = &format!("ptr{tmp}"); 361 | let len = &format!("len{tmp}"); 362 | let err = &format!("err{tmp}"); 363 | let default = &format!("default{tmp}"); 364 | let memory = &format!("memory{tmp}"); 365 | let realloc = &format!("realloc{tmp}"); 366 | let operand = &operands[0]; 367 | match self.direction { 368 | Direction::Export => { 369 | quote_in! { self.body => 370 | $['\r'] 371 | $memory := i.module.Memory() 372 | $realloc := i.module.ExportedFunction($(quoted(*realloc_name))) 373 | $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) 374 | $(match &self.result { 375 | GoResult::Anon(GoType::ValueOrError(typ)) => { 376 | if $err != nil { 377 | var $default $(typ.as_ref()) 378 | return $default, $err 379 | } 380 | } 381 | GoResult::Anon(GoType::Error) => { 382 | if $err != nil { 383 | return $err 384 | } 385 | } 386 | GoResult::Anon(_) | GoResult::Empty => { 387 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 388 | if $err != nil { 389 | panic($err) 390 | } 391 | } 392 | }) 393 | } 394 | } 395 | Direction::Import { .. } => { 396 | quote_in! { self.body => 397 | $['\r'] 398 | $memory := mod.Memory() 399 | $realloc := mod.ExportedFunction($(quoted(*realloc_name))) 400 | $ptr, $len, $err := writeString(ctx, $operand, $memory, $realloc) 401 | if $err != nil { 402 | panic($err) 403 | } 404 | }; 405 | } 406 | } 407 | results.push(Operand::SingleValue(ptr.into())); 408 | results.push(Operand::SingleValue(len.into())); 409 | } 410 | Instruction::CallWasm { name, .. } => { 411 | let tmp = self.tmp(); 412 | let raw = &format!("raw{tmp}"); 413 | let ret = &format!("results{tmp}"); 414 | let err = &format!("err{tmp}"); 415 | let default = &format!("default{tmp}"); 416 | // TODO(#17): Wrapping every argument in `uint64` is bad and we should instead be looking 417 | // at the types and converting with proper guards in place 418 | quote_in! { self.body => 419 | $['\r'] 420 | $raw, $err := i.module.ExportedFunction($(quoted(*name))).Call(ctx, $(for op in operands.iter() join (, ) => uint64($op))) 421 | $(match &self.result { 422 | GoResult::Anon(GoType::ValueOrError(typ)) => { 423 | if $err != nil { 424 | var $default $(typ.as_ref()) 425 | return $default, $err 426 | } 427 | } 428 | GoResult::Anon(GoType::Error) => { 429 | if $err != nil { 430 | return $err 431 | } 432 | } 433 | GoResult::Anon(_) | GoResult::Empty => { 434 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 435 | if $err != nil { 436 | panic($err) 437 | } 438 | } 439 | }) 440 | 441 | 442 | $(comment(&[ 443 | "The cleanup via `cabi_post_*` cleans up the memory in the guest. By", 444 | "deferring this, we ensure that no memory is corrupted before the function", 445 | "is done accessing it." 446 | ])) 447 | defer func() { 448 | if _, err := i.module.ExportedFunction($(quoted(format!("cabi_post_{name}")))).Call(ctx, $raw...); err != nil { 449 | $(comment(&[ 450 | "If we get an error during cleanup, something really bad is", 451 | "going on, so we panic. Also, you can't return the error from", 452 | "the `defer`" 453 | ])) 454 | panic($errors_new("failed to cleanup")) 455 | } 456 | }() 457 | 458 | $ret := $raw[0] 459 | }; 460 | results.push(Operand::SingleValue(ret.into())); 461 | } 462 | Instruction::I32Load8U { offset } => { 463 | // TODO(#58): Support additional ArchitectureSize 464 | let offset = offset.size_wasm32(); 465 | let tmp = self.tmp(); 466 | let value = &format!("value{tmp}"); 467 | let ok = &format!("ok{tmp}"); 468 | let default = &format!("default{tmp}"); 469 | let operand = &operands[0]; 470 | quote_in! { self.body => 471 | $['\r'] 472 | $value, $ok := i.module.Memory().ReadByte(uint32($operand + $offset)) 473 | $(match &self.result { 474 | GoResult::Anon(GoType::ValueOrError(typ)) => { 475 | if !$ok { 476 | var $default $(typ.as_ref()) 477 | return $default, $errors_new("failed to read byte from memory") 478 | } 479 | } 480 | GoResult::Anon(GoType::Error) => { 481 | if !$ok { 482 | return $errors_new("failed to read byte from memory") 483 | } 484 | } 485 | GoResult::Anon(_) | GoResult::Empty => { 486 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 487 | if !$ok { 488 | panic($errors_new("failed to read byte from memory")) 489 | } 490 | } 491 | }) 492 | }; 493 | results.push(Operand::SingleValue(value.into())); 494 | } 495 | Instruction::I32FromBool => { 496 | let tmp = self.tmp(); 497 | let value = format!("value{tmp}"); 498 | let operand = &operands[0]; 499 | quote_in! { self.body => 500 | $['\r'] 501 | var $(&value) uint32 502 | if $operand { 503 | $(&value) = 1 504 | } else { 505 | $(&value) = 0 506 | } 507 | } 508 | results.push(Operand::SingleValue(value)) 509 | } 510 | Instruction::BoolFromI32 => { 511 | let tmp = self.tmp(); 512 | let value = format!("value{tmp}"); 513 | let operand = &operands[0]; 514 | quote_in! { self.body => 515 | $['\r'] 516 | $(&value) := $operand != 0 517 | } 518 | results.push(Operand::SingleValue(value)) 519 | } 520 | Instruction::I32FromU32 => { 521 | // It seems like this isn't needed because Wazero works with Go's uint32 type 522 | let operand = &operands[0]; 523 | results.push(operand.clone()); 524 | } 525 | Instruction::U32FromI32 => { 526 | let tmp = self.tmp(); 527 | let result = &format!("result{tmp}"); 528 | let operand = &operands[0]; 529 | quote_in! { self.body => 530 | $['\r'] 531 | $result := uint32($operand) 532 | }; 533 | results.push(Operand::SingleValue(result.into())); 534 | } 535 | Instruction::PointerLoad { offset } => { 536 | // TODO(#58): Support additional ArchitectureSize 537 | let offset = offset.size_wasm32(); 538 | let tmp = self.tmp(); 539 | let ptr = &format!("ptr{tmp}"); 540 | let ok = &format!("ok{tmp}"); 541 | let default = &format!("default{tmp}"); 542 | let operand = &operands[0]; 543 | quote_in! { self.body => 544 | $['\r'] 545 | $ptr, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) 546 | $(match &self.result { 547 | GoResult::Anon(GoType::ValueOrError(typ)) => { 548 | if !$ok { 549 | var $default $(typ.as_ref()) 550 | return $default, $errors_new("failed to read pointer from memory") 551 | } 552 | } 553 | GoResult::Anon(GoType::Error) => { 554 | if !$ok { 555 | return $errors_new("failed to read pointer from memory") 556 | } 557 | } 558 | GoResult::Anon(_) | GoResult::Empty => { 559 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 560 | if !$ok { 561 | panic($errors_new("failed to read pointer from memory")) 562 | } 563 | } 564 | }) 565 | }; 566 | results.push(Operand::SingleValue(ptr.into())); 567 | } 568 | Instruction::LengthLoad { offset } => { 569 | // TODO(#58): Support additional ArchitectureSize 570 | let offset = offset.size_wasm32(); 571 | let tmp = self.tmp(); 572 | let len = &format!("len{tmp}"); 573 | let ok = &format!("ok{tmp}"); 574 | let default = &format!("default{tmp}"); 575 | let operand = &operands[0]; 576 | quote_in! { self.body => 577 | $['\r'] 578 | $len, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) 579 | $(match &self.result { 580 | GoResult::Anon(GoType::ValueOrError(typ)) => { 581 | if !$ok { 582 | var $default $(typ.as_ref()) 583 | return $default, $errors_new("failed to read length from memory") 584 | } 585 | } 586 | GoResult::Anon(GoType::Error) => { 587 | if !$ok { 588 | return $errors_new("failed to read length from memory") 589 | } 590 | } 591 | GoResult::Anon(_) | GoResult::Empty => { 592 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 593 | if !$ok { 594 | panic($errors_new("failed to read length from memory")) 595 | } 596 | } 597 | }) 598 | }; 599 | results.push(Operand::SingleValue(len.into())); 600 | } 601 | Instruction::I32Load { offset } => { 602 | // TODO(#58): Support additional ArchitectureSize 603 | let offset = offset.size_wasm32(); 604 | let tmp = self.tmp(); 605 | let value = &format!("value{tmp}"); 606 | let ok = &format!("ok{tmp}"); 607 | let default = &format!("default{tmp}"); 608 | let operand = &operands[0]; 609 | quote_in! { self.body => 610 | $['\r'] 611 | $value, $ok := i.module.Memory().ReadUint32Le(uint32($operand + $offset)) 612 | $(match &self.result { 613 | GoResult::Anon(GoType::ValueOrError(typ)) => { 614 | if !$ok { 615 | var $default $(typ.as_ref()) 616 | return $default, $errors_new("failed to read i32 from memory") 617 | } 618 | } 619 | GoResult::Anon(GoType::Error) => { 620 | if !$ok { 621 | return $errors_new("failed to read i32 from memory") 622 | } 623 | } 624 | GoResult::Anon(_) | GoResult::Empty => { 625 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 626 | if !$ok { 627 | panic($errors_new("failed to read i32 from memory")) 628 | } 629 | } 630 | }) 631 | }; 632 | results.push(Operand::SingleValue(value.into())); 633 | } 634 | Instruction::StringLift => { 635 | let tmp = self.tmp(); 636 | let buf = &format!("buf{tmp}"); 637 | let ok = &format!("ok{tmp}"); 638 | let default = &format!("default{tmp}"); 639 | let str = &format!("str{tmp}"); 640 | let ptr = &operands[0]; 641 | let len = &operands[1]; 642 | match self.direction { 643 | Direction::Export { .. } => { 644 | quote_in! { self.body => 645 | $['\r'] 646 | $buf, $ok := i.module.Memory().Read($ptr, $len) 647 | $(match &self.result { 648 | GoResult::Anon(GoType::ValueOrError(typ)) => { 649 | if !$ok { 650 | var $default $(typ.as_ref()) 651 | return $default, $errors_new("failed to read bytes from memory") 652 | } 653 | } 654 | GoResult::Anon(GoType::Error) => { 655 | if !$ok { 656 | return $errors_new("failed to read bytes from memory") 657 | } 658 | } 659 | GoResult::Anon(_) | GoResult::Empty => { 660 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 661 | if !$ok { 662 | panic($errors_new("failed to read bytes from memory")) 663 | } 664 | } 665 | }) 666 | $str := string($buf) 667 | }; 668 | } 669 | Direction::Import { .. } => { 670 | quote_in! { self.body => 671 | $['\r'] 672 | $buf, $ok := mod.Memory().Read($ptr, $len) 673 | if !$ok { 674 | panic($errors_new("failed to read bytes from memory")) 675 | } 676 | $str := string($buf) 677 | }; 678 | } 679 | } 680 | results.push(Operand::SingleValue(str.into())); 681 | } 682 | Instruction::ResultLift { 683 | result: 684 | Result_ { 685 | ok: Some(typ), 686 | err: Some(Type::String), 687 | }, 688 | .. 689 | } => { 690 | let (err_block, err_results) = self.pop_block(); 691 | assert_eq!(err_results.len(), 1); 692 | let err_op = &err_results[0]; 693 | 694 | let (ok_block, ok_results) = self.pop_block(); 695 | assert_eq!(ok_results.len(), 1); 696 | let ok_op = &ok_results[0]; 697 | 698 | let tmp = self.tmp(); 699 | let value = &format!("value{tmp}"); 700 | let err = &format!("err{tmp}"); 701 | let typ = resolve_type(typ, resolve); 702 | let tag = &operands[0]; 703 | quote_in! { self.body => 704 | $['\r'] 705 | var $value $typ 706 | var $err error 707 | switch $tag { 708 | case 0: 709 | $ok_block 710 | $value = $ok_op 711 | case 1: 712 | $err_block 713 | $err = $errors_new($err_op) 714 | default: 715 | $err = $errors_new("invalid variant discriminant for expected") 716 | } 717 | }; 718 | 719 | results.push(Operand::MultiValue((value.into(), err.into()))); 720 | } 721 | Instruction::ResultLift { 722 | result: 723 | Result_ { 724 | ok: None, 725 | err: Some(Type::String), 726 | }, 727 | .. 728 | } => { 729 | let (err_block, err_results) = self.pop_block(); 730 | assert_eq!(err_results.len(), 1); 731 | let err_op = &err_results[0]; 732 | 733 | let (ok_block, ok_results) = self.pop_block(); 734 | assert_eq!(ok_results.len(), 0); 735 | 736 | let tmp = self.tmp(); 737 | let err = &format!("err{tmp}"); 738 | let tag = &operands[0]; 739 | quote_in! { self.body => 740 | $['\r'] 741 | var $err error 742 | switch $tag { 743 | case 0: 744 | $ok_block 745 | case 1: 746 | $err_block 747 | $err = $errors_new($err_op) 748 | default: 749 | $err = $errors_new("invalid variant discriminant for expected") 750 | } 751 | }; 752 | 753 | results.push(Operand::SingleValue(err.into())); 754 | } 755 | Instruction::ResultLift { .. } => todo!("implement instruction: {inst:?}"), 756 | Instruction::Return { amt, .. } => { 757 | if *amt != 0 { 758 | let operand = &operands[0]; 759 | quote_in! { self.body => 760 | $['\r'] 761 | return $operand 762 | }; 763 | } 764 | } 765 | Instruction::CallInterface { func, .. } => { 766 | let ident = GoIdentifier::Public { name: &func.name }; 767 | let tmp = self.tmp(); 768 | let args = quote!($(for op in operands.iter() join (, ) => $op)); 769 | let returns = match &func.result { 770 | None => GoType::Nothing, 771 | Some(typ) => resolve_type(typ, resolve), 772 | }; 773 | let value = &format!("value{tmp}"); 774 | let err = &format!("err{tmp}"); 775 | let ok = &format!("ok{tmp}"); 776 | match &self.direction { 777 | Direction::Export { .. } => todo!("TODO(#10): handle export direction"), 778 | Direction::Import { interface_name, .. } => { 779 | let iface = GoIdentifier::Local { 780 | name: interface_name, 781 | }; 782 | quote_in! { self.body => 783 | $['\r'] 784 | $(match returns { 785 | GoType::Nothing => $iface.$ident(ctx, $args), 786 | GoType::Bool | GoType::Uint32 | GoType::Interface | GoType::String | GoType::UserDefined(_) => $value := $iface.$ident(ctx, $args), 787 | GoType::Error => $err := $iface.$ident(ctx, $args), 788 | GoType::ValueOrError(_) => { 789 | $value, $err := $iface.$ident(ctx, $args) 790 | } 791 | GoType::ValueOrOk(_) => { 792 | $value, $ok := $iface.$ident(ctx, $args) 793 | } 794 | _ => $(comment(&["TODO(#9): handle return type"])) 795 | }) 796 | } 797 | } 798 | } 799 | match returns { 800 | GoType::Nothing => (), 801 | GoType::Bool 802 | | GoType::Uint32 803 | | GoType::Interface 804 | | GoType::UserDefined(_) 805 | | GoType::String => { 806 | results.push(Operand::SingleValue(value.into())); 807 | } 808 | GoType::Error => { 809 | results.push(Operand::SingleValue(err.into())); 810 | } 811 | GoType::ValueOrError(_) => { 812 | results.push(Operand::MultiValue((value.into(), err.into()))); 813 | } 814 | GoType::ValueOrOk(_) => { 815 | results.push(Operand::MultiValue((value.into(), ok.into()))) 816 | } 817 | _ => todo!("TODO(#9): handle return type - {returns:?}"), 818 | } 819 | } 820 | Instruction::VariantPayloadName => { 821 | results.push(Operand::SingleValue("variantPayload".into())); 822 | } 823 | Instruction::I32Const { val } => results.push(Operand::Literal(val.to_string())), 824 | Instruction::I32Store8 { offset } => { 825 | // TODO(#58): Support additional ArchitectureSize 826 | let offset = offset.size_wasm32(); 827 | let tag = &operands[0]; 828 | let ptr = &operands[1]; 829 | if let Operand::Literal(byte) = tag { 830 | match &self.direction { 831 | Direction::Export => { 832 | quote_in! { self.body => 833 | $['\r'] 834 | i.module.Memory().WriteByte($ptr+$offset, $byte) 835 | } 836 | } 837 | Direction::Import { .. } => { 838 | quote_in! { self.body => 839 | $['\r'] 840 | mod.Memory().WriteByte($ptr+$offset, $byte) 841 | } 842 | } 843 | } 844 | } else { 845 | let tmp = self.tmp(); 846 | let byte = format!("byte{tmp}"); 847 | match &self.direction { 848 | Direction::Export => { 849 | quote_in! { self.body => 850 | $['\r'] 851 | var $(&byte) uint8 852 | switch $tag { 853 | case 0: 854 | $(&byte) = 0 855 | case 1: 856 | $(&byte) = 1 857 | default: 858 | $(comment(["TODO(#8): Return an error if the return type allows it"])) 859 | panic($errors_new("invalid int8 value encountered")) 860 | } 861 | i.module.Memory().WriteByte($ptr+$offset, $byte) 862 | } 863 | } 864 | Direction::Import { .. } => { 865 | quote_in! { self.body => 866 | $['\r'] 867 | var $(&byte) uint8 868 | switch $tag { 869 | case 0: 870 | $(&byte) = 0 871 | case 1: 872 | $(&byte) = 1 873 | default: 874 | panic($errors_new("invalid int8 value encountered")) 875 | } 876 | mod.Memory().WriteByte($ptr+$offset, $byte) 877 | } 878 | } 879 | } 880 | } 881 | } 882 | Instruction::I32Store { offset } => { 883 | // TODO(#58): Support additional ArchitectureSize 884 | let offset = offset.size_wasm32(); 885 | let tag = &operands[0]; 886 | let ptr = &operands[1]; 887 | match &self.direction { 888 | Direction::Export => { 889 | quote_in! { self.body => 890 | $['\r'] 891 | i.module.Memory().WriteUint32Le($ptr+$offset, $tag) 892 | } 893 | } 894 | Direction::Import { .. } => { 895 | quote_in! { self.body => 896 | $['\r'] 897 | mod.Memory().WriteUint32Le($ptr+$offset, $tag) 898 | } 899 | } 900 | } 901 | } 902 | Instruction::LengthStore { offset } => { 903 | // TODO(#58): Support additional ArchitectureSize 904 | let offset = offset.size_wasm32(); 905 | let len = &operands[0]; 906 | let ptr = &operands[1]; 907 | match &self.direction { 908 | Direction::Export => { 909 | quote_in! { self.body => 910 | $['\r'] 911 | i.module.Memory().WriteUint32Le($ptr+$offset, uint32($len)) 912 | } 913 | } 914 | Direction::Import { .. } => { 915 | quote_in! { self.body => 916 | $['\r'] 917 | mod.Memory().WriteUint32Le($ptr+$offset, uint32($len)) 918 | } 919 | } 920 | } 921 | } 922 | Instruction::PointerStore { offset } => { 923 | // TODO(#58): Support additional ArchitectureSize 924 | let offset = offset.size_wasm32(); 925 | let value = &operands[0]; 926 | let ptr = &operands[1]; 927 | match &self.direction { 928 | Direction::Export => { 929 | quote_in! { self.body => 930 | $['\r'] 931 | i.module.Memory().WriteUint32Le($ptr+$offset, uint32($value)) 932 | } 933 | } 934 | Direction::Import { .. } => { 935 | quote_in! { self.body => 936 | $['\r'] 937 | mod.Memory().WriteUint32Le($ptr+$offset, uint32($value)) 938 | } 939 | } 940 | } 941 | } 942 | Instruction::ResultLower { 943 | result: 944 | Result_ { 945 | ok: Some(_), 946 | err: Some(Type::String), 947 | }, 948 | .. 949 | } => { 950 | let (err_block, _) = self.pop_block(); 951 | let (ok_block, _) = self.pop_block(); 952 | let operand = &operands[0]; 953 | let (ok, err) = match operand { 954 | Operand::Literal(_) => { 955 | panic!("impossible: expected Operand::MultiValue but got Operand::Literal") 956 | } 957 | Operand::SingleValue(_) => panic!( 958 | "impossible: expected Operand::MultiValue but got Operand::SingleValue" 959 | ), 960 | Operand::MultiValue(bindings) => bindings, 961 | }; 962 | quote_in! { self.body => 963 | $['\r'] 964 | if $err != nil { 965 | variantPayload := $err.Error() 966 | $err_block 967 | } else { 968 | variantPayload := $ok 969 | $ok_block 970 | } 971 | }; 972 | } 973 | Instruction::ResultLower { 974 | result: 975 | Result_ { 976 | ok: None, 977 | err: Some(Type::String), 978 | }, 979 | .. 980 | } => { 981 | let (err, _) = self.pop_block(); 982 | let (ok, _) = self.pop_block(); 983 | let err_result = &operands[0]; 984 | quote_in! { self.body => 985 | $['\r'] 986 | if $err_result != nil { 987 | variantPayload := $err_result.Error() 988 | $err 989 | } else { 990 | $ok 991 | } 992 | }; 993 | } 994 | Instruction::ResultLower { .. } => todo!("implement instruction: {inst:?}"), 995 | Instruction::OptionLift { payload, .. } => { 996 | let (some, some_results) = self.blocks.pop().unwrap(); 997 | let (none, _) = self.blocks.pop().unwrap(); 998 | let some_result = &some_results[0]; 999 | 1000 | let tmp = self.tmp(); 1001 | let result = &format!("result{tmp}"); 1002 | let ok = &format!("ok{tmp}"); 1003 | let typ = resolve_type(payload, resolve); 1004 | let op = &operands[0]; 1005 | 1006 | quote_in! { self.body => 1007 | $['\r'] 1008 | var $result $typ 1009 | var $ok bool 1010 | if $op == 0 { 1011 | $none 1012 | $ok = false 1013 | } else { 1014 | $some 1015 | $ok = true 1016 | $result = $some_result 1017 | } 1018 | }; 1019 | 1020 | results.push(Operand::MultiValue((result.into(), ok.into()))); 1021 | } 1022 | Instruction::OptionLower { 1023 | payload: Type::String, 1024 | results: result_types, 1025 | .. 1026 | } => { 1027 | let (mut some_block, some_results) = self.pop_block(); 1028 | let (mut none_block, none_results) = self.pop_block(); 1029 | 1030 | let tmp = self.tmp(); 1031 | 1032 | let mut vars: Tokens = Tokens::new(); 1033 | for i in 0..result_types.len() { 1034 | let variant = &format!("variant{tmp}_{i}"); 1035 | let typ = resolve_wasm_type(&result_types[i]); 1036 | results.push(Operand::SingleValue(variant.into())); 1037 | 1038 | quote_in! { vars => 1039 | $['\r'] 1040 | var $variant $typ 1041 | } 1042 | 1043 | let some_result = &some_results[i]; 1044 | let none_result = &none_results[i]; 1045 | quote_in! { some_block => 1046 | $['\r'] 1047 | $variant = $some_result 1048 | }; 1049 | quote_in! { none_block => 1050 | $['\r'] 1051 | $variant = $none_result 1052 | }; 1053 | } 1054 | 1055 | let operand = &operands[0]; 1056 | match operand { 1057 | Operand::Literal(_) => { 1058 | panic!("impossible: expected Operand::MultiValue but got Operand::Literal") 1059 | } 1060 | // TODO(#7): This is a weird hack to implement `option` 1061 | // as arguments that currently only works for strings 1062 | // because it checks the empty string as the zero value to 1063 | // consider it None 1064 | Operand::SingleValue(value) => { 1065 | quote_in! { self.body => 1066 | $['\r'] 1067 | $vars 1068 | if $value == "" { 1069 | $none_block 1070 | } else { 1071 | variantPayload := $value 1072 | $some_block 1073 | } 1074 | }; 1075 | } 1076 | Operand::MultiValue((value, ok)) => { 1077 | quote_in! { self.body => 1078 | $['\r'] 1079 | if $ok { 1080 | variantPayload := $value 1081 | $some_block 1082 | } else { 1083 | $none_block 1084 | } 1085 | }; 1086 | } 1087 | }; 1088 | } 1089 | Instruction::OptionLower { .. } => todo!("implement instruction: {inst:?}"), 1090 | Instruction::RecordLower { record, .. } => { 1091 | let tmp = self.tmp(); 1092 | let operand = &operands[0]; 1093 | for field in record.fields.iter() { 1094 | let struct_field = GoIdentifier::Public { name: &field.name }; 1095 | let var = GoIdentifier::Local { 1096 | name: &format!("{}{tmp}", &field.name), 1097 | }; 1098 | quote_in! { self.body => 1099 | $['\r'] 1100 | $var := $operand.$struct_field 1101 | } 1102 | results.push(Operand::SingleValue(var.into())) 1103 | } 1104 | } 1105 | Instruction::RecordLift { record, name, .. } => { 1106 | let tmp = self.tmp(); 1107 | let value = &format!("value{tmp}"); 1108 | let fields = record 1109 | .fields 1110 | .iter() 1111 | .zip(operands) 1112 | .map(|(field, op)| (GoIdentifier::Public { name: &field.name }, op)); 1113 | 1114 | quote_in! {self.body => 1115 | $['\r'] 1116 | $value := $(GoIdentifier::Public { name }){ 1117 | $(for (name, op) in fields join ($['\r']) => $name: $op,) 1118 | } 1119 | }; 1120 | results.push(Operand::SingleValue(value.into())) 1121 | } 1122 | Instruction::IterElem { .. } => results.push(Operand::SingleValue(iter_element.into())), 1123 | Instruction::IterBasePointer => results.push(Operand::SingleValue(iter_base.into())), 1124 | Instruction::ListLower { realloc: None, .. } => { 1125 | todo!("implement instruction: {inst:?}") 1126 | } 1127 | Instruction::ListLower { 1128 | element, 1129 | realloc: Some(realloc_name), 1130 | } => { 1131 | let (body, _) = self.pop_block(); 1132 | let tmp = self.tmp(); 1133 | let vec = &format!("vec{tmp}"); 1134 | let result = &format!("result{tmp}"); 1135 | let err = &format!("err{tmp}"); 1136 | let default = &format!("default{tmp}"); 1137 | let ptr = &format!("ptr{tmp}"); 1138 | let len = &format!("len{tmp}"); 1139 | let operand = &operands[0]; 1140 | let size = self.sizes.size(element).size_wasm32(); 1141 | let align = self.sizes.align(element).align_wasm32(); 1142 | 1143 | quote_in! { self.body => 1144 | $['\r'] 1145 | $vec := $operand 1146 | $len := uint64(len($vec)) 1147 | $result, $err := i.module.ExportedFunction($(quoted(*realloc_name))).Call(ctx, 0, 0, $align, $len * $size) 1148 | $(match &self.result { 1149 | GoResult::Anon(GoType::ValueOrError(typ)) => { 1150 | if $err != nil { 1151 | var $default $(typ.as_ref()) 1152 | return $default, $err 1153 | } 1154 | } 1155 | GoResult::Anon(GoType::Error) => { 1156 | if $err != nil { 1157 | return $err 1158 | } 1159 | } 1160 | GoResult::Anon(_) | GoResult::Empty => { 1161 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 1162 | if $err != nil { 1163 | panic($err) 1164 | } 1165 | } 1166 | }) 1167 | $ptr := $result[0] 1168 | for idx := uint64(0); idx < $len; idx++ { 1169 | $iter_element := $vec[idx] 1170 | $iter_base := uint32($ptr + uint64(idx) * uint64($size)) 1171 | $body 1172 | } 1173 | }; 1174 | results.push(Operand::SingleValue(ptr.into())); 1175 | results.push(Operand::SingleValue(len.into())); 1176 | } 1177 | Instruction::ListLift { element, .. } => { 1178 | let (body, body_results) = self.pop_block(); 1179 | let tmp = self.tmp(); 1180 | let size = self.sizes.size(element).size_wasm32(); 1181 | let len = &format!("len{tmp}"); 1182 | let base = &format!("base{tmp}"); 1183 | let result = &format!("result{tmp}"); 1184 | let idx = &format!("idx{tmp}"); 1185 | 1186 | let base_operand = &operands[0]; 1187 | let len_operand = &operands[1]; 1188 | let body_result = &body_results[0]; 1189 | 1190 | let typ = resolve_type(element, resolve); 1191 | 1192 | quote_in! { self.body => 1193 | $['\r'] 1194 | $base := $base_operand 1195 | $len := $len_operand 1196 | $result := make([]$typ, $len) 1197 | for $idx := uint32(0); $idx < $len; $idx++ { 1198 | base := $base + $idx * $size 1199 | $body 1200 | $result[$idx] = $body_result 1201 | } 1202 | } 1203 | results.push(Operand::SingleValue(result.into())); 1204 | } 1205 | Instruction::VariantLower { 1206 | variant, 1207 | results: result_types, 1208 | .. 1209 | } => { 1210 | let blocks = self 1211 | .blocks 1212 | .drain(self.blocks.len() - variant.cases.len()..) 1213 | .collect::>(); 1214 | let tmp = self.tmp(); 1215 | let value = &operands[0]; 1216 | let default = &format!("default{tmp}"); 1217 | 1218 | for (i, typ) in result_types.iter().enumerate() { 1219 | let variant_item = &format!("variant{tmp}_{i}"); 1220 | let typ = resolve_wasm_type(typ); 1221 | quote_in! { self.body => 1222 | $['\r'] 1223 | var $variant_item $typ 1224 | } 1225 | results.push(Operand::SingleValue(variant_item.into())); 1226 | } 1227 | 1228 | let mut cases: Tokens = Tokens::new(); 1229 | for (case, (block, block_results)) in variant.cases.iter().zip(blocks) { 1230 | let mut assignments: Tokens = Tokens::new(); 1231 | for (i, result) in block_results.iter().enumerate() { 1232 | let variant_item = &format!("variant{tmp}_{i}"); 1233 | quote_in! { assignments => 1234 | $['\r'] 1235 | $variant_item = $result 1236 | }; 1237 | } 1238 | 1239 | let name = GoIdentifier::Public { name: &case.name }; 1240 | quote_in! { cases => 1241 | $['\r'] 1242 | case $name: 1243 | $block 1244 | $assignments 1245 | } 1246 | } 1247 | 1248 | quote_in! { self.body => 1249 | $['\r'] 1250 | switch variantPayload := $value.(type) { 1251 | $cases 1252 | default: 1253 | $(match &self.result { 1254 | GoResult::Anon(GoType::ValueOrError(typ)) => { 1255 | var $default $(typ.as_ref()) 1256 | return $default, $errors_new("invalid variant type provided") 1257 | } 1258 | GoResult::Anon(GoType::Error) => { 1259 | return $errors_new("invalid variant type provided") 1260 | } 1261 | GoResult::Anon(_) | GoResult::Empty => { 1262 | $(comment(&["The return type doesn't contain an error so we panic if one is encountered"])) 1263 | panic($errors_new("invalid variant type provided")) 1264 | } 1265 | }) 1266 | } 1267 | } 1268 | } 1269 | Instruction::EnumLower { enum_, .. } => { 1270 | let value = &operands[0]; 1271 | let tmp = self.tmp(); 1272 | let enum_tmp = &format!("enum{tmp}"); 1273 | 1274 | let mut cases: Tokens = Tokens::new(); 1275 | for (i, case) in enum_.cases.iter().enumerate() { 1276 | let case_name = GoIdentifier::Public { name: &case.name }; 1277 | quote_in! { cases => 1278 | $['\r'] 1279 | case $case_name: 1280 | $enum_tmp = $i 1281 | }; 1282 | } 1283 | 1284 | quote_in! { self.body => 1285 | $['\r'] 1286 | var $enum_tmp uint32 1287 | switch $value { 1288 | $cases 1289 | default: 1290 | panic($errors_new("invalid enum type provided")) 1291 | } 1292 | }; 1293 | 1294 | results.push(Operand::SingleValue(enum_tmp.to_string())); 1295 | } 1296 | Instruction::Bitcasts { .. } => todo!("implement instruction: {inst:?}"), 1297 | Instruction::I32Load8S { .. } => todo!("implement instruction: {inst:?}"), 1298 | Instruction::I32Load16U { .. } => todo!("implement instruction: {inst:?}"), 1299 | Instruction::I32Load16S { .. } => todo!("implement instruction: {inst:?}"), 1300 | Instruction::I64Load { .. } => todo!("implement instruction: {inst:?}"), 1301 | Instruction::F32Load { .. } => todo!("implement instruction: {inst:?}"), 1302 | Instruction::F64Load { .. } => todo!("implement instruction: {inst:?}"), 1303 | Instruction::I32Store16 { .. } => todo!("implement instruction: {inst:?}"), 1304 | Instruction::I64Store { .. } => todo!("implement instruction: {inst:?}"), 1305 | Instruction::F32Store { .. } => todo!("implement instruction: {inst:?}"), 1306 | Instruction::F64Store { .. } => todo!("implement instruction: {inst:?}"), 1307 | Instruction::I32FromChar => todo!("implement instruction: {inst:?}"), 1308 | Instruction::I64FromU64 => todo!("implement instruction: {inst:?}"), 1309 | Instruction::I64FromS64 => todo!("implement instruction: {inst:?}"), 1310 | Instruction::I32FromS32 => todo!("implement instruction: {inst:?}"), 1311 | Instruction::I32FromU16 => todo!("implement instruction: {inst:?}"), 1312 | Instruction::I32FromS16 => todo!("implement instruction: {inst:?}"), 1313 | Instruction::I32FromU8 => todo!("implement instruction: {inst:?}"), 1314 | Instruction::I32FromS8 => todo!("implement instruction: {inst:?}"), 1315 | Instruction::CoreF32FromF32 => todo!("implement instruction: {inst:?}"), 1316 | Instruction::CoreF64FromF64 => todo!("implement instruction: {inst:?}"), 1317 | Instruction::S8FromI32 => todo!("implement instruction: {inst:?}"), 1318 | Instruction::U8FromI32 => todo!("implement instruction: {inst:?}"), 1319 | Instruction::S16FromI32 => todo!("implement instruction: {inst:?}"), 1320 | Instruction::U16FromI32 => todo!("implement instruction: {inst:?}"), 1321 | Instruction::S32FromI32 => todo!("implement instruction: {inst:?}"), 1322 | Instruction::S64FromI64 => todo!("implement instruction: {inst:?}"), 1323 | Instruction::U64FromI64 => todo!("implement instruction: {inst:?}"), 1324 | Instruction::CharFromI32 => todo!("implement instruction: {inst:?}"), 1325 | Instruction::F32FromCoreF32 => todo!("implement instruction: {inst:?}"), 1326 | Instruction::F64FromCoreF64 => todo!("implement instruction: {inst:?}"), 1327 | Instruction::TupleLower { .. } => todo!("implement instruction: {inst:?}"), 1328 | Instruction::TupleLift { .. } => todo!("implement instruction: {inst:?}"), 1329 | Instruction::FlagsLower { .. } => todo!("implement instruction: {inst:?}"), 1330 | Instruction::FlagsLift { .. } => todo!("implement instruction: {inst:?}"), 1331 | Instruction::VariantLift { .. } => { 1332 | todo!("implement instruction: {inst:?}") 1333 | } 1334 | Instruction::EnumLift { .. } => todo!("implement instruction: {inst:?}"), 1335 | Instruction::Malloc { .. } => todo!("implement instruction: {inst:?}"), 1336 | Instruction::HandleLower { .. } | Instruction::HandleLift { .. } => { 1337 | todo!("implement resources: {inst:?}") 1338 | } 1339 | Instruction::ListCanonLower { .. } | Instruction::ListCanonLift { .. } => { 1340 | unimplemented!("gravity doesn't represent lists as Canonical") 1341 | } 1342 | Instruction::GuestDeallocateString 1343 | | Instruction::GuestDeallocate { .. } 1344 | | Instruction::GuestDeallocateList { .. } 1345 | | Instruction::GuestDeallocateVariant { .. } => { 1346 | unimplemented!("gravity doesn't generate the Guest code") 1347 | } 1348 | Instruction::FutureLower { .. } => todo!("implement instruction: {inst:?}"), 1349 | Instruction::FutureLift { .. } => todo!("implement instruction: {inst:?}"), 1350 | Instruction::StreamLower { .. } => todo!("implement instruction: {inst:?}"), 1351 | Instruction::StreamLift { .. } => todo!("implement instruction: {inst:?}"), 1352 | Instruction::ErrorContextLower { .. } => todo!("implement instruction: {inst:?}"), 1353 | Instruction::ErrorContextLift { .. } => todo!("implement instruction: {inst:?}"), 1354 | Instruction::AsyncTaskReturn { .. } => todo!("implement instruction: {inst:?}"), 1355 | Instruction::DropHandle { .. } => todo!("implement instruction: {inst:?}"), 1356 | Instruction::Flush { amt } => { 1357 | for n in 0..*amt { 1358 | results.push(operands[n].clone()); 1359 | } 1360 | } 1361 | } 1362 | } 1363 | 1364 | fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> Self::Operand { 1365 | unimplemented!("return_pointer") 1366 | } 1367 | 1368 | fn push_block(&mut self) { 1369 | let prev = mem::replace(&mut self.body, Tokens::new()); 1370 | self.block_storage.push(prev); 1371 | } 1372 | 1373 | fn finish_block(&mut self, operands: &mut Vec) { 1374 | let to_restore = self.block_storage.pop().expect("should have body"); 1375 | let src = mem::replace(&mut self.body, to_restore); 1376 | self.blocks.push((src, mem::take(operands))); 1377 | } 1378 | 1379 | fn sizes(&self) -> &wit_bindgen_core::wit_parser::SizeAlign { 1380 | &self.sizes 1381 | } 1382 | 1383 | fn is_list_canonical( 1384 | &self, 1385 | _resolve: &wit_bindgen_core::wit_parser::Resolve, 1386 | _element: &wit_bindgen_core::wit_parser::Type, 1387 | ) -> bool { 1388 | // Go slices are never directly in the Wasm Memory, so they are never "canonical" 1389 | false 1390 | } 1391 | } 1392 | 1393 | fn resolve_wasm_type(typ: &WasmType) -> GoType { 1394 | match typ { 1395 | WasmType::I32 => GoType::Uint32, 1396 | WasmType::I64 => GoType::Uint64, 1397 | WasmType::F32 => GoType::Float32, 1398 | WasmType::F64 => GoType::Float64, 1399 | WasmType::Pointer => GoType::Uint64, 1400 | WasmType::PointerOrI64 => GoType::Uint64, 1401 | WasmType::Length => GoType::Uint64, 1402 | } 1403 | } 1404 | 1405 | fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType { 1406 | match typ { 1407 | Type::Bool => GoType::Bool, 1408 | Type::U8 => GoType::Uint8, 1409 | Type::U16 => GoType::Uint16, 1410 | Type::U32 => GoType::Uint32, 1411 | Type::U64 => GoType::Uint64, 1412 | Type::S8 => GoType::Int8, 1413 | Type::S16 => GoType::Int16, 1414 | Type::S32 => GoType::Int32, 1415 | Type::S64 => GoType::Int64, 1416 | Type::F32 => GoType::Float32, 1417 | Type::F64 => GoType::Float64, 1418 | Type::Char => { 1419 | // Is this a Go "rune"? 1420 | todo!("TODO(#6): resolve char type") 1421 | } 1422 | Type::String => GoType::String, 1423 | Type::ErrorContext => todo!("TODO(#4): implement error context conversion"), 1424 | Type::Id(typ_id) => { 1425 | let TypeDef { name, kind, .. } = resolve.types.get(*typ_id).unwrap(); 1426 | match kind { 1427 | TypeDefKind::Record(Record { .. }) => { 1428 | let typ = name.clone().expect("record to have a name"); 1429 | GoType::UserDefined(typ) 1430 | } 1431 | TypeDefKind::Resource => todo!("TODO(#5): implement resources"), 1432 | TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"), 1433 | TypeDefKind::Flags(_) => todo!("TODO(#4): implement flag conversion"), 1434 | TypeDefKind::Tuple(_) => todo!("TODO(#4): implement tuple conversion"), 1435 | // Variants are handled as an empty interfaces in type signatures; however, that 1436 | // means they require runtime type reflection 1437 | TypeDefKind::Variant(_) => GoType::Interface, 1438 | TypeDefKind::Enum(_) => { 1439 | let typ = name.clone().expect("enum to have a name"); 1440 | GoType::UserDefined(typ) 1441 | } 1442 | TypeDefKind::Option(value) => { 1443 | GoType::ValueOrOk(Box::new(resolve_type(value, resolve))) 1444 | } 1445 | TypeDefKind::Result(Result_ { 1446 | ok: Some(ok), 1447 | err: Some(Type::String), 1448 | }) => GoType::ValueOrError(Box::new(resolve_type(ok, resolve))), 1449 | TypeDefKind::Result(Result_ { 1450 | ok: Some(_), 1451 | err: Some(_), 1452 | }) => { 1453 | todo!("TODO(#4): implement remaining result conversion") 1454 | } 1455 | TypeDefKind::Result(Result_ { 1456 | ok: Some(ok), 1457 | err: None, 1458 | }) => resolve_type(ok, resolve), 1459 | TypeDefKind::Result(Result_ { 1460 | ok: None, 1461 | err: Some(Type::String), 1462 | }) => GoType::Error, 1463 | TypeDefKind::Result(Result_ { 1464 | ok: None, 1465 | err: Some(_), 1466 | }) => todo!("TODO(#4): implement remaining result conversion"), 1467 | TypeDefKind::Result(Result_ { 1468 | ok: None, 1469 | err: None, 1470 | }) => GoType::Nothing, 1471 | TypeDefKind::List(typ) => GoType::Slice(Box::new(resolve_type(typ, resolve))), 1472 | TypeDefKind::Future(_) => todo!("TODO(#4): implement future conversion"), 1473 | TypeDefKind::Stream(_) => todo!("TODO(#4): implement stream conversion"), 1474 | TypeDefKind::Type(_) => { 1475 | let typ = name.clone().expect("type alias to have a name"); 1476 | GoType::UserDefined(typ) 1477 | } 1478 | TypeDefKind::FixedSizeList(_, _) => { 1479 | todo!("TODO(#4): implement fixed size list conversion") 1480 | } 1481 | TypeDefKind::Unknown => todo!("TODO(#4): implement unknown conversion"), 1482 | } 1483 | } 1484 | } 1485 | } 1486 | 1487 | struct Bindings { 1488 | out: Tokens, 1489 | } 1490 | 1491 | impl Bindings { 1492 | fn new() -> Self { 1493 | Self { out: Tokens::new() } 1494 | } 1495 | 1496 | fn define_type(&mut self, typ_def: &TypeDef, resolve: &Resolve) { 1497 | let TypeDef { name, kind, .. } = typ_def; 1498 | match kind { 1499 | TypeDefKind::Record(Record { fields }) => { 1500 | let name = GoIdentifier::Public { 1501 | name: &name.clone().expect("record to have a name"), 1502 | }; 1503 | let fields = fields.iter().map(|field| { 1504 | ( 1505 | GoIdentifier::Public { name: &field.name }, 1506 | resolve_type(&field.ty, resolve), 1507 | ) 1508 | }); 1509 | 1510 | quote_in! { self.out => 1511 | $['\n'] 1512 | type $name struct { 1513 | $(for (name, typ) in fields join ($['\r']) => $name $typ) 1514 | } 1515 | } 1516 | } 1517 | TypeDefKind::Resource => todo!("TODO(#5): implement resources"), 1518 | TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"), 1519 | TypeDefKind::Flags(_) => todo!("TODO(#4):generate flags type definition"), 1520 | TypeDefKind::Tuple(_) => todo!("TODO(#4):generate tuple type definition"), 1521 | TypeDefKind::Variant(_) => { 1522 | // TODO(#4): Generate aliases if the variant name doesn't match the struct name 1523 | } 1524 | TypeDefKind::Enum(inner) => { 1525 | let name = name.clone().expect("enum to have a name"); 1526 | let enum_type = GoIdentifier::Private { name: &name }; 1527 | 1528 | let enum_interface = GoIdentifier::Public { name: &name }; 1529 | 1530 | let enum_function = GoIdentifier::Private { 1531 | name: &format!("is-{}", &name), 1532 | }; 1533 | 1534 | let variants = inner.cases.iter().map(|variant| GoIdentifier::Public { 1535 | name: &variant.name, 1536 | }); 1537 | 1538 | quote_in! { self.out => 1539 | $['\n'] 1540 | type $enum_interface interface { 1541 | $enum_function() 1542 | } 1543 | 1544 | type $enum_type int 1545 | 1546 | func ($enum_type) $enum_function() {} 1547 | 1548 | const ( 1549 | $(for name in variants join ($['\r']) => $name $enum_type = iota) 1550 | ) 1551 | } 1552 | } 1553 | TypeDefKind::Option(_) => todo!("TODO(#4): generate option type definition"), 1554 | TypeDefKind::Result(_) => todo!("TODO(#4): generate result type definition"), 1555 | TypeDefKind::List(_) => todo!("TODO(#4): generate list type definition"), 1556 | TypeDefKind::Future(_) => todo!("TODO(#4): generate future type definition"), 1557 | TypeDefKind::Stream(_) => todo!("TODO(#4): generate stream type definition"), 1558 | TypeDefKind::Type(Type::Id(_)) => { 1559 | // TODO(#4): Only skip this if we have already generated the type 1560 | } 1561 | TypeDefKind::Type(Type::Bool) => todo!("TODO(#4): generate bool type alias"), 1562 | TypeDefKind::Type(Type::U8) => todo!("TODO(#4): generate u8 type alias"), 1563 | TypeDefKind::Type(Type::U16) => todo!("TODO(#4): generate u16 type alias"), 1564 | TypeDefKind::Type(Type::U32) => todo!("TODO(#4): generate u32 type alias"), 1565 | TypeDefKind::Type(Type::U64) => todo!("TODO(#4): generate u64 type alias"), 1566 | TypeDefKind::Type(Type::S8) => todo!("TODO(#4): generate s8 type alias"), 1567 | TypeDefKind::Type(Type::S16) => todo!("TODO(#4): generate s16 type alias"), 1568 | TypeDefKind::Type(Type::S32) => todo!("TODO(#4): generate s32 type alias"), 1569 | TypeDefKind::Type(Type::S64) => todo!("TODO(#4): generate s64 type alias"), 1570 | TypeDefKind::Type(Type::F32) => todo!("TODO(#4): generate f32 type alias"), 1571 | TypeDefKind::Type(Type::F64) => todo!("TODO(#4): generate f64 type alias"), 1572 | TypeDefKind::Type(Type::Char) => todo!("TODO(#4): generate char type alias"), 1573 | TypeDefKind::Type(Type::String) => { 1574 | let name = GoIdentifier::Public { 1575 | name: &name.clone().expect("string alias to have a name"), 1576 | }; 1577 | // TODO(#4): We might want a Type Definition (newtype) instead of Type Alias here 1578 | quote_in! { self.out => 1579 | $['\n'] 1580 | type $name = string 1581 | } 1582 | } 1583 | TypeDefKind::Type(Type::ErrorContext) => { 1584 | todo!("TODO(#4): generate error context definition") 1585 | } 1586 | TypeDefKind::FixedSizeList(_, _) => { 1587 | todo!("TODO(#4): generate fixed size list definition") 1588 | } 1589 | TypeDefKind::Unknown => panic!("cannot generate Unknown type"), 1590 | } 1591 | } 1592 | } 1593 | 1594 | // `wit_component::decode` uses `root` as an arbitrary name for the primary 1595 | // world name, see 1596 | // 1. https://github.com/bytecodealliance/wasm-tools/blob/585a0bdd8f49fc05d076effaa96e63d97f420578/crates/wit-component/src/decoding.rs#L144-L147 1597 | // 2. https://github.com/bytecodealliance/wasm-tools/issues/1315 1598 | pub const PRIMARY_WORLD_NAME: &str = "root"; 1599 | 1600 | fn main() -> Result { 1601 | let cmd = Command::new("gravity") 1602 | .arg( 1603 | Arg::new("world") 1604 | .short('w') 1605 | .long("world") 1606 | .help("generate host bindings for the specified world") 1607 | .default_value(PRIMARY_WORLD_NAME), 1608 | ) 1609 | .arg( 1610 | Arg::new("inline-wasm") 1611 | .long("inline-wasm") 1612 | .help("include the WebAssembly file as hex bytes in the output code") 1613 | .action(ArgAction::SetTrue), 1614 | ) 1615 | .arg( 1616 | Arg::new("file") 1617 | .help("the WebAssembly file to process") 1618 | .required(true), 1619 | ) 1620 | .arg( 1621 | Arg::new("output") 1622 | .help("the file path where output generated code should be output") 1623 | .short('o') 1624 | .long("output"), 1625 | ); 1626 | 1627 | let matches = cmd.get_matches(); 1628 | let selected_world = matches 1629 | .get_one::("world") 1630 | .expect("should have a world"); 1631 | let file = matches 1632 | .get_one::("file") 1633 | .expect("should have a file"); 1634 | let inline_wasm = matches.get_flag("inline-wasm"); 1635 | let output = matches.get_one::("output"); 1636 | 1637 | // Load the file specified as the `file` arg to clap 1638 | let wasm = match fs::read(file) { 1639 | Ok(wasm) => wasm, 1640 | Err(_) => { 1641 | eprintln!("unable to read file: {file}"); 1642 | return Ok(ExitCode::FAILURE); 1643 | } 1644 | }; 1645 | 1646 | let (module, bindgen) = wit_component::metadata::decode(&wasm) 1647 | // If the Wasm doesn't have a custom section, None will be returned so we need to use the original 1648 | .map(|(module, bindgen)| (module.unwrap_or(wasm), bindgen)) 1649 | .expect("file should be a valid WebAssembly module"); 1650 | 1651 | let wasm_file = &format!("{}.wasm", selected_world.replace('-', "_")); 1652 | 1653 | let raw_wasm = GoIdentifier::Private { 1654 | name: &format!("wasm-file-{selected_world}"), 1655 | }; 1656 | let factory = GoIdentifier::Public { 1657 | name: &format!("{selected_world}-factory"), 1658 | }; 1659 | let new_factory = GoIdentifier::Public { 1660 | name: &format!("new-{selected_world}-factory"), 1661 | }; 1662 | let instance = GoIdentifier::Public { 1663 | name: &format!("{selected_world}-instance"), 1664 | }; 1665 | 1666 | let context = &go::import("context", "Context"); 1667 | let wazero_new_runtime = &go::import("github.com/tetratelabs/wazero", "NewRuntime"); 1668 | let wazero_new_module_config = &go::import("github.com/tetratelabs/wazero", "NewModuleConfig"); 1669 | let wazero_runtime = &go::import("github.com/tetratelabs/wazero", "Runtime"); 1670 | let wazero_compiled_module = &go::import("github.com/tetratelabs/wazero", "CompiledModule"); 1671 | let wazero_api_module = &go::import("github.com/tetratelabs/wazero/api", "Module"); 1672 | let wazero_api_memory = &go::import("github.com/tetratelabs/wazero/api", "Memory"); 1673 | let wazero_api_function = &go::import("github.com/tetratelabs/wazero/api", "Function"); 1674 | 1675 | let mut bindings = Bindings::new(); 1676 | 1677 | if inline_wasm { 1678 | let hex_rows = module 1679 | .chunks(16) 1680 | .map(|bytes| { 1681 | quote! { 1682 | $(for b in bytes join ( ) => $(format!("0x{b:02x},"))) 1683 | } 1684 | }) 1685 | .collect::>>(); 1686 | 1687 | // TODO(#16): Don't use the internal bindings.out field 1688 | quote_in! { bindings.out => 1689 | var $raw_wasm = []byte{ 1690 | $(for row in hex_rows join ($['\r']) => $row) 1691 | } 1692 | }; 1693 | } else { 1694 | // TODO(#16): Don't use the internal bindings.out field 1695 | quote_in! { bindings.out => 1696 | import _ "embed" 1697 | 1698 | $(go_embed(wasm_file)) 1699 | var $raw_wasm []byte 1700 | } 1701 | } 1702 | 1703 | for (_, world) in &bindgen.resolve.worlds { 1704 | if world.name != *selected_world { 1705 | continue; 1706 | } 1707 | 1708 | // TODO(#16): Don't use the internal bindings.out field 1709 | quote_in! { bindings.out => 1710 | $['\n'] 1711 | type $factory struct { 1712 | runtime $wazero_runtime 1713 | module $wazero_compiled_module 1714 | } 1715 | }; 1716 | 1717 | let mut import_fns: BTreeMap> = BTreeMap::new(); 1718 | let mut ifaces = Vec::new(); 1719 | 1720 | for (idx, world_item) in world.imports.values().enumerate() { 1721 | match world_item { 1722 | WorldItem::Interface { id, .. } => { 1723 | let iface = &bindgen.resolve.interfaces[*id]; 1724 | let interface_name = iface.name.clone().expect("TODO"); 1725 | let err = &format!("err{idx}"); 1726 | 1727 | // TOOD: Can this ever be empty? 1728 | let mut import_module_name = String::new(); 1729 | if let Some(package) = iface.package { 1730 | let pkg = &bindgen.resolve.packages[package]; 1731 | import_module_name = format!( 1732 | "{}:{}/{}", 1733 | pkg.name.namespace, pkg.name.name, interface_name 1734 | ) 1735 | } 1736 | 1737 | let import_chain = import_fns.entry(import_module_name.clone()).or_insert( 1738 | quote! { 1739 | _, $err := wazeroRuntime.NewHostModuleBuilder($(quoted(import_module_name))). 1740 | }, 1741 | ); 1742 | 1743 | for typ_id in iface.types.values() { 1744 | let typ_def = bindgen.resolve.types.get(*typ_id).unwrap(); 1745 | bindings.define_type(typ_def, &bindgen.resolve); 1746 | } 1747 | 1748 | let mut interface_funcs = Tokens::new(); 1749 | for func in iface.functions.values() { 1750 | let mut params = Vec::with_capacity(func.params.len()); 1751 | for (name, wit_type) in func.params.iter() { 1752 | let go_type = resolve_type(wit_type, &bindgen.resolve); 1753 | params.push((GoIdentifier::Local { name }, go_type)); 1754 | } 1755 | 1756 | let result = match func.result { 1757 | Some(wit_type) => { 1758 | let go_type = resolve_type(&wit_type, &bindgen.resolve); 1759 | GoResult::Anon(go_type) 1760 | } 1761 | None => GoResult::Empty, 1762 | }; 1763 | 1764 | let func_name = GoIdentifier::Public { name: &func.name }; 1765 | quote_in! { interface_funcs => 1766 | $['\r'] 1767 | $(&func_name)( 1768 | ctx $context, 1769 | $(for (name, typ) in params join ($['\r']) => $(&name) $typ,) 1770 | ) $result 1771 | }; 1772 | } 1773 | let iface_name = GoIdentifier::Public { 1774 | name: &format!("i-{selected_world}-{interface_name}"), 1775 | }; 1776 | ifaces.push(interface_name.clone()); 1777 | 1778 | // TODO(#16): Don't use the internal bindings.out field 1779 | quote_in! { bindings.out => 1780 | $['\n'] 1781 | type $iface_name interface { 1782 | $interface_funcs 1783 | } 1784 | }; 1785 | 1786 | for func in iface.functions.values() { 1787 | let mut sizes = SizeAlign::default(); 1788 | sizes.fill(&bindgen.resolve); 1789 | 1790 | let wasm_sig = bindgen 1791 | .resolve 1792 | .wasm_signature(AbiVariant::GuestImport, func); 1793 | let result = if wasm_sig.results.is_empty() { 1794 | GoResult::Empty 1795 | } else { 1796 | // TODO: Should this instead produce the results based on the wasm_sig? 1797 | match &func.result { 1798 | Some(Type::Bool) => GoResult::Anon(GoType::Uint32), 1799 | Some(Type::Id(typ_id)) => { 1800 | let TypeDef { kind, .. } = 1801 | bindgen.resolve.types.get(*typ_id).unwrap(); 1802 | let go_type = match kind { 1803 | TypeDefKind::Enum(_) => GoType::Uint32, 1804 | _ => todo!("handle Type::Id({typ_id:?})"), 1805 | }; 1806 | GoResult::Anon(go_type) 1807 | } 1808 | Some(wit_type) => todo!("handle {wit_type:?}"), 1809 | None => GoResult::Empty, 1810 | } 1811 | }; 1812 | 1813 | let mut f = Func::import(interface_name.clone(), result, sizes); 1814 | wit_bindgen_core::abi::call( 1815 | &bindgen.resolve, 1816 | AbiVariant::GuestImport, 1817 | LiftLower::LiftArgsLowerResults, 1818 | func, 1819 | &mut f, 1820 | // async is not currently supported 1821 | false, 1822 | ); 1823 | let name = &func.name; 1824 | 1825 | quote_in! { *import_chain => 1826 | $['\r'] 1827 | NewFunctionBuilder(). 1828 | $['\r'] 1829 | WithFunc(func( 1830 | ctx $context, 1831 | mod $wazero_api_module, 1832 | $(for arg in f.args() join ($['\r']) => $arg uint32,) 1833 | ) $(f.result()) { 1834 | $f 1835 | }). 1836 | $['\r'] 1837 | Export($(quoted(name))). 1838 | }; 1839 | } 1840 | 1841 | quote_in! { *import_chain => 1842 | $['\r'] 1843 | Instantiate(ctx) 1844 | $['\r'] 1845 | if $err != nil { 1846 | return nil, $err 1847 | } 1848 | }; 1849 | } 1850 | WorldItem::Function(_) => (), 1851 | WorldItem::Type(id) => { 1852 | let typ_def = bindgen.resolve.types.get(*id).unwrap(); 1853 | bindings.define_type(typ_def, &bindgen.resolve); 1854 | } 1855 | }; 1856 | } 1857 | 1858 | // TODO(#16): Don't use the internal bindings.out field 1859 | quote_in! { bindings.out => 1860 | $['\n'] 1861 | func $new_factory( 1862 | ctx $context, 1863 | $(for interface_name in ifaces.iter() join ($['\r']) => $(GoIdentifier::Local { name: interface_name }) $(GoIdentifier::Public { 1864 | name: &format!("i-{selected_world}-{interface_name}"), 1865 | }),) 1866 | ) (*$factory, error) { 1867 | wazeroRuntime := $wazero_new_runtime(ctx) 1868 | 1869 | $(for import_fn in import_fns.values() join ($['\r']) => $import_fn) 1870 | 1871 | $(comment(&[ 1872 | "Compiling the module takes a LONG time, so we want to do it once and hold", 1873 | "onto it with the Runtime" 1874 | ])) 1875 | module, err := wazeroRuntime.CompileModule(ctx, $raw_wasm) 1876 | if err != nil { 1877 | return nil, err 1878 | } 1879 | 1880 | return &$factory{wazeroRuntime, module}, nil 1881 | } 1882 | 1883 | func (f *$factory) Instantiate(ctx $context) (*$instance, error) { 1884 | if module, err := f.runtime.InstantiateModule(ctx, f.module, $wazero_new_module_config()); err != nil { 1885 | return nil, err 1886 | } else { 1887 | return &$instance{module}, nil 1888 | } 1889 | } 1890 | 1891 | func (f *$factory) Close(ctx $context) { 1892 | f.runtime.Close(ctx) 1893 | } 1894 | }; 1895 | 1896 | // TODO: Only apply helpers like `writeString` if they are needed 1897 | // TODO(#16): Don't use the internal bindings.out field 1898 | quote_in! { bindings.out => 1899 | $['\n'] 1900 | type $instance struct { 1901 | module $wazero_api_module 1902 | } 1903 | 1904 | $(comment(&[ 1905 | "writeString will put a Go string into the Wasm memory following the Component", 1906 | "Model calling convetions, such as allocating memory with the realloc function" 1907 | ])) 1908 | func writeString( 1909 | ctx $context, 1910 | s string, 1911 | memory $wazero_api_memory, 1912 | realloc $wazero_api_function, 1913 | ) (uint64, uint64, error) { 1914 | if len(s) == 0 { 1915 | return 1, 0, nil 1916 | } 1917 | 1918 | results, err := realloc.Call(ctx, 0, 0, 1, uint64(len(s))) 1919 | if err != nil { 1920 | return 1, 0, err 1921 | } 1922 | ptr := results[0] 1923 | ok := memory.Write(uint32(ptr), []byte(s)) 1924 | if !ok { 1925 | return 1, 0, err 1926 | } 1927 | return uint64(ptr), uint64(len(s)), nil 1928 | } 1929 | 1930 | func (i *$instance) Close(ctx $context) error { 1931 | if err := i.module.Close(ctx); err != nil { 1932 | return err 1933 | } 1934 | 1935 | return nil 1936 | } 1937 | }; 1938 | 1939 | for world_item in world.exports.values() { 1940 | match world_item { 1941 | WorldItem::Function(func) => { 1942 | let mut params: Vec<(GoIdentifier<'_>, GoType)> = 1943 | Vec::with_capacity(func.params.len()); 1944 | for (name, wit_type) in func.params.iter() { 1945 | let go_type = resolve_type(wit_type, &bindgen.resolve); 1946 | match go_type { 1947 | // We can't represent this as an argument type so we unwrap the Some type 1948 | // TODO: Figure out a better way to handle this 1949 | GoType::ValueOrOk(typ) => { 1950 | params.push((GoIdentifier::Local { name }, *typ)) 1951 | } 1952 | typ => params.push((GoIdentifier::Local { name }, typ)), 1953 | } 1954 | } 1955 | 1956 | let mut sizes = SizeAlign::default(); 1957 | sizes.fill(&bindgen.resolve); 1958 | 1959 | let result = match &func.result { 1960 | Some(wit_type) => { 1961 | let go_type = resolve_type(wit_type, &bindgen.resolve); 1962 | GoResult::Anon(go_type) 1963 | } 1964 | None => GoResult::Empty, 1965 | }; 1966 | 1967 | let mut f = Func::export(result, sizes); 1968 | wit_bindgen_core::abi::call( 1969 | &bindgen.resolve, 1970 | AbiVariant::GuestExport, 1971 | LiftLower::LowerArgsLiftResults, 1972 | func, 1973 | &mut f, 1974 | // async is not currently supported 1975 | false, 1976 | ); 1977 | 1978 | let arg_assignments = f 1979 | .args() 1980 | .iter() 1981 | .zip(params.iter()) 1982 | .map(|(arg, (param, _))| (arg, param)) 1983 | .collect::>(); 1984 | 1985 | let fn_name = &GoIdentifier::Public { name: &func.name }; 1986 | // TODO(#16): Don't use the internal bindings.out field 1987 | quote_in! { bindings.out => 1988 | $['\n'] 1989 | func (i *$instance) $fn_name( 1990 | $['\r'] 1991 | ctx $context, 1992 | $(for (name, typ) in params.iter() join ($['\r']) => $name $typ,) 1993 | ) $(f.result()) { 1994 | $(for (arg, param) in arg_assignments join ($['\r']) => $arg := $param) 1995 | $f 1996 | } 1997 | }; 1998 | } 1999 | WorldItem::Interface { .. } => (), 2000 | WorldItem::Type(_) => (), 2001 | } 2002 | } 2003 | } 2004 | 2005 | let mut w = genco::fmt::FmtWriter::new(String::new()); 2006 | let fmt = genco::fmt::Config::from_lang::().with_indentation(genco::fmt::Indentation::Tab); 2007 | let config = go::Config::default().with_package(selected_world.replace('-', "_")); 2008 | 2009 | // TODO(#16): Don't use the internal bindings.out field 2010 | bindings 2011 | .out 2012 | .format_file(&mut w.as_formatter(&fmt), &config) 2013 | .unwrap(); 2014 | 2015 | match output { 2016 | Some(outpath) => { 2017 | if !inline_wasm { 2018 | let wasm_outpath = Path::new(outpath).with_file_name(wasm_file); 2019 | match fs::write(&wasm_outpath, module) { 2020 | Ok(_) => (), 2021 | Err(_) => { 2022 | eprintln!("failed to create file: {}", wasm_outpath.to_string_lossy()); 2023 | return Ok(ExitCode::FAILURE); 2024 | } 2025 | } 2026 | } 2027 | match fs::write(outpath, w.into_inner()) { 2028 | Ok(_) => Ok(ExitCode::SUCCESS), 2029 | Err(_) => { 2030 | eprintln!("failed to create file: {outpath}"); 2031 | Ok(ExitCode::FAILURE) 2032 | } 2033 | } 2034 | } 2035 | None => { 2036 | println!("{}", w.into_inner()); 2037 | Ok(ExitCode::SUCCESS) 2038 | } 2039 | } 2040 | } 2041 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | */*.go 2 | !*/*_test.go 3 | */*.wasm 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This example is provided as a guide for using a `wit-bindgen` guest written in 4 | Rust with Gravity. 5 | 6 | ## 1. Add the WebAssembly target 7 | 8 | Gravity doesn't use WebAssembly itself, so you'll want to add the 9 | `wasm32-unknown-unknown` target to your toolchain. 10 | 11 | ```sh 12 | rustup target add wasm32-unknown-unknown 13 | ``` 14 | 15 | ## 2. Build the example to Core Wasm 16 | 17 | Gravity currently needs a "Core Wasm" file with an embedded WIT custom section. 18 | This can be built using `cargo build` using the 19 | `--target wasm32-unknown-unknown` flag. 20 | 21 | ```sh 22 | cargo build -p example-basic --target wasm32-unknown-unknown --release 23 | ``` 24 | 25 | ## 3. Run Gravity against the Core Wasm file 26 | 27 | Gravity can be run against the Wasm file produced in Rust's `target/` directory. 28 | 29 | ```sh 30 | cargo run --bin gravity -- --world basic --output examples/basic/basic.go target/wasm32-unknown-unknown/release/example_basic.wasm 31 | ``` 32 | 33 | ## 4. Use the generate Go & Wasm files 34 | 35 | The above command will produce a `basic.go` and `basic.wasm` file inside the 36 | `examples/basic` directory. These could be used within a Go project. 37 | -------------------------------------------------------------------------------- /examples/basic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-basic" 3 | version = "0.0.2" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | wit-bindgen = "=0.42.1" 11 | wit-component = "=0.230.0" 12 | -------------------------------------------------------------------------------- /examples/basic/basic_test.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "testing" 7 | ) 8 | 9 | type SlogLogger struct{} 10 | 11 | func (s SlogLogger) Debug(ctx context.Context, msg string) { slog.DebugContext(ctx, msg) } 12 | func (s SlogLogger) Info(ctx context.Context, msg string) { slog.InfoContext(ctx, msg) } 13 | func (s SlogLogger) Warn(ctx context.Context, msg string) { slog.WarnContext(ctx, msg) } 14 | func (s SlogLogger) Error(ctx context.Context, msg string) { slog.ErrorContext(ctx, msg) } 15 | 16 | func TestBasic(t *testing.T) { 17 | fac, err := NewBasicFactory(t.Context(), SlogLogger{}) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer fac.Close(t.Context()) 22 | 23 | ins, err := fac.Instantiate(t.Context()) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer ins.Close(t.Context()) 28 | 29 | message, err := ins.Hello(t.Context()) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | const want = "Hello, world!" 35 | if message != want { 36 | t.Errorf("wanted: %s, but got: %s", want, message) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/basic/src/lib.rs: -------------------------------------------------------------------------------- 1 | use arcjet::basic::logger; 2 | 3 | wit_bindgen::generate!({ 4 | world: "basic", 5 | }); 6 | 7 | struct BasicWorld; 8 | 9 | export!(BasicWorld); 10 | 11 | impl Guest for BasicWorld { 12 | fn hello() -> Result { 13 | logger::debug("DEBUG MESSAGE"); 14 | 15 | Ok("Hello, world!".into()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/basic/wit/basic.wit: -------------------------------------------------------------------------------- 1 | package arcjet:basic; 2 | 3 | interface logger { 4 | debug: func(msg: string); 5 | info: func(msg: string); 6 | warn: func(msg: string); 7 | error: func(msg: string); 8 | } 9 | 10 | world basic { 11 | import logger; 12 | 13 | export hello: func() -> result; 14 | } 15 | -------------------------------------------------------------------------------- /examples/generate.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | //go:generate cargo build -p example-basic --target wasm32-unknown-unknown --release 4 | //go:generate cargo build -p example-iface-method-returns-string --target wasm32-unknown-unknown --release 5 | 6 | //go:generate cargo run --bin gravity -- --world basic --output ./basic/basic.go ../target/wasm32-unknown-unknown/release/example_basic.wasm 7 | //go:generate cargo run --bin gravity -- --world example --output ./iface-method-returns-string/example.go ../target/wasm32-unknown-unknown/release/example_iface_method_returns_string.wasm 8 | -------------------------------------------------------------------------------- /examples/iface-method-returns-string/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-iface-method-returns-string" 3 | version = "0.0.2" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | wit-bindgen = "0.42.1" 11 | wit-component = "0.230.0" 12 | -------------------------------------------------------------------------------- /examples/iface-method-returns-string/example_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | type Runtime struct { 11 | msg string 12 | } 13 | 14 | func (Runtime) Os(context.Context) string { return runtime.GOOS } 15 | func (Runtime) Arch(context.Context) string { return runtime.GOARCH } 16 | func (r *Runtime) Puts(_ context.Context, msg string) { r.msg = msg } 17 | 18 | func TestBasic(t *testing.T) { 19 | r := &Runtime{} 20 | fac, err := NewExampleFactory(t.Context(), r) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | defer fac.Close(t.Context()) 25 | 26 | ins, err := fac.Instantiate(t.Context()) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | defer ins.Close(t.Context()) 31 | 32 | message, err := ins.Hello(t.Context()) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | const want = "Hello, world!" 38 | if message != want { 39 | t.Errorf("wanted: %s, but got: %s", want, message) 40 | } 41 | 42 | wantPutsMsg := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) 43 | if r.msg != wantPutsMsg { 44 | t.Errorf("wanted: %s, but got: %s", wantPutsMsg, r.msg) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/iface-method-returns-string/src/lib.rs: -------------------------------------------------------------------------------- 1 | use arcjet::example::runtime; 2 | 3 | wit_bindgen::generate!({ 4 | world: "example", 5 | }); 6 | 7 | struct ExampleWorld; 8 | 9 | export!(ExampleWorld); 10 | 11 | impl Guest for ExampleWorld { 12 | fn hello() -> Result { 13 | runtime::puts(&format!("{}/{}", runtime::os(), runtime::arch())); 14 | 15 | Ok("Hello, world!".into()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/iface-method-returns-string/wit/example.wit: -------------------------------------------------------------------------------- 1 | package arcjet:example; 2 | 3 | interface runtime { 4 | os: func() -> string; 5 | arch: func() -> string; 6 | 7 | puts: func(msg: string); 8 | } 9 | 10 | world example { 11 | import runtime; 12 | 13 | export hello: func() -> result; 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arcjet/gravity 2 | 3 | go 1.24.3 4 | 5 | require github.com/tetratelabs/wazero v1.9.0 6 | 7 | require ( 8 | golang.org/x/mod v0.24.0 // indirect 9 | golang.org/x/sync v0.14.0 // indirect 10 | golang.org/x/tools v0.33.0 // indirect 11 | ) 12 | 13 | tool golang.org/x/tools/cmd/goimports 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= 2 | github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= 3 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 4 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 5 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 6 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 7 | golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= 8 | golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 9 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-01-01" 3 | components = [ 4 | "rustc", 5 | "cargo", 6 | "rustfmt", 7 | "rust-std", 8 | "rust-analyzer", 9 | "clippy", 10 | "rust-src", 11 | ] 12 | targets = [ 13 | "wasm32-unknown-unknown", 14 | "wasm32-wasip1", 15 | ] 16 | profile = "default" 17 | --------------------------------------------------------------------------------