매우 기본적인 컴파일러를 작성하는 방법


214

gcc코드가 작성된 언어 (예 : C, C ++ 등)에 따라 코드를 컴퓨터 판독 가능 파일로 컴파일하는 것과 같은 고급 컴파일러 실제로, 해당 언어의 라이브러리 및 기능에 따라 각 코드의 의미를 해석합니다. 틀린 점 있으면 지적 해주세요.

정적 파일 (예 : 텍스트 파일의 Hello World)을 컴파일하기 위해 매우 기본적인 컴파일러 (아마도 C로)를 작성하여 컴파일러를 더 잘 이해하고 싶습니다. 몇 가지 튜토리얼과 책을 시도했지만 모두 실제 사례를위한 것입니다. 해당 언어와 관련된 의미로 동적 코드 컴파일을 처리합니다.

정적 텍스트를 기계가 읽을 수있는 파일로 변환하는 기본 컴파일러를 작성하려면 어떻게해야합니까?

다음 단계는 변수를 컴파일러에 도입하는 것입니다. 언어의 일부 기능 만 컴파일하는 컴파일러를 작성한다고 상상해보십시오.

실용적인 튜토리얼과 자료를 소개합니다 :-)



lex / flex 및 yacc / bison을 사용해 보셨습니까?
mouviciel

15
@mouviciel : 컴파일러를 만드는 방법을 배우는 좋은 방법은 아닙니다. 이러한 도구는 많은 노력을 기울이므로 실제로는 수행하지 않고 수행 방법을 배우지 않습니다.
메이슨 휠러

11
두 번째는 지금의 중복으로 표시되는 동안 흥미롭게 @Mat, 먼저 링크, (404)를 제공 질문입니다.
Ruslan

답변:


326

소개

일반적인 컴파일러는 다음 단계를 수행합니다.

  • 구문 분석 : 소스 텍스트가 추상 구문 트리 (AST)로 변환됩니다.
  • 다른 모듈에 대한 참조 해결 (C는 링크 할 때까지이 단계를 연기합니다).
  • 의미 론적 검증 : 도달 할 수없는 코드 또는 중복 선언과 같이 의미가 맞지 않는 구문 적으로 올바른 문장을 제거합니다.
  • 동등한 변환 및 높은 수준의 최적화 : AST는 동일한 의미로보다 효율적인 계산을 나타내도록 변환됩니다. 여기에는 공통 하위 표현식 및 상수 표현식의 조기 계산, 과도한 로컬 할당 제거 ( SSA 참조 ) 등이 포함됩니다.
  • 코드 생성 : AST는 점프, 레지스터 할당 등을 통해 선형 저수준 코드로 변환됩니다. 이 단계에서 일부 함수 호출을 인라인하고 일부 루프를 풀 수 있습니다.
  • 들여다 보는 구멍 최적화 : 낮은 수준의 코드를 스캔하여 간단한 로컬 비 효율성을 제거합니다.

대부분의 최신 컴파일러 (예 : gcc 및 clang)는 마지막 두 단계를 한 번 더 반복합니다. 초기 코드 생성을 위해 중간 수준의 저급이지만 플랫폼 독립적 인 언어를 사용합니다. 그런 다음 해당 언어는 플랫폼 최적화 방식으로 거의 동일한 작업을 수행하는 플랫폼 별 코드 (x86, ARM 등)로 변환됩니다. 여기에는 가능한 경우 벡터 명령어 사용, 분기 예측 효율을 높이기위한 명령어 순서 변경 등이 포함됩니다.

그런 다음 객체 코드를 연결할 준비가되었습니다. 대부분의 네이티브 코드 컴파일러는 실행 파일을 생성하기 위해 링커를 호출하는 방법을 알고 있지만 컴파일 단계는 아닙니다. Java 및 C #과 같은 언어에서는로드시 VM에 의해 링크가 완전히 동적 일 수 있습니다.

기본 사항 기억

  • 작동하게 만들다
  • 아름답게
  • 효율적으로

이 고전적인 순서는 모든 소프트웨어 개발에 적용되지만 반복됩니다.

순서의 첫 번째 단계에 집중하십시오. 작동 할 수있는 가장 간단한 것을 만듭니다.

