컴파일러 출력 분석
컴파일러 출력 분석 (Analysing the Compiler Output)
컴파일러가 생성한 어셈블리 코드를 들여다보는 일은 꽤 유용해요. solc --bin contract.sol 의 출력인 생성된 바이너리는 일반적으로 읽기 어려워요. 어셈블리 출력을 분석하려면 --asm 플래그를 쓰는 걸 권장해요. 큰 컨트랙트에서도 변경 전후의 어셈블리 시각적 차이(diff)를 보면 아주 유익할 때가 많아요.
출처: 문서
본문
컴파일러가 생성한 어셈블리 코드를 살펴보는 것은 자주 유용해요. 생성된 바이너리, 즉 solc --bin contract.sol의 출력은 일반적으로 읽기 어려워요. 어셈블리 출력을 분석하려면 --asm 플래그를 사용하는 걸 권장해요. 큰 컨트랙트에서도 변경 전후의 어셈블리 시각적 diff를 보면 자주 매우 유익해요.
다음 컨트랙트(예를 들어 contract.sol이라고 하죠)를 고려해 볼게요:
open in Remix
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
function one() public pure returns (uint) {
return 1;
}
}
다음은 solc --asm contract.sol의 출력이에요:
======= contract.sol:C =======
EVM assembly:
/* "contract.sol":0:86 contract C {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_1
jumpi
0x00
dup1
revert
tag_1:
pop
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x00
codecopy
0x00
return
stop
sub_0: assembly {
/* "contract.sol":0:86 contract C {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_1
jumpi
0x00
dup1
revert
tag_1:
pop
jumpi(tag_2, lt(calldatasize, 0x04))
shr(0xe0, calldataload(0x00))
dup1
0x901717d1
eq
tag_3
jumpi
tag_2:
0x00
dup1
revert
/* "contract.sol":17:84 function one() public pure returns (uint) {... */
tag_3:
tag_4
tag_5
jump // in
tag_4:
mload(0x40)
tag_6
swap2
swap1
tag_7
jump // in
tag_6:
mload(0x40)
dup1
swap2
sub
swap1
return
tag_5:
/* "contract.sol":53:57 uint */
0x00
/* "contract.sol":76:77 1 */
0x01
/* "contract.sol":69:77 return 1 */
swap1
pop
/* "contract.sol":17:84 function one() public pure returns (uint) {... */
swap1
jump // out
/* "#utility.yul":7:125 */
tag_10:
/* "#utility.yul":94:118 */
tag_12
/* "#utility.yul":112:117 */
dup2
/* "#utility.yul":94:118 */
tag_13
jump // in
tag_12:
/* "#utility.yul":89:92 */
dup3
/* "#utility.yul":82:119 */
mstore
/* "#utility.yul":72:125 */
pop
pop
jump // out
/* "#utility.yul":131:353 */
tag_7:
0x00
/* "#utility.yul":262:264 */
0x20
/* "#utility.yul":251:260 */
dup3
/* "#utility.yul":247:265 */
add
/* "#utility.yul":239:265 */
swap1
pop
/* "#utility.yul":275:346 */
tag_15
/* "#utility.yul":343:344 */
0x00
/* "#utility.yul":332:341 */
dup4
/* "#utility.yul":328:345 */
add
/* "#utility.yul":319:325 */
dup5
/* "#utility.yul":275:346 */
tag_10
jump // in
tag_15:
/* "#utility.yul":229:353 */
swap3
swap2
pop
pop
jump // out
/* "#utility.yul":359:436 */
tag_13:
0x00
/* "#utility.yul":425:430 */
dup2
/* "#utility.yul":414:430 */
swap1
pop
/* "#utility.yul":404:436 */
swap2
swap1
pop
jump // out
auxdata: 0xa2646970667358221220a5874f19737ddd4c5d77ace1619e5160c67b3d4bedac75fce908fed32d98899864736f6c637827302e382e342d646576656c6f702e323032312e332e33302b636f6d6d69742e65613065363933380058
}
대안으로, 위 출력을 Remix에서도 얻을 수 있어요. 컨트랙트를 컴파일한 뒤 "Compilation Details" 옵션 아래에서요.
asm 출력은 생성/생성자(creation/constructor) 코드로 시작한다는 점에 주목해요. 배포 코드(deploy code)는 서브 객체의 일부로 제공돼요(위 예시에서는 서브-객체 sub_0의 일부예요). auxdata 필드는 컨트랙트 메타데이터에 해당해요. 어셈블리 출력의 주석은 소스 위치를 가리켜요. #utility.yul은 유틸리티 함수의 내부 생성 파일이며, --combined-json generated-sources,generated-sources-runtime 플래그를 사용해 얻을 수 있어요.
마찬가지로 최적화된 어셈블리는 solc --optimize --asm contract.sol 명령으로 얻을 수 있어요. Solidity의 서로 다른 두 소스가 같은 최적화 코드를 만들어내는지 보는 것도 자주 흥미로워요. 예를 들어 (a * b) / c와 a * b / c 표현식이 같은 바이트코드를 만드는지 확인하는 식이죠. 이는 소스 위치를 참조하는 주석을 제거한 뒤, 해당 어셈블리 출력의 diff를 떠서 쉽게 확인할 수 있어요.
참고 (Note)
--asm출력은 기계가 읽도록 설계된 것이 아니에요. 따라서 solc의 소수 버전 사이에 출력에 breaking change가 있을 수 있어요.