ewasm / design

Ewasm Design Overview and Specification
Apache License 2.0
1.02k stars 125 forks source link

Inlined metering #182

Open axic opened 5 years ago

axic commented 5 years ago

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:

Some questions:

jakelang commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

btw, this is prototyped here https://github.com/ewasm/sentinel-rs/pull/9

poemm commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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'.