책을 읽으십시오!

Aho와 Ullman 의 Dragon Book 을 읽으십시오 . 이것은 고전적이며 오늘날에도 여전히 적용 가능합니다.

현대 컴파일러 디자인 도 칭찬합니다.

이 내용이 지금 너무 어려우면 먼저 구문 분석에 대한 소개를 읽으십시오. 일반적으로 구문 분석 라이브러리에는 소개 및 예제가 포함됩니다.

그래프, 특히 나무로 작업하는 것이 편안해야합니다. 이것들은 프로그램이 논리적 수준에서 만들어진 것들입니다.

언어를 잘 정의하십시오

원하는 표기법을 사용하되, 언어에 대한 완전하고 일관된 설명이 있어야합니다. 여기에는 구문과 의미가 모두 포함됩니다.

미래의 컴파일러를위한 테스트 사례로 새로운 언어로 된 코드 스 니펫을 작성할 때가되었습니다.

좋아하는 언어를 사용하십시오

파이썬이나 루비 또는 쉬운 언어로 컴파일러를 작성해도 괜찮습니다. 잘 알고있는 간단한 알고리즘을 사용하십시오. 첫 번째 버전은 빠르거나 효율적이거나 기능이 완전하지 않아도됩니다. 정확하고 수정하기 만하면됩니다.

필요한 경우 다른 언어의 컴파일러 단계를 다른 언어로 작성해도됩니다.

많은 테스트 작성 준비

전체 언어는 테스트 사례로 다루어야합니다. 효과적으로 그것들에 의해 정의 될 것입니다. 선호하는 테스트 프레임 워크에 익숙해 지십시오. 첫날부터 테스트를 작성하십시오. 잘못된 코드를 감지하는 대신 올바른 코드를 받아들이는 '긍정적 인'테스트에 집중하십시오.

모든 테스트를 정기적으로 실행하십시오. 진행하기 전에 깨진 테스트를 수정하십시오. 유효한 코드를 받아 들일 수없는 잘못 정의 된 언어로 끝나는 것은 부끄러운 일입니다.

좋은 파서 만들기

파서 생성기는 많이 있습니다. 원하는 것을 고르세요. 또한 처음부터 자신의 파서를 쓸 수 있습니다,하지만 그것은 단지 가치가 언어의 구문은 경우 죽은 간단합니다.

파서는 구문 오류를 감지하고보고해야합니다. 긍정적이든 부정적이든 많은 테스트 사례를 작성하십시오. 언어를 정의하면서 작성한 코드를 재사용하십시오.

파서의 출력은 추상 구문 트리입니다.

언어에 모듈이있는 경우 파서의 출력은 생성 한 '객체 코드'의 가장 간단한 표현 일 수 있습니다. 트리를 파일로 덤프하고 빠르게 다시로드하는 방법에는 여러 가지가 있습니다.

시맨틱 유효성 검사기 만들기

대부분의 언어는 특정 상황에서 의미가 없을 수있는 구문 적으로 올바른 구성을 허용합니다. 예를 들어 동일한 변수를 중복 선언하거나 잘못된 유형의 매개 변수를 전달하는 것이 있습니다. 유효성 검사기는 트리를 보면서 이러한 오류를 감지합니다.

또한 유효성 검사기는 사용자 언어로 작성된 다른 모듈에 대한 참조를 확인하고 이러한 다른 모듈을로드 한 다음 유효성 검사 프로세스에 사용합니다. 예를 들어,이 단계는 다른 모듈에서 함수에 전달 된 매개 변수의 수가 올바른지 확인합니다.

다시 말하지만 많은 테스트 사례를 작성하고 실행하십시오. 사소한 경우는 스마트하고 복잡한 문제 해결에 없어서는 안될 필수 요소입니다.

코드 생성

가장 간단한 기술을 사용하십시오. ifHTML 템플릿과 달리 언어 구문 (예 : 명령문)을 매개 변수가 적은 코드 템플릿 으로 직접 변환하는 것이 좋습니다.

다시 한 번 효율성을 무시하고 정확성에 집중하십시오.

플랫폼 독립적 인 저수준 VM을 대상으로 함

