Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inlined metering #182

Open
axic opened this issue Mar 13, 2019 · 6 comments
Open

Inlined metering #182

axic opened this issue Mar 13, 2019 · 6 comments
Labels

Comments

@axic
Copy link
Member

axic commented Mar 13, 2019

This idea has been independently suggested by @chfast, @poemm, @jwasinger and me.

Currently the metering injection inserts a call to an imported method (ethereum::useGas) at every check, which comes with a large overhead. One potential solution to this is to inline the statements and reduce the amount of external calls being make.

If we assume that code is validated (via the "sentinel contract") prior to metering injection, then we can make use of globals. The proposal is the following:

  • Insert a new global and call it gasLeft
  • Set the value of this global prior to execution to the gas limit of the execution
  • Insert code which decrements this counter and aborts if it is less than 0

Some questions:

  • What is the overhead of inserting the branch at every single metering statement? A potential optimisation is inject a helper function which does the check and call this helper instead of calling the external import.
  • Is it safe to use unreachable for the failure case? Can it be optimised out by engines? Would it be better to insert a call to an imported abort function? (ethereum::abortOutOfGas for example)
@axic axic added the metering label Mar 13, 2019
@jakelang
Copy link
Member

jakelang commented Mar 13, 2019

Another question in a hypothetical metering scheme where a global is used:
The gas logic injected must not only decrement the counter, but trap if OOG.
This implies there must be a helper injected for such logic, lest inlining this logic at every branch inflate the binary size.
What verification must be done to ensure the gas helper is doing what it says? How do we mitigate attack vectors opened if the useGas helper is spoofed by the contract writer?

@chfast
Copy link
Collaborator

chfast commented Mar 13, 2019

Definitely, the prototype of the version with getLeft global variable should be done.

  • Set the value of this global prior to execution to the gas limit of the execution

How? By adding additional code in the main function that copies it from the added param? Or by making the gasLeft exported and then accessible by a wasm engine interface?

  • What is the overhead of inserting the branch at every single metering statement? A potential optimisation is inject a helper function which does the check and call this helper instead of calling the external import.

For compilers predictable branches like this one are quite free. You can also add a compiler hint about branching probability, but I wouldn't go so far at first.
For interpreters, hard to tell, but using a helper function will rather not help. Still would be good to test this variant and compare the code size.

  • Is it safe to use unreachable for the failure case? Can it be optimised out by engines? Would it be better to insert a call to an imported abort function? (ethereum::abortOutOfGas for example)

Personally, my favorite option would be to add ethereum::abort(int) function to EEI. And in this case do ethereum::abort(EVMC_OUT_OF_GAS). The abort() to be a "noreturn" function so the injected code should look like

call $abort
unreachable

Second more complicated option is to return error code from main().

@cdetrio
Copy link
Contributor

cdetrio commented Mar 20, 2019

btw, this is prototyped here ewasm/sentinel-rs#9

@poemm
Copy link
Contributor

poemm commented Mar 20, 2019

Also prototyped here https://github.com/poemm/pywebassembly/blob/master/examples/metering.py . This prototype existed for over a year, but it was cleaned up recently to allow for mutable global exports which landed in the WebAssembly spec this past summer.

Having multiple implementations allows for differential testing. This prototype can also be modified to test other things. Python has been great for prototying and testing.

@recmo
Copy link

recmo commented Aug 31, 2019

What is the overhead of inserting the branch at every single metering statement? A potential optimisation is inject a helper function which does the check and call this helper instead of calling the external import.

No need to check at every branch if you allow the gas to go negative. You only need to make the comparison often enough to avoid overly large negative gas. So maybe check on function entry and once in every unbounded loop. Hot tight loops could be unrolled a couple of times to amortize the check more.

Downside is it will be harder to determine the exact opcode where it went out of gas. Is this information required?

@recmo
Copy link

recmo commented Aug 31, 2019

Is it safe to use unreachable for the failure case? Can it be optimised out by engines?

Wasm-opt will assume unreachable is literally unreachable and can remove every branch that goes there as 'dead code'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants