{"id":18016905,"url":"https://github.com/fitzgen/winliner","last_synced_at":"2025-07-03T19:07:19.474Z","repository":{"id":188976287,"uuid":"679798987","full_name":"fitzgen/winliner","owner":"fitzgen","description":"The WebAssembly Indirect Call Inliner","archived":false,"fork":false,"pushed_at":"2023-10-18T20:46:53.000Z","size":244,"stargazers_count":27,"open_issues_count":8,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-07-01T00:09:07.367Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fitzgen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-17T16:38:24.000Z","updated_at":"2025-04-28T00:35:48.000Z","dependencies_parsed_at":"2024-10-30T04:43:52.481Z","dependency_job_id":null,"html_url":"https://github.com/fitzgen/winliner","commit_stats":null,"previous_names":["fitzgen/winliner"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fitzgen/winliner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fwinliner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fwinliner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fwinliner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fwinliner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitzgen","download_url":"https://codeload.github.com/fitzgen/winliner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fwinliner/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263385727,"owners_count":23458744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-30T04:19:38.964Z","updated_at":"2025-07-03T19:07:19.446Z","avatar_url":"https://github.com/fitzgen.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eWinliner\u003c/h1\u003e\n\n  \u003cp\u003e\n    \u003cstrong\u003eThe WebAssembly indirect call inliner!\u003c/strong\u003e\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/fitzgen/winliner/actions?query=workflow%3ACI\"\u003e\u003cimg src=\"https://github.com/fitzgen/winliner/workflows/CI/badge.svg\" alt=\"build status\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/winliner\"\u003e\u003cimg src=\"https://docs.rs/winliner/badge.svg\" alt=\"Documentation Status\" /\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\n  \u003ch3\u003e\n    \u003ca href=\"https://docs.rs/winliner\"\u003eAPI Docs\u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://github.com/fitzgen/winliner/blob/main/CONTRIBUTING.md\"\u003eContributing\u003c/a\u003e\n  \u003c/h3\u003e\n\u003c/div\u003e\n\n* [About](#about)\n* [Install](#install)\n* [Example Usage](#example-usage)\n* [Caveats](#caveats)\n* [Using Winliner as a Library](#using-winliner-as-a-library)\n* [Acknowledgements](#acknowledgements)\n\n## About\n\nWinliner speculatively inlines indirect calls in WebAssembly, based on observed\ninformation from a previous profiling phase. This is a form of [profile-guided\noptimization] that we affectionately call *winlining*.\n\n[profile-guided optimization]: https://en.wikipedia.org/wiki/Profile-guided_optimization\n\nFirst, Winliner inserts instrumentation to observe the actual target callee of\nevery indirect call site in your Wasm program. Next, you run the instrumented\nprogram for a while, building up a profile. Finally, you invoke Winliner again,\nthis time providing it with the recorded profile, and it optimizes your Wasm\nprogram based on the behavior observed in that profile.\n\nFor example, if profiling shows that an indirect call always (or nearly always)\ngoes to the 42nd entry in the funcrefs table, then Winliner will perform the\nfollowing semantically-transparent transformation:\n\n```wat\n;; Before:\n\ncall_indirect\n\n;; After:\n\n;; If the callee index is 42, execute the inlined body of\n;; the associated function.\nlocal.tee $temp\ni32.const 42\ni32.eq\nif\n  \u003cinlined body of table[42] here\u003e\nelse\n  local.get $temp\n  call_indirect\nend\n```\n\nThe speculative inlining by itself is generally not a huge performance win,\nsince CPU indirect branch prediction is very powerful these days. (Although,\ndepending on the Wasm engine, entering a new function may incur some cost and\ninlining does avoid that.) The primary benefit is that it allows the Wasm\ncompiler to \"see through\" the indirect call and perform subsequent optimizations\n(like [GVN] and [LICM]) on the inlined callee's body, which can result in\nsignificant performance benefits.\n\n[GVN]: https://en.wikipedia.org/wiki/Value_numbering#Global_value_numbering\n[LICM]: https://en.wikipedia.org/wiki/Loop-invariant_code_motion\n\nThis technique is similar to *devirtualization* but doesn't require that the\ncompiler is able to statically determine the callee, nor that the callee is\nalways a single, particular function 100% of the time. Unlike devirtualization,\nWinlining can still optimize indirect calls that go a certain way 99% of the\ntime and a different way 1% of the time because it can always fall back to an\nunoptimized indirect call.\n\n## Install\n\nYou can install via `cargo`:\n\n```shell-session\n$ cargo install winliner --all-features\n```\n\n## Example Usage\n\nFirst, instrument your Wasm program:\n\n```shell-session\n$ winliner instrument my-program.wasm \u003e my-program.instrumented.wasm\n```\n\nNext, run the instrumented program to build a profile. This can either be done\nin your Wasm environment of choice (e.g. the Web) with a little glue code to\nextract and shepherd out the profile, or you can run within Winliner itself and\nthe Wasmtime-based WASI environment that comes with it:\n\n```shell-session\n$ winliner profile my-program.instrumented.wasm \u003e profile.json\n```\n\nFinally, tell Winliner to optimize the original program based on the observed\n`call_indirect` behavior observed in the given profile:\n\n```shell-session\n$ winliner optimize --profile profile.json my-program.wasm \u003e my-program.winlined.wasm\n```\n\n## Caveats\n\n* Winliner is not safe in the face of mutations to the `funcref` table, which is\n  possible via the `table.set` instruction (and others) introduced as part of\n  [the reference-types\n  proposal](https://github.com/WebAssembly/reference-types). You must either\n  disable this proposal or manually uphold the invariant that the `funcref`\n  table is never mutated. Breaking this invariant will likely lead to diverging\n  behavior from the original program and very wonky bugs! Any exported funcref\n  tables must additionally not be mutated by the host.\n\n* Winliner only optimizes `call_indirect` instructions; it cannot optimize\n  `call_ref` instructions because WebAssembly function references are not\n  comparable, so we can't insert the `if actual_callee == speculative_callee`\n  check.\n\n* Winliner assumes support for the (widely implemented) multi-value proposal in\n  its generated code.\n\n## Using Winliner as a Library\n\nFirst, add a dependency on Winliner to your `Cargo.toml`:\n\n```toml\n[dependencies]\nwinliner = \"1\"\n```\n\nThen, use the library like so:\n\n```rust,no_run\nuse winliner::{InstrumentationStrategy, Instrumenter, Optimizer, Profile, Result};\n\nfn main() -\u003e Result\u003c()\u003e {\n    let original_wasm = std::fs::read(\"path/to/my.wasm\")?;\n\n    // Configure instrumentation.\n    let mut instrumenter = Instrumenter::new();\n    instrumenter.strategy(InstrumentationStrategy::ThreeGlobals);\n\n    // Instrument our wasm.\n    let instrumented_wasm = instrumenter.instrument(\u0026original_wasm)?;\n\n    // Get a profile for our Wasm program from somewhere. Read it from disk,\n    // record it now in this process, etc...\n    //\n    // See the API docs for `Profile` for more details.\n    let profile = Profile::default();\n\n    // Configure optimization and thresholds for inlining.\n    let mut optimizer = Optimizer::new();\n    optimizer\n        .min_total_calls(100)\n        .min_ratio(0.8)?\n        .max_inline_depth(3);\n\n    // Run the optimizer with the given profile!\n    let optimized_wasm = optimizer.optimize(\u0026profile, \u0026original_wasm)?;\n\n    std::fs::write(\"path/to/optimized.wasm\", optimized_wasm)?;\n    Ok(())\n}\n```\n\n## Acknowledgements\n\nThe inspiration for this tool -- along with the low-overhead but imprecise\n\"three globals\" instrumentation strategy -- sprang from conversations with\n[Chris Fallin] and [Luke Wagner].\n\n[Chris Fallin]: https://github.com/cfallin\n[Luke Wagner]: https://github.com/lukewagner\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fwinliner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitzgen%2Fwinliner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fwinliner/lists"}