Move SAR, SHR and SHL to UInt256#10216
Conversation
da22999 to
82337b0
Compare
| public static OperationResult staticOperation(final MessageFrame frame) { | ||
| if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; | ||
| frame.setTopV2(StackArithmetic.sar(stack, frame.stackTopV2())); | ||
| long[] _stack = frame.stackDataV2(); |
There was a problem hiding this comment.
I would suggest to just use stack instead of _stack to be inline with the naming used in the project.
| if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; | ||
| frame.setTopV2(StackArithmetic.shl(stack, frame.stackTopV2())); | ||
| long[] _stack = frame.stackDataV2(); |
There was a problem hiding this comment.
I would suggest to just use stack instead of _stack to be inline with the naming used in the project.
| public static OperationResult staticOperation(final MessageFrame frame) { | ||
| if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; | ||
| frame.setTopV2(StackArithmetic.shr(stack, frame.stackTopV2())); | ||
| long[] _stack = frame.stackDataV2(); |
There was a problem hiding this comment.
I would suggest to just use stack instead of _stack to be inline with the naming used in the project.
There was a problem hiding this comment.
Sure, it is a leftover from the previous method argument before I removed it
| || shift.u2() != 0 | ||
| || shift.u1() != 0 | ||
| || Long.compareUnsigned(shift.u0(), 256) >= 0) { | ||
| bytesToShift = 256; |
There was a problem hiding this comment.
I guess this is bitsToShift ?
There was a problem hiding this comment.
We use bitShift in private methods below
There was a problem hiding this comment.
yes it is bits, well spotted
| || shift.u2() != 0 | ||
| || shift.u1() != 0 | ||
| || Long.compareUnsigned(shift.u0(), 256) >= 0) { | ||
| bytesToShift = 256; |
| return new UInt256(w3, w2, w1, w0); | ||
| } | ||
|
|
||
| private static long shiftLeftWord(final long value, final long nextValue, final int bitShift) { |
| return (value << bitShift) | (nextValue >>> (64 - bitShift)); | ||
| } | ||
|
|
||
| private static long shiftRightWord(final long value, final long prevValue, final int bitShift) { |
| final long[] s = new long[8]; | ||
| writeLimbs(s, 0, valueVal); | ||
| writeLimbs(s, 4, shiftVal); | ||
| final UInt256 result = executor.execute(valueVal, shiftVal); |
There was a problem hiding this comment.
👍 (this is a good argument that this design is better)
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
82337b0 to
f4ed77d
Compare
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Looked into it and optimised a little more - but I'm going to park it here. Worst cases (FULL_RANDOM) are much closer or have improved significantly. IMO these are prob the most realistic ones. |
| Arguments.of( | ||
| "0x8000000000000000000000000000000000000000000000000000000000000000", | ||
| "0x100", | ||
| "0x0100", |
There was a problem hiding this comment.
Why do make this change and all the changes below on unit tests ?
There was a problem hiding this comment.
Is it related not using anymore fromHexStringLenient ?
There was a problem hiding this comment.
changed fromHexStringLenient to fromHexString in the test to make the hexadecimal exact without having to guess if there will be a zero or not prepended. Hard to know if you don't know what lenient does. Since we are providing the values hardcoded does it make sense to "disguise" them? For instance 0x0 is half a byte so it seems lenient would put a zero to complete the byte.
I can revert it if you feel strongly about it.
siladu
left a comment
There was a problem hiding this comment.
If we can maintain same performance despite UInt256 allocations then that's fine by me.
Like the reuse between sar and shl, just need to update name and javadoc.
Various minor comments.
| public Operation.OperationResult executeFixedCostOperation( | ||
| final MessageFrame frame, final EVM evm) { | ||
| return staticOperation(frame, frame.stackDataV2()); | ||
| return staticOperation(frame); |
| stack[shiftOffset + 1], | ||
| stack[shiftOffset + 2], | ||
| stack[shiftOffset + 3]); | ||
| final int valueOffset = (--top) << 2; |
There was a problem hiding this comment.
I initially took issue with the mutability of this versus (top - 2) << 2, but warmed to it once I realised
--top === pop
++top === push
which is pretty neat.
| * @param fill value to prepend while shifting | ||
| * @return the result | ||
| */ | ||
| // TODO: check perf - wiring shiftRight callers with this one |
There was a problem hiding this comment.
Since PR is approved, just pointing out there's still a TODO
There was a problem hiding this comment.
I don't want to clutter this PR with that, but also want to track this for changing in a follow up.
| * @param shift number of bits to shift (must be in [1, 255]) | ||
| * @return the result | ||
| */ | ||
| // TODO: check perf - wiring shiftLeft callers with this one |
| } else { | ||
| bitShift = (int) shift.u0(); | ||
| } | ||
| return sar0(bitShift, 0); |
There was a problem hiding this comment.
Seems odd for shr to call a sar method
There was a problem hiding this comment.
why? I think this is elegant. SAR is pretty much a SHR with a custom fill.
There was a problem hiding this comment.
So couldn't you equally call it shr0 ?
sar and shr imply specific things, their shared code is not sar.
There was a problem hiding this comment.
to be clear, I like the structure just not the name
There was a problem hiding this comment.
I think I disagree there, because a shift with a custom fill is an arithmetic shift so it makes sense for SHR to call an internal SAR with a static fill of 0.
There was a problem hiding this comment.
By your description, I think you are saying shl === sar0, but sar0 only makes sense to me when parameter is 0, e.g.
UInt256 sar0(int bitShift) {
return sar(bitShift, 0);
}
The method declaration
private UInt256 sar0(final int shift, final long fill) {
allows custom fill param so if anything should be sar not sar0.
However, I still find this confusing since it isn't the same as SAR opcode. Both SHR and SAR are using it, so better to call it something different IMO.
| * @return the result | ||
| */ | ||
| // TODO: check perf - wiring shiftLeft callers with this one | ||
| private UInt256 shl0(final int shift) { |
There was a problem hiding this comment.
| private UInt256 shl0(final int shift) { | |
| private UInt256 shiftLeft(final int shift) { |
There was a problem hiding this comment.
I believe this clashes with the current shiftLeft method already in place, hence I put the TODO to address this in a follow up. I do what to move onto those names.
There was a problem hiding this comment.
Why do we have two versions of public shiftLeft after this PR?
Is UInt256 now a mix of V1 and V2 variants?
shiftLeftV2 would make more sense if that's the case IMO, or maybe we need UInt256V2 class though would rather not.
I think handling the TODOs in this PR would make things clearer tbh, it's not a large PR.
| * Arithmetic right-shifts a 256-bit value in place by 0..255 bits, sign-extending with {@code | ||
| * fill}. | ||
| * | ||
| * @param shift number of bits to shift (must be in [1, 255]) |
There was a problem hiding this comment.
Think this comment is wrong now - it's handling 0 and 256 as well?
There was a problem hiding this comment.
yeah you're right. That's why I don't like comments in private methods. They frequently change and easily get outdated and compiler does not catch them.
There was a problem hiding this comment.
claude /loop 1 week "check comments still match code" 😆
| bitShift = 256; | ||
| } else { | ||
| bitShift = (int) shift.u0(); | ||
| } |
There was a problem hiding this comment.
Did you consider
| } | |
| } | |
| if (bitShift == 0) return this; |
?
There was a problem hiding this comment.
I believe so - I tried multiple things. These methods are heavily optimized, I would recommend you try and see the results - sometimes it may seem like a fast path but it changes how JIT sees the method.
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
c2b7347 to
23bfc88
Compare
PR description
I was honestly not convinced with the current design of the opcodes on EVM V2 from #10154 so I did some experimentation and I would like to challenge the existing design.
I managed to achieve the same performance level while splitting up duties between the arithmetic/bitwise computations from the opcodes themselves. Opcodes should be the ones fetching/updating the stack, and not the code that does computations - this should be strictly decoupled from one another.
IMO code looks much cleaner and easier to read. It also benefits from code reuse with already existing arithmetics in UInt256. I will take a look at repurposing shl and shr for modulus arithmetics in another PR as well as I believe we might be able to reuse them.
Performance stats:
Issue(s)
#10131
Thanks for sending a pull request! Have you done the following?
doc-change-requiredlabel to this PR if updates are required.Locally, you can run these tests to catch failures early:
./gradlew spotlessApply./gradlew build./gradlew acceptanceTest./gradlew integrationTest./gradlew ethereum:referenceTests:referenceTests