하드웨어 관련 세부 사항에 관심이 없다면 저수준 항목을 무시한다고 가정합니다. 이러한 세부 사항은 복잡하고 복잡합니다.

귀하의 옵션 :

  • LLVM : 일반적으로 x86 및 ARM에 대해 효율적인 기계 코드 생성이 가능합니다.
  • CLR : 주로 x86 / Windows 기반의 .NET을 대상으로합니다. 좋은 JIT가 있습니다.
  • JVM : 매우 다중 플랫폼 인 Java 세계를 대상으로 JIT가 우수합니다.

최적화 무시

최적화는 어렵다. 거의 항상 최적화는 시기상조입니다. 비효율적이지만 올바른 코드를 생성하십시오. 결과 코드를 최적화하기 전에 전체 언어를 구현하십시오.

물론 사소한 최적화도 도입 할 수 있습니다. 그러나 컴파일러가 안정되기 전에 교활하고 털이 많은 것을 피하십시오.

그래서 무엇?

이 모든 것이 당신을 너무 위협하지 않으면 계속 진행하십시오! 간단한 언어의 경우 각 단계는 생각보다 간단 할 수 있습니다.

컴파일러가 만든 프로그램에서 'Hello world'를 보는 것이 그만한 가치가 있습니다.


45
이것은 내가 본 최고의 답변 중 하나입니다.
gahooa

11
나는 당신이 질문의 일부를 놓친 것 같아 ... OP는 매우 기본적인 컴파일러 를 작성하고 싶었습니다 . 나는 당신이 여기에서 매우 기본적인 것을 넘어서는 것 같아
marco-fiset

22
@ marco-fiset 는 반대로 OP가 매우 기본적인 컴파일러를 수행하는 방법을 알려주는 동시에 뛰어난 단계를 피하고 정의하기 위해 함정을 지적하는 탁월한 답변이라고 생각합니다.
smci

6
이것은 전체 Exchange Exchange 유니버스에서 본 최고의 답변 중 하나입니다. 명성!
Andre Terra

3
컴파일러가 만든 프로그램에서 'Hello world'를 보는 것이 그만한 가치가 있습니다. - 사실
slier

27

Jack Crenshaw의 Let 's Build a Compiler 는 아직 완성되지 않은 채로 읽을 수있는 소개 및 튜토리얼입니다.

Nicklaus Wirth의 컴파일러 구성 은 간단한 컴파일러 구성의 기본에 대한 훌륭한 교과서입니다. 그는 하향식 재귀 강하에 중점을 두었습니다. 그의 그룹이 작성한 최초의 PASCAL 컴파일러는 이런 식으로 수행되었습니다.

다른 사람들은 다양한 드래곤 책을 언급했습니다.


1
파스칼의 장점 중 하나는 모든 것이 사용되기 전에 정의되거나 선언되어야한다는 것입니다. 따라서 한 번에 컴파일 할 수 있습니다. Turbo Pascal 3.0이 그러한 예 중 하나이며 여기 에 내부에 대한 많은 문서가 있습니다 .
tcrosley

1
PASCAL은 특별히 원 패스 컴파일 및 링크를 염두에두고 설계되었습니다. Wirth의 컴파일러 책은 멀티 패스 컴파일러에 대해 언급하고 있으며 70 (예, 70) 패스를 수행 한 PL / I 컴파일러에 대해 알고 있다고 덧붙였습니다.
John R. Strohm 2016 년

사용 전 필수 선언은 ALGOL로 거슬러 올라갑니다. Tony Hoare는 FORG의 기본 유형 규칙과 유사한 기본 유형 규칙 추가를 제안하려고 할 때 ALGOL위원회에 의해 귀를 돌려 받았습니다. 그들은 이미 발생할 수있는 문제에 대해 알고 있었으며 이름에 인쇄상의 오류가 있었고 흥미로운 규칙을 만드는 기본 규칙이있었습니다.
John R. Strohm 2016 년

1
다음은 원저자가 저술 한 책의 더 업데이트되고 완성 된 버전입니다. stack.nl/~marcov/compiler.pdf 답변을 수정하고 다음을 추가 해주십시오 :)
sonnet

16

