d-sonuga opened PR #12541 from d-sonuga:custom-operator-cost to bytecodealliance:main:
Closes https://github.com/bytecodealliance/wasmtime/issues/11572.
Adds support for setting custom operator costs per operator.
d-sonuga requested cfallin for a review on PR #12541.
d-sonuga requested wasmtime-compiler-reviewers for a review on PR #12541.
d-sonuga requested wasmtime-core-reviewers for a review on PR #12541.
d-sonuga updated PR #12541.
d-sonuga updated PR #12541.
github-actions[bot] added the label wasmtime:config on PR #12541.
github-actions[bot] added the label wasmtime:api on PR #12541.
github-actions[bot] added the label winch on PR #12541.
github-actions[bot] commented on PR #12541:
Subscribe to Label Action
cc @saulecabrera
<details>
This issue or pull request has been labeled: "wasmtime:api", "wasmtime:config", "winch"Thus the following users have been cc'd because of the following labels:
- saulecabrera: winch
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
github-actions[bot] commented on PR #12541:
Label Messager: wasmtime:config
It looks like you are changing Wasmtime's configuration options. Make sure to
complete this check list:
[ ] If you added a new
Configmethod, you wrote extensive documentation for
it.<details>
Our documentation should be of the following form:
```text
Short, simple summary sentence.More details. These details can be multiple paragraphs. There should be
information about not just the method, but its parameters and results as
well.Is this method fallible? If so, when can it return an error?
Can this method panic? If so, when does it panic?
Example
Optional example here.
```</details>
[ ] If you added a new
Configmethod, or modified an existing one, you
ensured that this configuration is exercised by the fuzz targets.<details>
For example, if you expose a new strategy for allocating the next instance
slot inside the pooling allocator, you should ensure that at least one of our
fuzz targets exercises that new strategy.Often, all that is required of you is to ensure that there is a knob for this
configuration option in [wasmtime_fuzzing::Config][fuzzing-config] (or one
of its nestedstructs).Rarely, this may require authoring a new fuzz target to specifically test this
configuration. See [our docs on fuzzing][fuzzing-docs] for more details.</details>
[ ] If you are enabling a configuration option by default, make sure that it
has been fuzzed for at least two weeks before turning it on by default.[fuzzing-config]: https://github.com/bytecodealliance/wasmtime/blob/ca0e8d0a1d8cefc0496dba2f77a670571d8fdcab/crates/fuzzing/src/generators.rs#L182-L194
[fuzzing-docs]: https://docs.wasmtime.dev/contributing-fuzzing.html
<details>
To modify this label's message, edit the <code>.github/label-messager/wasmtime-config.md</code> file.
To add new label messages or remove existing label messages, edit the
<code>.github/label-messager.json</code> configuration file.</details>
d-sonuga converted PR #12541 Add support for fine-grained operator costs to a draft.
d-sonuga has marked PR #12541 as ready for review.
d-sonuga updated PR #12541.
cfallin submitted PR review:
Thanks for this!
I was initially expecting a boxed function closure as part of the
Configto query for cost on eachOperatorbut the struct-of-costs approach has merit too -- at the very least, it should be faster. It also gives a nice uniformity between the custom and default cases (we just use our default table).One high-level issue with this design is that if one ever needs more custom logic that depends on other context -- e.g., adjust the cost of a
Callbased on what the call target is -- then that is a reasonably straightforward extension with a closure (take another argument with context, or the operand arguments) but not for a table-driven approach. I guess this design must still satisfy your use-case, but I wonder if any others are out there that would need the more generic one?Anyway, I'm happy to land this as-is if it works for you, modulo a few small comments below. Thanks!
cfallin created PR review comment:
It's too bad that we don't have lower-case variants of the opcode names available in
for_each_operator!(we do have thevisit_*forms with lowercased names but not the bare opcodes)I'm a little uncomfortable with the non-idiomatic camelcasing on struct field names here but maybe it's ok to avoid a wasmparser change. cc @alexcrichton for thoughts on this...
cfallin created PR review comment:
I see the value in the detailed error message here but it also seems like a bit too much custom macro use, which we generally try to minimize -- could we instead derive
Eqon theOperatorCoststruct and do a simple structural comparison? Embedders who use this feature can separately print theOperatorCosts in their compilation and execution environments and debug the difference if needed, I think.
d-sonuga updated PR #12541.
alexcrichton submitted PR review:
w.r.t. table-vs-closure, one thought I'd have is that the table approach here is very large at runtime. With 627 opcodes currently and with 8-bytes-per-opcode here that's 5016 bytes for a structure that's an optional feature to move around and manage. That's not the end of the world per se, but it's something I think is worth considering as this is a costly data structure to manage.
This also has the penalty of being serialized into all Wasmtime artifacts, meaning that all
*.cwasms would have this table baked in by-default even if it's not actually used.An upside of tables over closures, however, is that this is more easily serializable. Right now this isn't hooked up to the CLI, but this sort of leaves the door open for eventually doing that.
that is a reasonably straightforward extension with a closure (take another argument with context, or the operand arguments) but not for a table-driven approach
Another possible thing to consider here would be something like
memory.copy. The fuel-based cost of that opcode ideally would account for the size of the copy as opposed to just having a static cost for the opcode.
alexcrichton created PR review comment:
I'm a bit concerned with
i64here as it's a very large value per-opcode (8 bytes per opcode) plus it raises the question of what to do about negative numbers (e.g. should we support negative costs of opcodes?). Couldu8be used here instead?
alexcrichton created PR review comment:
This is a pretty massive struct which would make this structure I think kilobytes large, so perhaps this could be an
Option<Box<OperatorCost>>to reduce its footprint?
alexcrichton created PR review comment:
Yeah there's unfortunately no easy way to get a snake_case version of the fields from the macro currently. I'd say that's generally fine assuming the struct-based approach is chosen (vs a function-based approach)
d-sonuga commented on PR #12541:
Maybe we can have both table & closure approaches: there can be two config options:
static_operator_costwhich takes theOperatorCosttable anddynamic_operator_costwhich takes the operator + whatever context may be needed.So, it can be up to the user to decide which one fits their purpose - if the table based approach is enough, they can simply use that or if they need more flexibility, they can use the dynamic one. Then internally, the operator cost used can be an enumeration:
enum OperatorCostStrategy { Static(Box<OperatorCost>) Dynamic(Box<dyn Fn(&Operator, ctx type) -> u8>) Default }
d-sonuga updated PR #12541.
d-sonuga updated PR #12541.
d-sonuga requested cfallin for a review on PR #12541.
d-sonuga requested alexcrichton for a review on PR #12541.
alexcrichton commented on PR #12541:
Personally I'd say that the current state of the PR is the local maxima to aim for. I think that'll have a reasonable cost when using it and when not using it, and it also doesn't require too much refactoring at this time. A closure-based approach will be tricky from a serializable point of view, but I think it's reasonable to try to cover that later as necessary.
@d-sonuga looks like a merge conflict has snuck in, if you wouldn't mind rebasing I can add this to the merge queue (assuming you don't have anything else you'd like to see @cfallin)
cfallin commented on PR #12541:
Ah, sorry, I lost track of this one; yes, I'm happy with the current state too. Thanks!
cfallin submitted PR review.
d-sonuga updated PR #12541.
cfallin has enabled auto merge for PR #12541.
cfallin has disabled auto merge for PR #12541.
d-sonuga updated PR #12541.
cfallin commented on PR #12541:
@d-sonuga I put your updated prior commit (17597cb) on the merge queue but your subsequent force-push overrode that and canceled the merge. Could you let us know when you're ready for final review and merge?
d-sonuga commented on PR #12541:
I just rebased to remove the conflict and fix a doc comment.
It's ready for the final review and merge now.
cfallin submitted PR review.
cfallin added PR #12541 Add support for fine-grained operator costs to the merge queue.
github-merge-queue[bot] removed PR #12541 Add support for fine-grained operator costs from the merge queue.
d-sonuga updated PR #12541.
d-sonuga commented on PR #12541:
@cfallin, just pushed again to fix the Miri test fail.
alexcrichton added PR #12541 Add support for fine-grained operator costs to the merge queue.
alexcrichton merged PR #12541.
alexcrichton removed PR #12541 Add support for fine-grained operator costs from the merge queue.
Last updated: Feb 24 2026 at 04:36 UTC