새로운 Smart Contract 프로그래밍 언어 만들기 — VM(1)

김성재
DE-labtory
Published in
7 min readFeb 6, 2019

안녕하세요. KOA 프로젝트에서 VM 컴포넌트를 맡고 있는 김성재입니다. 이번 글을 시작으로해서 약 3번 정도의 포스팅을 예상하고 있는데 그 중 첫 번째 글이되겠습니다. 이번 포스팅에서는 가볍게 VM이 무엇인지, 블록체인에서 어떤 역할을 하고 있는지와 같은 전체적인 구조를 알아가도록 하겠습니다. 또한 Lexer, Parser, Compiler에 대한 내용이 궁금하면 다른 멤버들이 쓴 글을 보시면 될 것 같습니다.

Virtual Machine이란?

Virtual Machine(이하 VM)을 하나로 정의하기에는 여러 의미를 내포하고 있기 때문에 잘 정리된 위키피디아를 참고하겠습니다.

  • System virtual machines (also termed full virtualization VMs) provide a substitute for a real machine. They provide functionality needed to execute entire operating systems. A hypervisor uses native execution to share and manage hardware, allowing for multiple environments which are isolated from one another, yet exist on the same physical machine. Modern hypervisors use hardware-assisted virtualization, virtualization-specific hardware, primarily from the host CPUs.
  • Process virtual machines are designed to execute computer programs in a platform-independent environment.

VM에는 System virtual machineProcess virtual machine이 있다고 합니다. System virtual machine은vmware나 virtual box와 같은 OS내에새로운 가상 OS를 실행시키기 위한 VM이고 Process virtual machine은 platform에 독립적으로 실행 환경을 제공하는 자바 가상머신인 JVM이나 이더리움의 EVM이라고 생각하시면 될 것 같습니다. 그리고 KOA에서 사용하는 VM의 개념은 Process virtual machine 입니다.

KOA Virtual Machine

koa architecture

VM은 Compiler가 만들어낸 ByteCode를 받아서 결과물을 만들어 냅니다. 즉 Compiler가 코드를 컴퓨터 언어로 바꿔주는 것이라면 VM은 그것을 읽어주는 역할입니다.

그렇다면 KOA의 VM 내부적으로는 어떤 일이 일어나게 될까요?

koa vm structure

위의 그림은 KOA VM의 전체적인 구조를 잘 나타낸 그림입니다. KOA VM은 Compiler가 만들어낸 Bytecode를 KOA에 맞는 어셈블 Code로 바꾼 후 그것을 실행시킵니다. 그리고 CallFunc라는 contract 내의 함수 실행 정보를 가지고 Bytecode를 해석하며 Stack Memory를 이용하여 연산을 진행합니다. 각각 하는 역할을 알아보자면 아래와 같습니다.

Stack

Stack은 총 1024개의 item을 쌓을 수 있으며 각 item에는 64bits 크기만큼의 데이터를 저장할 수 있습니다. Stack에는 데이터가 쌓이다가 연산자를 만나게 되면 연산을 하게되는 것이죠. 이해를 위해 예시를 하나 들어보겠습니다.

1 + 2

위와 같은 코드가 Compiler를 거쳐서 Bytecode가 된다면 아래와 같이 변형됩니다.

0x20 0x01 0x20 0x01 0x01

그리고 위의 code는 너무 low level이라서 아래와 같이 Assemble 코드로 바꾸어서 표현하곤 합니다.

PUSH 1 PUSH 2 ADD

위의 코드를 VM은 하나씩 아래와 같이 실행하게 됩니다.

stack

이미지의 순서는 왼쪽 위, 아래, 오른쪽 위, 아래 입니다. PUSH 는 자신 다음에 있는 데이터를 STACK에 넣는 것이고 ADD는 STACK에 있는 두 item을 더하는 예약어 입니다.

이더리움은 PUSH 연산의 종류만 32가지 입니다.

위의 과정을 보면 알 수 있듯이 Stack은 데이터를 저장하는 공간이 아니라 연산을 위한 공간입니다. 데이터를 저장하는 공간은 Memory와 CallFunc가 있습니다.

Memory

Memory변수return value를 관리하는 역할을 합니다. 프로그래머가 변수에 값을 할당하면 그 값은 메모리에 저장되고, return 값 또한 메모리에 저장됩니다. 그리고 저장한 변수를 사용하고 싶을 때 메모리에서 꺼내오게 되는 것이죠.

memory

위의 이미지는 메모리의 구조를 나타내고 있습니다. 메모리는 Stack과 다르게 size의 제한이 없습니다. 그렇기 때문에 string이나 array와 같은 dynamic type의 value들을 저장할 수 있는 것이죠.

메모리에 저장할 때는 type에 알맞게 size가 정해져서 저장되게 됩니다. static type(int, bool 등)일 때에는 Stack의 item과 같이 64bits 크기로 저장이 되고 dynamic type(array, string 등)일 때에는 그 크기에 맞게 size가 조정되어서 저장됩니다.

그래서 메모리에 값을 저장했다가 쓰기 위해서는 저장한 위치(offset)과 value의 size를 기억하고 있어야 합니다.

memory size

그렇다면 KOA를 사용하는 프로그래머는 offset과 size를 다 기억하고 사용해야 할까요? 당연히 아닙니다! 프로그래머는 일반적인 코딩과 같이 변수의 이름만 알고 있으면 됩니다. 위의 과정은 친절한 Compiler가 알아서Bytecode로 변형해 줄것이기 때문입니다!

CallFunc

CallFunc는 프로그래머가 contract를 작성하고나서 contract 내부의 function호출할 때 쓰입니다. 특정 함수를 호출하려면 알아야 하는 정보는 함수의 이름과 함수의 파라미터 정보 입니다.

즉, “난 A라는 함수를 호출할거고 거기에 쓸 파라미터는 a, b, c야” 라는 정보를 CallFunc라는 녀석이 저장하고 있다가 bytecode가 실행되면 해당 정보를 던져주는 것이죠.

이 정보들이 어떤식으로 저장되고 bytecode가 실행될 때는 어떤식으로 알게되고 실행이 될까요? 이 부분은 ABI(Application Binary Interface)를 다루는 다음 포스팅에서 자세히 다루도록 하겠습니다.

이상으로 KOA VM에 대한 개략적인 구조를 살펴보았습니다. VM은 Compiler가 만들어준 Bytecode를 해석하여 결과물을 뱉어주는 역할을 합니다. 그리고 Bytecode를 해석하면서 필요한 것들이 Stack과 Memory, CallFunc 이 있고 이 세가지 자료구조들이 서로 소통을 하면서 VM의 실행을 도와주게 됩니다.

--

--