arcusbuilds opened PR #14215 from arcusbuilds:fuel-const-expr-operator-cost to bytecodealliance:main:
Closes #14204.
Operators inside constant expressions were each charged a hardcoded 1 fuel unit, so
Config::operator_costhad no effect on any instantiation-time work: global initializers, element and data segment offsets, and element segment expressions. The same hardcoded 1 was used for the synthesized call to a module'sstartfunction inFuncEnvironment::module_start, which manually replicates the accountingfuel_before_opperforms forOperator::Call.Constant expressions are stored as
ConstOprather thanwasmparser::Operator, soOperatorCost::costcannot be called on them. This adds aconst_op_costlookup alongside it, reading the same table, and uses it intranslate_const_expr. The match overConstOphas no catch-all arm, so a future proposal that adds a variant will fail to compile rather than silently charge the wrong cost.module_startsynthesizes a realcall, so it uses the existingcostlookup.Default behavior is unchanged. Every
ConstOpstill costs 1 under the default table, which the existingfuel.wastassertions confirm.Two notes for reviewers:
The issue's "Extra Info" also reports that the
OperatorCost::variableper-element costs are not applied inside const-exprs. That part appears to be incorrect, so this PR does not change it.ConstOp::ArrayNewandConstOp::ArrayNewDefaultdispatch intotranslate_array_newandtranslate_array_new_default, which already callpre_translate_bulk_opwitharray_new_per_elementandarray_new_default_per_element.tests/all/fuel.wastalready asserts 211 for a module whose global initializer is(array.new_default $a (i32.const 100)), and that total only decomposes if the 100 per-element charges are already being applied. Adding the charge intranslate_const_exprwould double it.array.new_fixedhas no per-element variable cost on any path, since its elements are separate operators that are each charged individually.#14203 is a separate bug in the same area: when a module has no
startfunction, the buffered const-expr charges are never flushed intofuel_varand are dropped. It is left for a follow-up. Both new tests here declare astartfunction, which is what forces the flush and makes the charges observable.
Config::operator_costshipped in 48.0.0, so this changes the observable behavior of released public API. Added a note under 49.0.0'sChangedsection perdocs/stability-release.md.Tests:
const_expr_honors_operator_costsetsI32Constto 7 andI32Addto 100 and asserts instantiation consumes 117, with the same module under the default table asserting 6. Two costs rather than one so a mis-wired arm in the new match cannot produce a passing total.module_start_call_honors_operator_costsetsCallto 50 on a module containing only a start function and asserts 52.Both are plain
#[test]rather than#[wasmtime_test]like the neighbouring cost tests. Module startup is compiled by Cranelift under every strategy, so parameterizing over compilers would add variants that exercise the same code.
arcusbuilds requested cfallin for a review on PR #14215.
arcusbuilds requested wasmtime-compiler-reviewers for a review on PR #14215.
arcusbuilds requested wasmtime-core-reviewers for a review on PR #14215.
arcusbuilds requested alexcrichton for a review on PR #14215.
arcusbuilds requested wasmtime-default-reviewers for a review on PR #14215.
arcusbuilds edited PR #14215:
Closes #14204.
Operators in constant expressions were charged a hardcoded 1 fuel unit each, ignoring
Config::operator_cost. That covers everything that runs at instantiation: global initializers, element and data segment offsets, and element segment expressions.FuncEnvironment::module_starthad the same literal for the synthesized call to a module'sstartfunction, where it hand-rolls the accountingfuel_before_opdoes forOperator::Call.Const-exprs are stored as
ConstOp, notwasmparser::Operator, soOperatorCost::costcan't be called on them. I added aconst_op_costlookup that reads the same table, and used it intranslate_const_expr. It has no catch-all arm, so a future proposal that adds aConstOpvariant will fail to compile rather than silently mis-charge it.module_startsynthesizes a realcall, so it just uses the existing lookup.Nothing changes under the default table, where every
ConstOpstill costs 1.Config::operator_costshipped in 48.0.0, so I added a note under 49.0.0'sChangedsection.One thing I deliberately didn't change: the issue also reports that the
variableper-element costs are missing inside const-exprs. I don't think that's right.ConstOp::ArrayNewandArrayNewDefaultdispatch intotranslate_array_newandtranslate_array_new_default, which already callpre_translate_bulk_op.tests/all/fuel.wastasserts 211 for a module whose global initializer is(array.new_default $a (i32.const 100)), and that total only works if those 100 per-element charges are already being applied, so charging again intranslate_const_exprwould double them.array.new_fixedhas no per-element cost on any path, since its elements are separate operators that each get charged.Tests:
const_expr_honors_operator_costsetsI32Constto 7 andI32Addto 100 and expects instantiation to consume 117, with the same module under the default table expecting 6. Two costs rather than one, so a mis-wired match arm can't still add up.module_start_call_honors_operator_costsetsCallto 50 on a module containing only a start function and expects 52. Both modules declare astartfunction because that's what flushes the buffered charges into the counter; without one they're dropped entirely (#14203, which I've left alone).Both are plain
#[test]rather than#[wasmtime_test]like the neighbouring cost tests. Module startup is compiled by Cranelift under every strategy, so the extra variants would exercise the same code.
cfallin commented on PR #14215:
@arcusbuilds can you please confirm that you have followed our AI tools policy, including the point about not ever using LLM output directly in a PR description or any other communications on GitHub?
(I ask because most folks would not write a paragraph about how they wrote a test that adds 7 and 100. If this is a false positive, my apologies.)
Once you have confirmed this and/or made adjustments to conform to our policy, I will then review the PR -- thanks.
github-actions[bot] added the label wasmtime:api on PR #14215.
github-actions[bot] added the label wasmtime:config on PR #14215.
github-actions[bot] added the label wasmtime:docs on PR #14215.
github-actions[bot] commented on PR #14215:
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>
arcusbuilds edited PR #14215:
Closes #14204.
Const-expr operators and the synthesized start call now use the configured operator costs instead of a hardcoded fuel charge of 1. Adds regression tests and a release note.
The issue also reports that variable per-element costs are missing from const-exprs, but those costs are already applied by the shared
array.newtranslation path used by both const-exprs and function bodies. Existing fuel tests confirm this behavior:tests/all/fuel.wast:289asserts 211 fuel for a module whose global initializer is(array.new_default $a (i32.const 100)), and that total only works if the 100 per-element charges are already applied.
arcusbuilds edited PR #14215:
Closes #14204.
Const-expr operators and the synthesized start call now use the configured operator costs instead of a hardcoded fuel charge of 1. Adds regression tests and a release note.
The issue also reports that variable per-element costs are missing from const-exprs, but those costs are already applied by the shared
array.newtranslation path used by both const-exprs and function bodies. Existing fuel tests confirm this behavior:tests/all/fuel.wast:289.
arcusbuilds commented on PR #14215:
@cfallin, I have done the adjustment as per the policy.
:memo: cfallin submitted PR review.
:speech_balloon: cfallin created PR review comment:
Rather than doing this, could we translate to
Operatorand then use the existing cost machinery?
:speech_balloon: cfallin created PR review comment:
No need for the excessively verbose (and LLM-produced) comment here -- anyone wanting to know what the test does can look at it.
:speech_balloon: cfallin created PR review comment:
Likewise here.
arcusbuilds updated PR #14215.
:memo: cfallin submitted PR review:
Almost there! Happy to merge with the below comment addressed.
:speech_balloon: cfallin created PR review comment:
Rather than putting this in
tunables, could we put it near the definition ofConstOp, and make it a method on the type (e.g.const_op.to_operator())?
arcusbuilds updated PR #14215.
:memo: arcusbuilds submitted PR review.
:speech_balloon: arcusbuilds created PR review comment:
done!
:thumbs_up: cfallin submitted PR review.
cfallin added PR #14215 Fuel const expr operator cost to the merge queue.
:check: cfallin merged PR #14215.
cfallin removed PR #14215 Fuel const expr operator cost from the merge queue.
Last updated: Aug 30 2026 at 09:07 UTC