어셈블

어셈블

어려움

지침

이 연습 문제에서는 x86-64 어셈블리 언어에서 영감을 받은 작은 언어의 문법과 실행 모델을 정의해요.

목표는 제한된 환경 안에서 코드가 어떻게 작성되고, 파싱되고, 실행되는지를 형식화하는 것이에요.

실행 모델

어셈블리 코드는 특별한 구조 안에 작성해요:

let program := assemble!(
    -- assembly code here
)

이 구조는 어셈블리 코드를 실행하고 값을 반환하는 함수를 시뮬레이션하는 프로그램을 만들어야 해요. 이 프로그램은 다음 문법으로 호출하는데, 여기서 a, b, c 등은 인자예요:

let result := program(a, b, c, ...)

모든 인자와 반환값은 _64비트 부호 있는 정수_예요. 인자는 최대 _6개_까지 허용돼요.

레지스터

반환값과 모든 인자는 레지스터로 전달돼요. 여러분의 언어는 다음 레지스터를 지원해야 해요:

레지스터 역할
rdi 첫 번째 인자
rsi 두 번째 인자
rdx 세 번째 인자
rcx 네 번째 인자
r8 다섯 번째 인자
r9 여섯 번째 인자
rax 반환값

레지스터는 함수에 인자를 전달하는 데 사용되지 않는 한 기본값 0으로 시작해요.

예를 들어 다음 프로그램에서 rdi는 10으로, rsi는 -20으로 초기화되고, 이를 제외한 모든 레지스터의 값은 0이에요:

program(10, -20)

프로그램의 반환값은 항상 rax에 저장돼요.

Note

레지스터 이름은 대소문자를 구분하지 않아요. 즉, rax, RAX, rAx는 모두 같은 레지스터를 가리켜요.

어셈블리 코드

어셈블리 코드의 각 줄은 다음 두 가지 형태 중 하나예요:

  • 레이블은 일부 명령어가 사용할 프로그램의 특정 위치를 표시해요.
  • 명령어는 프로그램이 실행하는 연산을 나타내요.

어떤 명령어에 의해 바뀌지 않는 한, 프로그램의 실행 흐름은 선형으로 진행돼요. 즉, 앞 줄이 완전히 실행된 다음에 그다음 줄이 실행돼요.

레이블

레이블의 문법은 다음과 같아요:

<label>:

레이블은 명령어가 사용할 수 있도록 코드의 특정 위치를 표시하는 것 외에는, 어떤 레지스터의 값도 바꾸지 않고 프로그램에도 아무런 영향을 주지 않아요.

Note

레이블은 대소문자를 구분해요. 즉, Start, start, sTart는 모두 서로 다른 레이블이에요.

두 개의 피연산자를 갖는 명령어

대부분의 명령어는 다음과 같은 문법을 가져요:

<opcode> <destination>, <source>

opcode는 destination과 source 피연산자의 값을 사용해 실행할 연산을 나타내요. 연산 결과는 destination 피연산자에 저장돼요.

예를 들어, add 명령어는 destination과 source 피연산자의 값을 더해 그 결과를 destination 피연산자에 저장해요.

destination 피연산자는 항상 레지스터인 반면, source 피연산자는 레지스터일 수도 있고 리터럴 숫자일 수도 있어요.

다음은 여러분의 프로그램이 지원해야 하는 명령어 목록이에요:

opcode 수행하는 연산
mov destination := source
add destination := destination + source
sub destination := destination - source
mul destination := destination * source
div destination := destination / source
xor destination := destination ^^^ source
and destination := destination &&& source
or destination := destination ||| source
shl destination := destination <<< source
shr destination := destination >>> source

이 외에도, 여러분의 프로그램은 두 개의 피연산자를 갖는 cmp 명령어를 지원해야 해요.

이 명령어는 어떤 레지스터도 바꾸지 않고, 대신 destination과 source 피연산자를 비교해 프로그램의 내부 상태를 다음 세 가지 중 하나로 설정해요:

  • destination > source이면 큼
  • destination == source이면 같음
  • destination < source이면 작음

이 상태를 내부적으로 어떻게 관리할지는 자유예요.

하나의 피연산자를 갖는 명령어

프로그램의 흐름을 바꿔 실행을 코드의 다른 지점으로 옮기는 명령어도 있어요. 이런 명령어를 점프 명령어라고 해요.

이 명령어들은 모두 피연산자를 하나만 받는데, 그 피연산자는 점프의 대상, 즉 실행이 이어질 코드의 지점을 나타내는 레이블이에요:

<opcode> <label>

jmp 명령어는 항상 대상 레이블로 점프해요.

다른 점프 명령어들은 프로그램의 내부 상태가 특정 값으로 설정된 경우에만 점프해요. 그래서 보통 cmp 명령어 뒤에 와요.

다음은 여러분의 프로그램이 지원해야 하는 점프 명령어예요:

명령어 수행하는 연산
jmp 무조건 점프해요. 점프는 항상 실행돼요
je 내부 상태가 _같음_이면 점프해요
jl 내부 상태가 _작음_이면 점프해요
jg 내부 상태가 _큼_이면 점프해요
Note

명령어 opcode는 두 개의 피연산자를 갖든 하나의 피연산자를 갖든 대소문자를 구분하지 않아요. 즉, add, ADD, aDd는 모두 같은 명령어를 가리켜요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Lean Exercism

어셈블 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Lean 트랙을 연습 문제 100개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.