4

I have the following simple Solidity contract:

pragma solidity ^0.4.0;
contract Test {

   function Test() {
       intfunc(5);
   }

   uint8 store;

   function intfunc (uint8 a) internal {
       store = a * 9;
   }
}

I am compiling it using Remix, and I'm getting bytecode that I can't explain between addresses 0x11 and 0x1E (I'm including hex addresses on the left for convenience):

//Standard preamble:
0x00: PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH1 0xB JUMPI INVALID

//Beginning of Test() constructor:
0x0B: JUMPDEST JUMPDEST PUSH1 0x20 PUSH1 0x5

//Here's the really strange code:
0x11: PUSH5 0x100000000
0x17: PUSH1 0x7
0x19: PUSH1 0x25
0x1B: DUP3
0x1C: MUL
0x1D: OR
0x1E: DIV

//Here we jump to the intfunc() function
0x1F: JUMP

//Here we come back from intfunc() and jump to rest of the Test() constructor
0x20: JUMPDEST JUMPDEST PUSH1 0x3B JUMP

//intfunc() itself:
0x25: JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH1 0xFF NOT AND PUSH1 0x9 DUP4 MUL PUSH1 0xFF AND OR SWAP1 SSTORE JUMPDEST POP JUMP

//The rest of the Test() constructor and the rest of the code is here...
0x3B: JUMPDEST ...
//(The rest isn't really relevant to this question)

What is the deal with the code between 0x11 and 0x1E - why is it so cumbersome? Can't it just be replaced by a simple "PUSH1 0x25"? Isn't it just a waste of gas to go through all these strange steps just to compute the 0x25 value?

Moreover, where is the number 7 in the 0x17 instruction coming from? It seems completely pointless.

Note that I'm getting similar code for both "optimized" and "unoptimized" Remix modes.

Any insight would be appreciated!

3
  • Interested in the answer to this. Did you ever get any further? :-) Commented May 22, 2017 at 13:08
  • Unfortunately, no Commented May 23, 2017 at 14:40
  • Agreed with all your points! It is very peculiar. You should look at the same code compiled with 0.4.0 of the compiler in Remix: much more concise, and none of this nonsense. It's one of the reasons I'm exploring LLL; there is very little intervention by the compiler. Commented Jul 29, 2017 at 16:55

2 Answers 2

1
+50

This behaviour was introduced in this Github PR. It looks to be related to an optimisation around storing tags for function calls when in the constructor context.

The shift-left by 32 bits MUL 0x0100000000 and OR operations are inserted by function pushCombinedFunctionEntryLabel in file libsolidity/codegen/CompilerUtils.cpp. The subsequent shift-right by 32 bits is inserted by ExpressionCompiler.cpp where it calls rightShiftNumberOnStack.

Probably this is genuinely useful for some circumstances for packing jump labels that need to be stored in contract storage (and thus saving a lot of gas - storage is expensive). In the case of this simple contract it just seems to be an unnecessary remnant.

0

if we compile a simple contract like :

    contract C {

   uint store=45;

    }

we will get (I am using compiler 0.4) :

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value to store 45
07 PUSH1 00 //storage address
09 PUSH1 00 // useless
11 POP      //useless
12 SSTORE

however if we change the uint to uint8 the situation change. we will get instead a longer bytecode :

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value
07 PUSH1 00 //storage address
09 PUSH1 00//mask offset
11 PUSH2 0100// multiplier
14 EXP
15 DUP2
16 SLOAD
17 DUP2
18 PUSH1 ff
20 MUL
21 NOT
22 AND
23 SWAP1
24 DUP4
25 MUL
26 OR
27 SWAP1
28 SSTORE
29 POP

So what's the problem?
when we use uint we use directly a 32byte word, however when we use uint8 we need only the first byte in a storage word to put it alongside other values in a storage slot, so we need to perform some manipulation to avoid any data overwrite.

I think the compiler will pad the 0x2d to 32bytes so the other values in the slot will be overwritten and we will keep only the first byte (2d). To avoid this problem we use sload to load the previous value present on the word and we use bit operations(MUL NOT AND SWAP1 DUP4 MUL OR) to calculate(div and mul used are to shift the values) the new value to be stored in the slot combining the padded value 0x2d00000...00000 and the previous value, at the end we call sstore to save the result.

5
  • This has nothing to do with the jump address calculation in the question. Commented Jul 29, 2017 at 20:29
  • @benjaminion have you read these questions : What is the deal with the code between 0x11 and 0x1E - why is it so cumbersome?Isn't it just a waste of gas to go through all these strange steps just to compute the 0x25 value? Moreover, where is the number 7 in the 0x17 instruction coming from? It seems completely pointless.
    – Badr Bellaj
    Commented Jul 29, 2017 at 20:30
  • Yes of course, and I think my answer covers it. It's nothing to do with uint8 - try compling the OP's code with uint16 or bigger, it doesn't change the behaviour of this jump calculation between 0x11 and 01E. Commented Jul 29, 2017 at 20:38
  • @benjaminion you were right i have rushed in my answer because i thought this was related to uint8 (in old compiler) i didn't notice the opcode addresses. I'll award you the bounty.
    – Badr Bellaj
    Commented Jul 29, 2017 at 21:14
  • Thank you, appreciated. It was a lot of work digging around in the compiler source code and Github to investigate this. It's basically just the remains of a failed optimisation. Commented Jul 30, 2017 at 6:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.