실제로 Brainfuck 용 컴파일러를 작성하는 것으로 시작했습니다 . 프로그래밍하기에는 상당히 모호한 언어이지만 구현할 명령은 8 개뿐입니다. 가능한 한 간단하고 구문이 잘못되어있는 경우 관련 명령에 대한 동등한 C 명령어가 있습니다.


7
그러나 BF 컴파일러가 준비되면 코드를 작성해야합니다. (
500-Internal Server Error

@ 500-InternalServerError C 하위 집합 방법을 사용하십시오
World Engineer

12

컴퓨터가 읽을 수있는 코드 만 작성하고 가상 컴퓨터를 대상으로하지 않으려면 Intel 설명서를 읽고 이해해야합니다.

  • 에이. 실행 코드 연결 및로드

  • 비. COFF 및 PE 형식 (Windows 용) 또는 ELF 형식 (Linux 용) 이해

  • 씨. .COM 파일 형식 이해 (PE보다 쉬움)
  • 디. 어셈블러 이해
  • 이자형. 컴파일러의 컴파일러 및 코드 생성 엔진을 이해하십시오.

말한 것보다 훨씬 어렵습니다. C ++에서 컴파일러와 인터프리터를 시작점 (Ronald Mak으로)으로 읽는 것이 좋습니다. 또는 Crenshaw의 "컴파일러 작성"이 정상입니다.

그렇게하지 않으려면 자체 VM을 작성하고 해당 VM을 대상으로하는 코드 생성기를 작성할 수 있습니다.

팁 : Flex와 Bison을 먼저 배우십시오. 그런 다음 자체 컴파일러 / VM을 빌드하십시오.

행운을 빕니다!


7
실제 머신 코드가 아닌 LLVM을 타겟팅하는 것이 오늘날 가장 좋은 방법이라고 생각합니다.
9000

나는 지금 당장 LLVM을 따르고 있으며, 그것을 목표로 삼는 데 필요한 프로그래머의 노력으로 몇 년 동안 본 최고의 것들 중 하나라고 말해야한다!
Aniket Inge

2
무엇 MIPS 및 사용에 대한 스핌 을 실행? 아니면 MIX ?

@MichaelT MIPS를 사용하지는 않았지만 좋을 것이라고 확신합니다.
Aniket Inge

@PrototypeStark RISC 명령어 세트, 오늘날에도 여전히 사용되고있는 실제 프로세서 (내장 시스템으로 번역 가능함을 이해함). 전체 명령어 세트는 wikipedia에 있습니다. 인터넷을 살펴보면 많은 예제가 있으며 많은 학술 수업에서 기계 언어 프로그래밍의 대상으로 사용됩니다. SO 에는 약간의 활동이 있습니다 .

10

간단한 컴파일러에 대한 DIY 접근 방식은 다음과 같습니다 (적어도 내 유니 프로젝트는 다음과 같습니다).

  1. 언어의 문법을 정의하십시오. 문맥이 없습니다.
  2. 문법이 아직 LL (1)이 아닌 경우 지금 수행하십시오. 평범한 CF 문법에서 괜찮게 보이는 일부 규칙은 추악한 것으로 나타날 수 있습니다. 아마도 당신의 언어는 너무 복잡 할 것입니다 ...
  3. 텍스트 스트림을 토큰 (단어, 숫자, 리터럴)으로 자르는 Lexer를 작성하십시오.
  4. 입력을 승인하거나 거부하는 문법에 대해 하향식 재귀 하향식 파서를 작성하십시오.
  5. 구문 분석기에 구문 트리 생성을 추가하십시오.
  6. 구문 트리에서 머신 코드 생성기를 작성하십시오.
  7. Profit & Beer, 또는 더 스마트 한 파서를 수행하거나 더 나은 코드를 생성하는 방법을 생각할 수 있습니다.

각 단계를 자세히 설명하는 문헌이 많이 있어야합니다.


일곱 번째 요점은 OP가 요구하는 것입니다.
Florian Margaine

7
1-5는 관련이 없으며 그러한주의를 기울일 필요가 없습니다. 6이 가장 흥미로운 부분입니다. 불행히도, 악명 높은 용의 책 뒤에는 대부분의 책들이 같은 패턴을 따르고 있습니다.
SK-logic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.