LLVM TableGen 언어의 상세 참조서. 소스 파일, 어휘 분석, 타입, 값과 식, 문장(class/def/let/multiclass/defm 등), DAG, 전처리, 느낌표(bang) 연산자, 붙여넣기(paste) 연산자와 예제를 다룹니다.
URL: https://llvm.org/docs/TableGen/ProgRef.html
Title: 1 TableGen 프로그래머 참고서 — LLVM 22.0.0git 문서
TableGen의 목적은, 출력 파일보다 작성하기 훨씬 쉽고 시간이 지나도 유지·수정이 쉬운 소스 파일의 정보에 기반하여 복잡한 출력 파일을 생성하는 것입니다. 정보는 클래스와 레코드를 포함하는 선언적 스타일로 작성되며, 이후 TableGen이 이를 처리합니다. 내부화된 레코드들은 다양한 백엔드로 전달되며, 백엔드는 레코드의 부분집합에서 정보를 추출해 하나 이상의 출력 파일을 생성합니다. 이 출력 파일들은 보통 C++에서 사용하는 .inc 파일이지만, 백엔드 개발자가 필요로 하는 어떤 형식의 파일도 될 수 있습니다.
이 문서는 LLVM TableGen 기능을 자세히 설명합니다. TableGen을 사용해 프로젝트의 코드를 생성하려는 프로그래머를 대상으로 합니다. 간단한 개요가 필요하다면 TableGen 개요를 참고하세요. TableGen을 호출하는 다양한 *-tblgen 명령은 tblgen 패밀리 - C++ 코드로의 설명에 설명되어 있습니다.
백엔드의 예로 RegisterInfo가 있으며, 이는 특정 타깃 머신의 레지스터 파일 정보를 생성하여 LLVM 타깃-독립 코드 생성기가 사용하도록 합니다. LLVM TableGen 백엔드에 대한 설명은 TableGen 백엔드를, 새로운 백엔드 작성 가이드는 TableGen 백엔드 개발자 안내서를 참고하세요.
다음은 백엔드가 할 수 있는 몇 가지 일입니다.
특정 타깃 머신의 레지스터 파일 정보 생성.
타깃의 명령 정의 생성.
코드 생성기가 명령을 중간 표현(IR) 노드에 매칭하는 데 사용하는 패턴 생성.
Clang용 의미 속성 식별자 생성.
Clang용 추상 구문 트리(AST) 선언 노드 정의 생성.
Clang용 AST 문(statement) 노드 정의 생성.
TableGen 소스 파일에는 두 가지 주요 항목이 있습니다: 추상 레코드와 구체 레코드. 이 문서 및 다른 TableGen 문서에서 추상 레코드는 클래스라고 부릅니다. (이 클래스는 C++ 클래스와 다르며 이에 대응되지 않습니다.) 또한 구체 레코드는 보통 그냥 레코드라고 부르지만, 때로는 레코드라는 용어가 클래스와 구체 레코드 모두를 지칭하기도 합니다. 문맥상 구분이 분명할 것입니다.
클래스와 구체 레코드는 고유한 이름을 가지며, 이는 프로그래머가 선택하거나 TableGen이 생성합니다. 그 이름과 연관되어 값이 있는 필드 목록과 선택적 부모 클래스 목록(기반 또는 슈퍼 클래스라고도 함)이 있습니다. 필드는 백엔드가 처리하는 기본 데이터입니다. TableGen은 필드에 의미를 부여하지 않는다는 점에 유의하세요. 의미는 전적으로 백엔드와 그 출력물을 포함하는 프로그램에 달려 있습니다.
참고
“부모 클래스”라는 용어는 다른 클래스의 부모인 클래스를 가리키기도 하고, 구체 레코드가 상속하는 클래스를 가리키기도 합니다. TableGen이 클래스와 구체 레코드를 유사하게 다루기 때문에 생기는 비표준적인 사용입니다.
백엔드는 TableGen 파서가 구축한 구체 레코드의 부분집합을 처리하고 출력 파일을 생성합니다. 이 파일들은 보통 데이터가 필요한 프로그램이 포함하는 C++ .inc 파일입니다. 하지만 백엔드는 어떤 형식의 출력 파일이든 만들 수 있습니다. 예를 들어 식별자와 치환 매개변수로 태그된 메시지를 포함하는 데이터 파일을 생성할 수도 있습니다. LLVM 코드 생성기 같은 복잡한 사용 사례에서는 많은 구체 레코드가 존재하고, 일부 레코드는 예상치 못할 정도로 많은 필드를 가질 수 있어 큰 출력 파일을 초래합니다.
TableGen 파일의 복잡성을 줄이기 위해 클래스는 레코드 필드 그룹을 추상화하는 데 사용됩니다. 예를 들어 몇몇 클래스는 머신 레지스터 파일의 개념을 추상화하고, 다른 클래스는 명령 포맷을, 또 다른 클래스는 개별 명령을 추상화할 수 있습니다. TableGen은 임의의 클래스 계층 구조를 허용하므로, 두 개념의 추상 클래스가 공통 “하위 개념”을 추상화하는 세 번째 슈퍼클래스를 공유할 수 있습니다.
클래스를 더 유용하게 만들기 위해, 구체 레코드(또는 다른 클래스)는 부모 클래스를 요청하고 템플릿 인수를 전달할 수 있습니다. 이 템플릿 인수는 부모 클래스의 필드에서 사용자 지정 방식으로 초기화하는 데 사용할 수 있습니다. 즉, 레코드 또는 클래스 A는 한 세트의 템플릿 인수로 부모 클래스 S를 요청할 수 있고, 레코드 또는 클래스 B는 다른 세트의 인수로 S를 요청할 수 있습니다. 템플릿 인수가 없으면 각 조합마다 별도의 클래스가 필요해집니다.
클래스와 구체 레코드 모두 초기화되지 않은 필드를 포함할 수 있습니다. 초기화되지 않은 “값”은 물음표(?)로 표시됩니다. 클래스는 종종 구체 레코드가 상속할 때 채워지기를 기대하는 초기화되지 않은 필드를 가집니다. 그럼에도 불구하고 구체 레코드의 일부 필드는 초기화되지 않은 채로 남을 수 있습니다.
TableGen은 여러 레코드 정의를 한곳에 모으는 multiclass를 제공합니다. Multiclass는 한 번에 여러 구체 레코드를 정의하도록 “호출”할 수 있는 일종의 매크로입니다. Multiclass는 다른 multiclass로부터 상속할 수 있으며, 이는 해당 multiclass가 부모 multiclass의 모든 정의를 상속한다는 뜻입니다.
부록 C: 샘플 레코드는 Intel X86 타깃의 복잡한 레코드와 그것이 간단하게 정의되는 방식을 보여줍니다.
TableGen 소스 파일은 평범한 ASCII 텍스트 파일입니다. 파일에는 문장, 주석, 빈 줄이 포함될 수 있습니다(어휘 분석 참조). TableGen 파일의 표준 확장자는 .td입니다.
TableGen 파일은 매우 커질 수 있으므로, 한 파일이 다른 파일의 내용을 포함할 수 있는 include 메커니즘이 있습니다(Include 파일 참조). 이를 통해 큰 파일을 더 작은 파일로 분할할 수 있으며, 여러 소스 파일이 동일한 라이브러리 파일을 포함할 수 있는 간단한 라이브러리 메커니즘도 제공합니다.
TableGen은 .td 파일의 일부를 조건부로 처리할 수 있는 간단한 전처리기를 지원합니다. 자세한 내용은 전처리 기능을 참조하세요.
여기에서 사용하는 어휘 및 구문 표기는 Python의 표기를 모방한 것입니다. 특히 어휘 정의의 경우 생성 규칙은 문자 수준에서 동작하며 요소 사이에 공백이 암시되지 않습니다. 구문 정의는 토큰 수준에서 동작하므로 토큰 사이의 공백이 암시됩니다.
TableGen은 BCPL 스타일 주석(// ...)과 중첩 가능한 C 스타일 주석(/* ... */)을 지원합니다. 또한 간단한 전처리 기능도 제공합니다.
폼피드 문자(formfeed)는 파일 인쇄 검토 시 페이지 구분을 위해 자유롭게 사용할 수 있습니다.
다음은 기본 구두점 토큰입니다:
숫자 리터럴은 다음 형식을 가집니다:
TokInteger ::= DecimalInteger | HexInteger | BinInteger
DecimalInteger ::= ["+" | "-"] ("0"..."9")+
HexInteger ::= "0x" ("0"..."9" | "a"..."f" | "A"..."F")+
BinInteger ::= "0b" ("0" | "1")+
DecimalInteger 토큰은 대부분의 언어와 달리 선택적 + 또는 - 기호를 포함합니다. 대부분의 언어에서는 기호가 단항 연산자로 취급됩니다.
TableGen에는 두 종류의 문자열 리터럴이 있습니다:
TokString ::= '"' (비-" 문자와 이스케이프) '"' TokCode ::= "[{" (텍스트("}]") 미포함) "}]"
TokCode는 단지 [{와 }]로 구분되는 다중 행 문자열 리터럴입니다. 줄을 넘어갈 수 있으며 줄바꿈이 문자열에 그대로 유지됩니다.
현재 구현이 허용하는 이스케이프 시퀀스는 다음과 같습니다:
\ \' " \t \n
TableGen에는 대소문자를 구분하는 이름 및 식별자 유사 토큰이 있습니다.
ualpha ::= "a"..."z" | "A"..."Z" | "_"
TokIdentifier ::= ("0"..."9")* ualpha (ualpha | "0"..."9")*
TokVarName ::= "$" ualpha (ualpha | "0"..."9")*
대부분의 언어와 달리 TableGen은 TokIdentifier가 숫자로 시작하는 것을 허용한다는 점에 주의하세요. 모호한 경우, 토큰은 식별자가 아닌 숫자 리터럴로 해석됩니다.
TableGen에는 다음 예약 키워드가 있으며, 식별자로 사용할 수 없습니다:
assert bit bits class code dag def dump else false foreach defm defset defvar field if in include int let list multiclass string then true
경고
field 예약어는 CodeEmitterGen 백엔드에서 일반 레코드 필드와 인코딩 필드를 구분하는 데 사용하는 경우를 제외하고는 더 이상 사용되지 않습니다.
TableGen은 다양한 용도로 사용할 수 있는 "느낌표 연산자"를 제공합니다:
BangOperator ::= one of !add !and !cast !con !dag !div !empty !eq !exists !filter !find !foldl !foreach !ge !getdagarg !getdagname !getdagop !getdagopname !gt !head !if !initialized !instances !interleave !isa !le !listconcat !listflatten !listremove !listsplat !logtwo !lt !match !mul !ne !not !or !range !repr !setdagarg !setdagname !setdagop !setdagopname !shl !size !sra !srl !strconcat !sub !subst !substr !tail !tolower !toupper !xor
!cond 연산자는 다른 느낌표 연산자와 약간 다른 구문을 가지므로 별도로 정의합니다:
CondOperator ::= !cond
각 느낌표 연산자의 설명은 부록 A: 느낌표 연산자를 참조하세요.
TableGen에는 include 메커니즘이 있습니다. 포함된 파일의 내용은 include 지시어를 어휘적으로 대체하며, 이후 원래 메인 파일에 있었던 것처럼 파싱됩니다.
IncludeDirective ::= "include" TokString
메인 파일과 포함된 파일의 일부는 전처리 지시어를 사용해 조건부로 처리할 수 있습니다.
PreprocessorDirective ::= "#define" | "#ifdef" | "#ifndef"
TableGen 언어는 간단하지만 완전한 타입 시스템을 사용하는 정적 타입 언어입니다. 타입은 오류 검사, 암묵적 변환 수행, 그리고 인터페이스 설계자가 허용되는 입력을 제한하는 데 사용됩니다. 모든 값은 연관된 타입을 가져야 합니다.
TableGen은 저수준 타입(예: bit)과 고수준 타입(예: dag)을 혼합하여 지원합니다. 이러한 유연성 덕분에 다양한 레코드를 편리하고 간결하게 기술할 수 있습니다.
Type ::= "bit" | "int" | "string" | "dag" | "code"
| "bits" "<" TokInteger ">"
| "list" "<" Type ">"
| ClassID
ClassID ::= TokIdentifier
bit
bit은 0 또는 1이 될 수 있는 불리언 값입니다.
int
int 타입은 64비트 정수 값을 나타내며, 예를 들면 5 또는 -42 같은 값입니다.
string
string 타입은 임의 길이의 문자 시퀀스를 나타냅니다.
code
키워드 code는 string의 별칭으로, 코드인 문자열 값을 나타내는 데 사용할 수 있습니다.
bits<n>
bits 타입은 임의 길이 _n_의 고정 크기 정수로, 개별 비트로 취급됩니다. 각 비트에 개별 접근이 가능합니다. 이 타입의 필드는 명령 opcode, 레지스터 번호, 또는 주소 모드/레지스터/디스플레이스먼트를 표현하는 데 유용합니다. 필드의 비트는 개별적으로 또는 하위 필드로 설정될 수 있습니다. 예를 들어 명령 주소에서 주소 모드, 기반 레지스터 번호, 디스플레이스먼트는 각각 별도로 설정할 수 있습니다.
list<type>
이 타입은 꺾쇠괄호 안에 지정한 _type_의 요소로 이루어진 리스트를 나타냅니다. 요소 타입은 임의이며, 다른 리스트 타입일 수도 있습니다. 리스트 요소는 0부터 인덱싱합니다.
dag
이 타입은 중첩 가능한 유향 비순환 그래프(DAG)를 나타냅니다. 각 노드는 연산자(operator)와 0개 이상의 인자(argument 또는 operand)를 가집니다. 인자는 또 다른 dag 객체가 될 수 있어 임의의 트리 형태의 노드/엣지 구조를 만들 수 있습니다. 예로, DAG는 코드 생성기의 명령 선택 알고리즘에서 사용할 코드 패턴을 표현하는 데 사용됩니다. 자세한 내용은 유향 비순환 그래프(DAG)를 참조하세요.
ClassID
타입 문맥에서 클래스 이름을 지정하는 것은 정의된 값의 타입이 해당 클래스의 서브클래스여야 함을 의미합니다. 이는 list 타입과 함께 유용합니다. 예를 들어 리스트 요소를 공통 기반 클래스에 제한하기 위해 사용할 수 있습니다(예: list<Register>는 Register 클래스를 상속한 정의만 포함할 수 있습니다). ClassID는 이전에 선언 또는 정의된 클래스를 지칭해야 합니다.
TableGen 문장에는 값이 필요한 상황이 많습니다. 흔한 예로 레코드 정의에서 각 필드는 이름과 선택적 값으로 지정됩니다. TableGen은 값 식을 구성할 때 합리적인 다양한 형태를 허용합니다. 이러한 형태 덕분에 TableGen 파일을 애플리케이션에 자연스러운 문법으로 작성할 수 있습니다.
모든 값에는 서로 다른 타입 간 변환 규칙이 있다는 점에 유의하세요. 예컨대 7 같은 값을 bits<4> 타입의 엔터티에 할당할 수 있습니다.
Value ::= SimpleValue ValueSuffix*
| Value "#" [Value]
ValueSuffix ::= "{" RangeList "}"
| "[" SliceElements "]"
| "." TokIdentifier
RangeList ::= RangePiece ("," RangePiece)*
RangePiece ::= TokInteger
| TokInteger "..." TokInteger
| TokInteger "-" TokInteger
| TokInteger TokInteger
SliceElements ::= (SliceElement ",")* SliceElement ","?
SliceElement ::= Value
| Value "..." Value
| Value "-" Value
| Value TokInteger
경고
RangePiece와 SliceElement의 마지막 특이한 형태는, “-”가 TokInteger에 포함되기 때문입니다. 따라서 1-5는 “1”, “-”, “5”가 아니라 값이 1과 -5인 연속된 두 토큰으로 어휘 분석됩니다. 범위 구두점으로 하이픈을 사용하는 것은 더 이상 권장되지 않습니다.
SimpleValue에는 여러 형태가 있습니다.
SimpleValue ::= SimpleValue1
| SimpleValue2
| SimpleValue3
| SimpleValue4
| SimpleValue5
| SimpleValue6
| SimpleValue7
| SimpleValue8
| SimpleValue9
SimpleValue1 ::= TokInteger | TokString+ | TokCode
값은 정수 리터럴, 문자열 리터럴, 코드 리터럴이 될 수 있습니다. C/C++처럼 인접한 여러 문자열 리터럴은 연결되며, 단순 값은 이 문자열들의 연결 결과입니다. 코드 리터럴은 문자열이 되며 이후에는 문자열과 구별되지 않습니다.
SimpleValue2 ::= "true" | "false"
true와 false 리터럴은 본질적으로 정수 값 1과 0에 대한 문법적 설탕(syntactic sugar)입니다. 이는 불리언 값이 필드 초기화, 비트 시퀀스, if 문 등에서 사용될 때 TableGen 파일의 가독성을 높여줍니다. 파싱 시 이러한 리터럴은 정수로 변환됩니다.
참고
true와 false가 1과 0의 리터럴 이름이긴 하지만, 스타일 상 불리언 값에만 사용하는 것을 권장합니다.
SimpleValue3 ::= "?"
물음표는 초기화되지 않은 값을 나타냅니다.
SimpleValue4 ::= "{" [ValueList] "}"
ValueList ::= ValueListNE
ValueListNE ::= Value ("," Value)*
이 값은 비트 시퀀스를 나타내며, bits<n> 필드를 초기화하는 데 사용할 수 있습니다(중괄호에 유의). 이때 값들은 합계가 n 비트를 나타내야 합니다.
SimpleValue5 ::= "[" ValueList "]" ["<" Type ">"]
이 값은 리스트 초기화자입니다(대괄호에 유의). 대괄호 안의 값은 리스트의 요소입니다. 선택적 Type은 특정 요소 타입을 나타내는 데 사용할 수 있습니다. 지정되지 않으면 요소 타입은 주어진 값으로부터 추론됩니다. TableGen은 보통 타입을 추론할 수 있지만, 값이 빈 리스트([])인 경우에는 그렇지 않을 수 있습니다.
SimpleValue6 ::= "(" DagArg [DagArgList] ")"
DagArgList ::= DagArg ("," DagArg)*
DagArg ::= Value [":" TokVarName] | TokVarName
이는 DAG 초기화자를 나타냅니다(괄호에 유의). 첫 번째 DagArg는 DAG의 “연산자”라 하며 레코드여야 합니다. 자세한 내용은 유향 비순환 그래프(DAG)를 참조하세요.
SimpleValue7 ::= TokIdentifier
결과 값은 해당 식별자가 명명하는 엔터티의 값입니다. 가능한 식별자는 여기 설명하지만, 이 가이드를 나머지 읽은 후에 더 이해가 잘 될 것입니다.
class의 템플릿 인자. 예: 다음에서 Bar의 사용:class Foo <int Bar> { int Baz = Bar; }
class 또는 multiclass 정의의 암묵적 템플릿 인자 NAME(see NAME).
class에 로컬인 필드. 예: 다음에서 Bar의 사용:
class Foo { int Bar = 5; int Baz = Bar; }
Foo 정의에서 Bar의 사용:def Bar : SomeClass { int X = 5; }
def Foo { SomeClass Baz = Bar; }
Bar의 사용:def Foo { int Bar = 5; int Baz = Bar; } 레코드의 부모 클래스에서 상속된 필드에도 동일한 방식으로 접근할 수 있습니다.
multiclass의 템플릿 인자. 예: 다음에서 Bar의 사용:multiclass Foo <int Bar> { def : SomeClass<Bar>; }
defvar 또는 defset 문으로 정의된 변수.
foreach의 반복 변수. 예: 다음에서 i의 사용:
foreach i = 0...5 in def Foo#i;
SimpleValue8 ::= ClassID "<" ArgValueList ">"
이 형태는 새 익명 레코드 정의를 생성하며(지정된 클래스에서 주어진 템플릿 인수로 상속하는 이름 없는 def가 만드는 것과 동일; def 참조), 그 값은 해당 레코드가 됩니다. 해당 레코드의 필드는 접미를 사용해 얻을 수 있습니다. 접미 값 참조.
이 방식으로 클래스를 호출하면 간단한 서브루틴 기능을 제공할 수 있습니다. 자세한 내용은 서브루틴으로서의 클래스 사용을 참조하세요.
SimpleValue9 ::= BangOperator ["<" Type ">"] "(" ValueListNE ")"
| CondOperator "(" CondClause ("," CondClause)* ")"
CondClause ::= Value ":" Value
느낌표 연산자는 다른 단순 값으로 제공되지 않는 함수를 제공합니다. !cond를 제외하고, 느낌표 연산자는 괄호로 둘러싼 인자 목록을 받고 그 인자에 대해 어떤 함수를 수행한 후 값을 생성합니다. !cond 연산자는 콜론으로 구분된 인자 쌍 목록을 받습니다. 각 느낌표 연산자에 대한 설명은 부록 A: 느낌표 연산자를 참조하세요.
Type은 특정 느낌표 연산자에서만 허용되며, code일 수 없습니다.
위에서 설명한 SimpleValue 값에는 특정 접미를 지정할 수 있습니다. 접미의 목적은 기본 값의 하위 값을 얻는 것입니다. 다음은 어떤 기본 _value_에 대해 가능한 접미들입니다.
value{17}
최종 값은 정수 _value_의 비트 17입니다(중괄호 참고).
value{8...15}
최종 값은 정수 _value_의 비트 8–15입니다. {15...8}로 지정하면 비트 순서를 반대로 할 수 있습니다.
value[i]
최종 값은 리스트 _value_의 요소 i입니다(대괄호 참고). 즉, 대괄호는 리스트에 대한 첨자 연산자로 동작합니다. 단일 요소가 지정된 경우에만 해당합니다.
value[i,]
최종 값은 리스트의 단일 요소 i를 포함하는 리스트입니다. 즉, 단일 요소의 리스트 슬라이스입니다.
value[4...7,17,2...3,4]
최종 값은 리스트 _value_의 슬라이스인 새 리스트입니다. 새 리스트에는 요소 4, 5, 6, 7, 17, 2, 3, 4가 포함됩니다. 요소는 여러 번 포함될 수 있고 임의의 순서가 가능합니다. 둘 이상의 요소가 지정된 경우에만 이런 결과가 나옵니다.
value[i,m...n,j,ls]
각 요소는 식(변수, 느낌표 연산자 등)이 될 수 있습니다. m과 n의 타입은 int여야 합니다. i, j, ls의 타입은 int 또는 list<int>여야 합니다.
value.field
최종 값은 지정된 레코드 _value_의 지정된 _field_의 값입니다.
붙여넣기 연산자(#)는 TableGen 식에서 사용할 수 있는 유일한 중위 연산자입니다. 문자열이나 리스트를 연결할 수 있으나 몇 가지 특이한 특징이 있습니다.
붙여넣기 연산자는 Def 또는 Defm 문에서 레코드 이름을 지정할 때 사용할 수 있으며, 이 경우 반드시 문자열을 구성해야 합니다. 피연산자가 정의되지 않은 이름(TokIdentifier)이거나 전역 Defvar 또는 Defset의 이름이라면, 해당 피연산자는 글자 그대로의 문자열로 취급됩니다. 전역 이름의 값은 사용되지 않습니다.
붙여넣기 연산자는 다른 모든 값 식에서도 사용할 수 있으며, 이 경우 문자열 또는 리스트를 구성할 수 있습니다. 다소 이상하지만 이전 경우와 일관되게, 오른쪽 피연산자가 정의되지 않은 이름 또는 전역 이름이면 글자 그대로의 문자열로 취급됩니다. 왼쪽 피연산자는 정상적으로 취급됩니다.
값이 후행 붙여넣기 연산자를 가질 수 있으며, 이 경우 왼쪽 피연산자는 빈 문자열에 연결됩니다.
붙여넣기 연산자의 동작 예시는 부록 B: 붙여넣기 연산자 예시를 참고하세요.
다음 문장들이 TableGen 소스 파일의 최상위에 나타날 수 있습니다.
TableGenFile ::= (Statement | IncludeDirective
| PreprocessorDirective)*
Statement ::= Assert | Class | Def | Defm | Defset | Deftype
| Defvar | Dump | Foreach | If | Let | MultiClass
다음 절에서는 이러한 최상위 문장 각각을 설명합니다.
class — 추상 레코드 클래스 정의¶class 문은 다른 클래스와 레코드가 상속할 수 있는 추상 레코드 클래스를 정의합니다.
Class ::= "class" ClassID [TemplateArgList] RecordBody
TemplateArgList ::= "<" TemplateArgDecl ("," TemplateArgDecl)* ">"
TemplateArgDecl ::= Type TokIdentifier ["=" Value]
클래스는 “템플릿 인자” 목록으로 매개변수화할 수 있으며, 그 값은 클래스의 레코드 본문에서 사용할 수 있습니다. 이 템플릿 인자는 클래스가 다른 클래스나 레코드에 의해 상속될 때마다 지정됩니다.
템플릿 인자가 =로 기본값을 할당받지 않으면 초기화되지 않은 상태(“값”이 ?)이며, 해당 클래스가 상속될 때 템플릿 인자 목록에서 반드시 지정해야 합니다(필수 인자). 인자가 기본값을 할당받으면 인자 목록에서 지정할 필요가 없습니다(선택 인자). 선언에서 모든 필수 템플릿 인자는 선택 인자보다 앞에 와야 합니다. 템플릿 인자의 기본값은 왼쪽에서 오른쪽 순으로 평가됩니다.
RecordBody는 아래에서 정의됩니다. 이는 현재 클래스가 상속하는 부모 클래스 목록과 필드 정의 및 기타 문장을 포함할 수 있습니다. 클래스 C가 다른 클래스 D를 상속하면, D의 필드는 효과적으로 C의 필드에 병합됩니다.
주어진 클래스는 한 번만 정의할 수 있습니다. 다음 조건 중 하나라도 참이면 class 문은 해당 클래스를 정의하는 것으로 간주됩니다(RecordBody 요소는 아래에서 설명합니다).
TemplateArgList가 존재하거나,
RecordBody의 ParentClassList가 존재하거나,
RecordBody의 Body가 존재하고 비어 있지 않은 경우.
빈 TemplateArgList와 빈 RecordBody를 지정하여 빈 클래스를 선언할 수 있습니다. 이는 제한된 형태의 전방 선언(forward declaration) 역할을 할 수 있습니다. 전방 선언된 클래스로부터 파생된 레코드는 해당 클래스로부터 어떤 필드도 상속하지 않는다는 점에 유의하세요. 왜냐하면 그 레코드들은 선언이 파싱될 때 생성되므로, 클래스가 최종적으로 정의되기 전이기 때문입니다.
모든 클래스에는 NAME(대문자)이라는 암묵적 템플릿 인자가 있으며, 이는 클래스에서 상속하는 Def 또는 Defm의 이름에 바인딩됩니다. 클래스가 익명 레코드에 의해 상속되는 경우, 이름은 지정되지 않지만 전역적으로 고유합니다.
예시는 예시: 클래스와 레코드를 참조하세요.
레코드 본문은 클래스와 레코드 정의 모두에 나타납니다. 레코드 본문에는 현재 클래스나 레코드가 필드를 상속하는 클래스들을 지정하는 부모 클래스 목록이 포함될 수 있습니다. 이러한 클래스는 해당 클래스나 레코드의 부모 클래스라고 합니다. 레코드 본문에는 또한 정의의 메인 본문이 포함되며, 클래스나 레코드의 필드 지정이 들어 있습니다.
RecordBody ::= ParentClassList Body
ParentClassList ::= [":" ParentClassListNE]
ParentClassListNE ::= ClassRef ("," ClassRef)*
ClassRef ::= (ClassID | MultiClassID) ["<" [ArgValueList] ">"]
ArgValueList ::= PostionalArgValueList [","] NamedArgValueList
PostionalArgValueList ::= [Value {"," Value}]
NamedArgValueList ::= [NameValue "=" Value {"," NameValue "=" Value}]
MultiClassID를 포함하는 ParentClassList는 defm 문의 클래스 목록에서만 유효합니다. 이 경우 ID는 multiclass의 이름이어야 합니다.
인자 값은 두 가지 형태로 지정할 수 있습니다:
위치 기반 인자(value). 값은 해당 위치의 인자에 할당됩니다. Foo<a0, a1>의 경우 a0는 첫 번째 인자에, a1은 두 번째 인자에 할당됩니다.
이름 기반 인자(name=value). 값은 지정된 이름의 인자에 할당됩니다. Foo<a=a0, b=a1>의 경우 a0는 이름 a인 인자에, a1은 이름 b인 인자에 할당됩니다.
필수 인자도 이름 기반 인자로 지정할 수 있습니다.
인자는 지정 방식(이름 또는 위치)과 관계없이 한 번만 지정할 수 있으며, 위치 기반 인자는 이름 기반 인자보다 앞에 와야 합니다.
Body ::= ";" | "{" BodyItem* "}"
BodyItem ::= Type TokIdentifier ["=" Value] ";"
| "let" TokIdentifier ["{" RangeList "}"] "=" Value ";"
| "defvar" TokIdentifier "=" Value ";"
| Assert
본문의 필드 정의는 클래스나 레코드에 포함될 필드를 지정합니다. 초기값을 지정하지 않으면 해당 필드의 값은 초기화되지 않은 상태가 됩니다. 타입은 반드시 지정해야 합니다. TableGen은 값을 근거로 타입을 추론하지 않습니다.
let 형태는 필드를 새 값으로 재설정하는 데 사용됩니다. 이는 본문에서 직접 정의된 필드나 부모 클래스에서 상속받은 필드 모두에 대해 가능합니다. bit<n> 필드의 특정 비트를 재설정하기 위해 RangeList를 지정할 수 있습니다.
defvar 형태는 본문 내 다른 값 식에서 사용할 수 있는 변수를 정의합니다. 이 변수는 필드가 아닙니다. 즉, 정의 중인 클래스나 레코드의 필드가 되지 않습니다. 변수는 본문 처리 중 임시 값을 저장하기 위해 제공됩니다. 자세한 내용은 레코드 본문에서의 Defvar를 참조하세요.
클래스 C2가 클래스 C1을 상속하면 C1의 모든 필드 정의를 획득합니다. 이러한 정의가 클래스 C2로 병합되는 동안, C2가 C1에 전달한 템플릿 인자들이 해당 정의에 치환됩니다. 즉, C1이 정의한 추상 레코드 필드는 C2로 병합되기 전에 템플릿 인자로 확장됩니다.
def — 구체 레코드 정의¶def 문은 새로운 구체 레코드를 정의합니다.
Def ::= "def" [NameValue] RecordBody
NameValue ::= Value (특수 모드로 파싱)
이름 값은 선택 사항입니다. 지정된 경우, 정의되지 않은(인식되지 않는) 식별자를 리터럴 문자열로 해석하는 특수 모드로 파싱됩니다. 특히 전역 식별자는 인식되지 않는 것으로 간주됩니다. 여기에는 defvar와 defset으로 정의된 전역 변수가 포함됩니다. 레코드 이름은 빈 문자열일 수 있습니다.
이름 값이 주어지지 않으면 해당 레코드는 익명입니다. 익명 레코드의 최종 이름은 지정되지 않지만 전역적으로 고유합니다.
def가 multiclass 문 내부에 나타나는 경우 특수 처리가 이루어집니다. 자세한 내용은 아래 multiclass 절을 참조하세요.
레코드는 레코드 본문 시작 부분에 ParentClassList 절을 지정하여 하나 이상의 클래스로부터 상속할 수 있습니다. 부모 클래스의 모든 필드는 레코드에 추가됩니다. 두 개 이상의 부모 클래스가 동일한 필드를 제공하면, 레코드는 마지막 부모 클래스의 필드 값으로 끝납니다.
특별한 경우로, 레코드의 이름을 해당 레코드의 부모 클래스에 템플릿 인자로 전달할 수 있습니다. 예:
class A <dag d> { dag the_dag = d; }
def rec1 : A<(ops rec1)>;
DAG (ops rec1)는 클래스 A에 템플릿 인자로 전달됩니다. DAG에는 현재 정의 중인 레코드 rec1이 포함되어 있음을 주목하세요.
새 레코드를 만드는 단계는 다소 복잡합니다. 레코드가 생성되는 방식을 참조하세요.
예시는 예시: 클래스와 레코드를 참고하세요.
다음은 하나의 클래스와 두 개의 레코드 정의를 포함하는 간단한 TableGen 파일입니다.
class C { bit V = true; }
def X : C; def Y : C { let V = false; string Greeting = "Hello!"; }
먼저 추상 클래스 C가 정의됩니다. 이 클래스는 참으로 초기화된 비트 타입의 V라는 필드를 하나 가집니다.
다음으로 클래스 C를 부모 클래스로 하여 두 개의 레코드가 정의됩니다. 즉, 이 둘은 V 필드를 상속합니다. 레코드 Y는 또 다른 문자열 필드 Greeting을 정의하며, 초기값은 "Hello!"입니다. 또한 Y는 상속된 V 필드를 false로 재정의합니다.
클래스는 여러 레코드 간 공통 특성을 한곳에 모으는 데 유용합니다. 클래스는 공통 필드를 기본값으로 초기화할 수 있으며, 해당 클래스를 상속하는 레코드는 기본값을 재정의할 수 있습니다.
TableGen은 매개변수화된 클래스와 비매개변수화된 클래스 정의를 모두 지원합니다. 매개변수화된 클래스는 클래스가 다른 클래스나 레코드의 부모 클래스로 지정될 때 바인딩되는 변수 선언 목록을 지정하며, 변수는 기본값을 가질 수 있습니다.
class FPFormat <bits<3> val> { bits<3> Value = val; }
def NotFP : FPFormat<0>; def ZeroArgFP : FPFormat<1>; def OneArgFP : FPFormat<2>; def OneArgFPRW : FPFormat<3>; def TwoArgFP : FPFormat<4>; def CompareFP : FPFormat<5>; def CondMovFP : FPFormat<6>; def SpecialFP : FPFormat<7>;
FPFormat 클래스의 목적은 일종의 열거형 타입 역할을 하는 것입니다. 이 클래스는 3비트 숫자를 담는 단일 필드 Value를 제공합니다. 템플릿 인자 val은 Value 필드를 설정하는 데 사용됩니다. 8개의 각각의 레코드는 FPFormat을 부모 클래스로 정의됩니다. 열거값은 꺾쇠괄호로 된 템플릿 인자로 전달됩니다. 각 레코드는 적절한 열거값을 가진 Value 필드를 상속합니다.
다음은 템플릿 인자를 가진 더 복잡한 클래스 예시입니다. 먼저 위의 FPFormat과 유사한 클래스를 정의합니다. 템플릿 인자를 받아 Value라는 필드를 초기화합니다. 그런 다음 네 개의 레코드를 정의하여 Value 필드를 네 가지 서로 다른 정수 값으로 상속합니다.
class ModRefVal <bits<2> val> { bits<2> Value = val; }
def None : ModRefVal<0>; def Mod : ModRefVal<1>; def Ref : ModRefVal<2>; def ModRef : ModRefVal<3>;
다소 억지스러울 수 있지만, Value 필드의 두 비트를 독립적으로 살펴보고자 한다고 가정해 봅시다. ModRefVal 레코드를 템플릿 인자로 받아 그 값을 두 개의 필드로 나누는 클래스를 정의할 수 있습니다. 그런 다음 ModRefBits를 상속하는 레코드를 정의하여 템플릿 인자로 전달된 ModRefVal 레코드의 각 비트에 해당하는 두 개의 필드를 획득합니다.
class ModRefBits <ModRefVal mrv> { // 값을 비트로 나눠, ModRefVal 값에 대한 좋은 인터페이스를 제공합니다. bit isMod = mrv.Value{0}; bit isRef = mrv.Value{1}; }
// 예시 사용. def foo : ModRefBits<Mod>; def bar : ModRefBits<Ref>; def snork : ModRefBits<ModRef>;
이는 한 클래스가 다른 클래스의 필드를 재구성하여 그 내부 표현을 숨기는 방법을 보여줍니다.
이 예시에 대해 llvm-tblgen을 실행하면 다음과 같은 정의가 출력됩니다:
def bar { // Value bit isMod = 0; bit isRef = 1; } def foo { // Value bit isMod = 1; bit isRef = 0; } def snork { // Value bit isMod = 1; bit isRef = 1; }
let — 클래스나 레코드의 필드 재정의¶let 문은 필드 값(바인딩이라고도 함)의 집합을 모아 let의 범위에 있는 문장으로 정의되는 모든 클래스와 레코드에 적용합니다.
Let ::= "let" LetList "in" "{" Statement* "}"
| "let" LetList "in" Statement
LetList ::= LetItem ("," LetItem)*
LetItem ::= TokIdentifier ["<" RangeList ">"] "=" Value
let 문은 중괄호 안의 문장 시퀀스 또는 중괄호 없는 단일 문장으로서의 범위를 설정합니다. LetList의 바인딩은 해당 범위의 문장에 적용됩니다.
LetList의 필드 이름은 해당 범위에서 정의되는 클래스와 레코드가 상속하는 클래스의 필드를 가리켜야 합니다. 필드 값은 레코드가 부모 클래스로부터 모든 필드를 상속받은 후에 적용됩니다. 따라서 let은 상속된 필드 값을 재정의하는 역할을 합니다. let은 템플릿 인자의 값을 재정의할 수 없습니다.
최상위 let 문은 여러 레코드에서 몇몇 필드를 재정의할 필요가 있을 때 유용합니다. 다음은 두 가지 예시입니다. let 문은 중첩될 수 있다는 점에 유의하세요.
let isTerminator = true, isReturn = true, isBarrier = true, hasCtrlDep = true in def RET : I<0xC3, RawFrm, (outs), (ins), "ret", [(X86retflag 0)]>;
let isCall = true in // 모든 호출은 callee-saved가 아닌 레지스터들을 망가뜨립니다... let Defs = [EAX, ECX, EDX, FP0, FP1, FP2, FP3, FP4, FP5, FP6, ST0, MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7, XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, EFLAGS] in { def CALLpcrel32 : Ii32<0xE8, RawFrm, (outs), (ins i32imm:$dst, variable_ops), "call\t${dst:call}", []>; def CALL32r : I<0xFF, MRM2r, (outs), (ins GR32:$dst, variable_ops), "call\t{}$dst", [(X86call GR32:$dst)]>; def CALL32m : I<0xFF, MRM2m, (outs), (ins i32mem:$dst, variable_ops), "call\t{}$dst", []>; }
최상위 let은 클래스나 레코드 자체에서 정의된 필드를 재정의하지 않는다는 점에 유의하세요.
multiclass — 다중 레코드 정의¶템플릿 인자를 가진 클래스는 여러 레코드 사이의 공통성을 분해하는 좋은 방법이지만, multiclass는 한 번에 많은 레코드를 정의하는 편리한 수단을 제공합니다. 예를 들어, 명령이 두 가지 포맷으로 나오는 3-주소 명령 아키텍처를 생각해 봅시다:
reg = reg
op reg
그리고 reg = reg op imm (예: SPARC). 한 곳에서 이 두 가지 공통 포맷이 존재함을 지정하고, 별도의 곳에서 어떤 연산들이 있는지 지정하고자 합니다. multiclass와 defm 문이 이 목표를 달성합니다. Multiclass는 여러 레코드로 확장되는 매크로나 템플릿으로 생각할 수 있습니다.
MultiClass ::= "multiclass" TokIdentifier [TemplateArgList]
ParentClassList
"{" MultiClassStatement+ "}"
MultiClassID ::= TokIdentifier
MultiClassStatement ::= Assert | Def | Defm | Defvar | Foreach | If | Let
일반 클래스와 마찬가지로 multiclass는 이름을 가지며 템플릿 인자를 받을 수 있습니다. Multiclass는 다른 multiclass로부터 상속할 수 있으며, 이는 다른 multiclass가 확장되어 상속하는 multiclass의 레코드 정의에 기여함을 의미합니다. Multiclass의 본문에는 Def와 Defm을 사용하여 레코드를 정의하는 일련의 문장이 포함됩니다. 추가로, 더 많은 공통 요소를 분해하기 위해 Defvar, Foreach, Let 문을 사용할 수 있습니다. If와 Assert 문도 사용할 수 있습니다.
또한 일반 클래스와 동일하게 multiclass에는 암묵적 템플릿 인자 NAME이 있습니다(NAME 참조). Multiclass에서 명명된(익명 아님) 레코드를 정의할 때, 레코드 이름에 템플릿 인자 NAME 사용이 포함되지 않으면, 그러한 사용이 자동으로 이름 앞에 붙습니다. 즉, multiclass 내부에서 다음은 동등합니다:
def Foo ... def NAME # Foo ...
Multiclass에서 정의된 레코드는 multiclass 정의 밖의 defm 문에 의해 multiclass가 “인스턴스화” 또는 “호출”될 때 생성됩니다. Multiclass의 각 def 문은 하나의 레코드를 생성합니다. 최상위 def 문과 마찬가지로 이러한 정의는 여러 부모 클래스를 상속할 수 있습니다.
예시는 예시: multiclass와 defm을 참고하세요.
defm — multiclass 호출로 여러 레코드 정의¶Multiclass가 정의된 후에는 defm 문을 사용하여 이를 “호출”하고 해당 multiclass의 다중 레코드 정의를 처리합니다. 이러한 레코드 정의는 multiclass의 def 문으로 직접 지정되거나 defm 문으로 간접적으로 지정됩니다.
Defm ::= "defm" [NameValue] ParentClassList ";"
선택적 NameValue는 def 이름과 같은 방식으로 구성됩니다. ParentClassList는 콜론 다음에 하나 이상의 multiclass와 임의 개수의 일반 클래스를 나열한 목록입니다. Multiclass는 일반 클래스보다 앞에 와야 합니다. defm에는 본문이 없다는 점에 유의하세요.
이 문장은 지정된 모든 multiclass에서 def 문으로 직접 혹은 defm 문으로 간접적으로 정의된 모든 레코드를 인스턴스화합니다. 이들 레코드는 부모 클래스 목록에 포함된 일반 클래스에서 정의한 필드도 받습니다. 이는 해당 defm에 의해 생성된 모든 레코드에 공통 필드 집합을 추가하는데 유용합니다.
이름은 def에서 사용하는 것과 같은 특수 모드로 파싱됩니다. 이름이 포함되지 않으면, 지정되지 않았지만 전역적으로 고유한 이름이 제공됩니다. 즉, 다음 예시는 서로 다른 이름을 갖게 됩니다:
defm : SomeMultiClass<...>; // 전역적으로 고유한 이름. defm "" : SomeMultiClass<...>; // 빈 이름.
defm 문은 multiclass 본문에서 사용할 수 있습니다. 이 경우 두 번째 변형은 다음과 동등합니다:
defm NAME : SomeMultiClass<...>;
좀 더 일반적으로, defm이 multiclass 내부에 있고 그 이름에 암묵적 템플릿 인자 NAME 사용이 포함되지 않으면, NAME이 자동으로 앞에 붙습니다. 즉, multiclass 내부에서 다음은 동등합니다:
defm Foo : SomeMultiClass<...>; defm NAME # Foo : SomeMultiClass<...>;
예시는 예시: multiclass와 defm을 참고하세요.
다음은 multiclass와 defm을 사용하는 간단한 예시입니다. reg = reg op reg과 reg = reg op imm(즉시값) 두 가지 포맷의 명령을 가진 3-주소 명령 아키텍처를 고려해 봅시다. SPARC가 이러한 아키텍처의 예입니다.
def ops; def GPR; def Imm; class inst <int opc, string asmstr, dag operandlist>;
multiclass ri_inst <int opc, string asmstr> { def _rr : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, GPR:$src2)>; def _ri : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, Imm:$src2)>; }
// RR 및 RI 포맷의 각 명령에 대한 레코드 정의. defm ADD : ri_inst<0b111, "add">; defm SUB : ri_inst<0b101, "sub">; defm MUL : ri_inst<0b100, "mul">;
ri_inst multiclass의 각 사용은 _rr 접미가 붙은 레코드 하나와 _ri 접미가 붙은 레코드 하나, 이렇게 두 개의 레코드를 정의합니다. Multiclass를 사용하는 defm의 이름이 multiclass에서 정의한 레코드의 이름 앞에 붙는다는 점을 상기하세요. 따라서 생성되는 정의의 이름은 다음과 같습니다:
ADD_rr, ADD_ri SUB_rr, SUB_ri MUL_rr, MUL_ri
multiclass 기능이 없었다면, 명령은 다음과 같이 정의해야 합니다.
def ops; def GPR; def Imm; class inst <int opc, string asmstr, dag operandlist>;
class rrinst <int opc, string asmstr> : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, GPR:$src2)>;
class riinst <int opc, string asmstr> : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, Imm:$src2)>;
// RR 및 RI 포맷의 각 명령에 대한 레코드 정의. def ADD_rr : rrinst<0b111, "add">; def ADD_ri : riinst<0b111, "add">; def SUB_rr : rrinst<0b101, "sub">; def SUB_ri : riinst<0b101, "sub">; def MUL_rr : rrinst<0b100, "mul">; def MUL_ri : riinst<0b100, "mul">;
defm은 multiclass 내부에서 다른 multiclass를 “호출”하여 그 multiclass가 정의한 레코드들을 현재 multiclass가 정의한 레코드에 추가로 생성하도록 사용할 수 있습니다. 다음 예시에서 basic_s와 basic_p multiclass에는 basic_r multiclass를 참조하는 defm 문이 포함되어 있습니다. basic_r multiclass는 def 문만 포함합니다.
class Instruction <bits<4> opc, string Name> { bits<4> opcode = opc; string name = Name; }
multiclass basic_r <bits<4> opc> { def rr : Instruction<opc, "rr">; def rm : Instruction<opc, "rm">; }
multiclass basic_s <bits<4> opc> { defm SS : basic_r<opc>; defm SD : basic_r<opc>; def X : Instruction<opc, "x">; }
multiclass basic_p <bits<4> opc> { defm PS : basic_r<opc>; defm PD : basic_r<opc>; def Y : Instruction<opc, "y">; }
defm ADD : basic_s<0xf>, basic_p<0xf>;
마지막 defm은 basic_s multiclass에서 다섯 개, basic_p multiclass에서 다섯 개, 총 열 개의 레코드를 생성합니다:
ADDSSrr, ADDSSrm ADDSDrr, ADDSDrm ADDX ADDPSrr, ADDPSrm ADDPDrr, ADDPDrm ADDY
defm 문은 최상위와 multiclass 내부 모두에서, multiclass뿐 아니라 일반 클래스도 상속할 수 있습니다. 규칙은 일반 클래스가 multiclass 뒤에 나열되어야 하며, 한 개 이상의 multiclass가 있어야 한다는 것입니다.
class XD { bits<4> Prefix = 11; } class XS { bits<4> Prefix = 12; } class I <bits<4> op> { bits<4> opcode = op; }
multiclass R { def rr : I<4>; def rm : I<2>; }
multiclass Y { defm SS : R, XD; // 먼저 multiclass R, 그 다음 일반 클래스 XD. defm SD : R, XS; }
defm Instr : Y;
이 예시는 다음 네 개의 레코드를 생성하며, 여기서는 필드와 함께 알파벳 순으로 보여줍니다.
def InstrSDrm { bits<4> opcode = { 0, 0, 1, 0 }; bits<4> Prefix = { 1, 1, 0, 0 }; }
def InstrSDrr { bits<4> opcode = { 0, 1, 0, 0 }; bits<4> Prefix = { 1, 1, 0, 0 }; }
def InstrSSrm { bits<4> opcode = { 0, 0, 1, 0 }; bits<4> Prefix = { 1, 0, 1, 1 }; }
def InstrSSrr { bits<4> opcode = { 0, 1, 0, 0 }; bits<4> Prefix = { 1, 0, 1, 1 }; }
또한 multiclass 내부에서 let 문을 사용할 수도 있어, 특히 여러 단계의 multiclass 인스턴스화를 사용할 때 레코드의 공통성을 더 분해하는 또 다른 방법을 제공합니다.
multiclass basic_r <bits<4> opc> { let Predicates = [HasSSE2] in { def rr : Instruction<opc, "rr">; def rm : Instruction<opc, "rm">; } let Predicates = [HasSSE3] in def rx : Instruction<opc, "rx">; }
multiclass basic_ss <bits<4> opc> { let IsDouble = false in defm SS : basic_r<opc>;
let IsDouble = true in defm SD : basic_r<opc>; }
defm ADD : basic_ss<0xf>;
defset — 정의 집합 만들기¶defset 문은 레코드 집합을 전역 레코드 리스트에 모으는 데 사용됩니다.
Defset ::= "defset" Type TokIdentifier "=" "{" Statement* "}"
중괄호 안에서 def와 defm로 정의된 모든 레코드는 평소처럼 정의되며, 해당 이름(TokIdentifier)의 전역 리스트로도 수집됩니다.
지정된 타입은 list<class>여야 하며, 여기서 _class_는 어떤 레코드 클래스입니다. defset 문은 그 문장의 범위를 설정합니다. defset의 범위에서 class 타입이 아닌 레코드를 정의하는 것은 오류입니다.
defset 문은 중첩될 수 있습니다. 내부 defset은 자신의 집합에 레코드를 추가하며, 그 레코드들은 외부 집합에도 모두 추가됩니다.
ClassID<...> 문법을 사용한 초기화 식 내부에서 생성된 익명 레코드는 집합에 수집되지 않습니다.
deftype — 타입 정의¶deftype 문은 타입을 정의합니다. 이 타입은 정의 이후의 문장 전반에서 사용할 수 있습니다.
Deftype ::= "deftype" TokIdentifier "=" Type ";"
= 왼쪽의 식별자는 타입 이름으로 정의되며, 실제 타입은 = 오른쪽의 타입 식으로 주어집니다.
현재로서는 원시 타입과 타입 별칭만 소스 타입으로 지원되며, deftype 문은 최상위에만 나타날 수 있습니다.
defvar — 변수 정의¶defvar 문은 전역 변수를 정의합니다. 그 값은 정의 이후의 문장에서 전반적으로 사용할 수 있습니다.
Defvar ::= "defvar" TokIdentifier "=" Value ";"
= 왼쪽의 식별자는 전역 변수로 정의되며, 그 값은 = 오른쪽의 값 식으로 주어집니다. 변수의 타입은 자동으로 추론됩니다.
한 번 변수가 정의되면, 다른 값으로 설정할 수 없습니다.
최상위 foreach에서 정의된 변수는 각 루프 반복의 끝에서 스코프를 벗어나므로, 한 반복에서의 값이 다음 반복에서 사용 가능하지 않습니다. 다음 defvar는 작동하지 않습니다:
defvar i = !add(i, 1);
변수는 레코드 본문에서 defvar로도 정의할 수 있습니다. 자세한 내용은 레코드 본문에서의 Defvar를 참조하세요.
foreach — 문장 시퀀스 반복¶foreach 문은 값 시퀀스에 대해 변수를 변화시키면서 일련의 문장을 반복합니다.
Foreach ::= "foreach" ForeachIterator "in" "{" Statement* "}"
| "foreach" ForeachIterator "in" Statement
ForeachIterator ::= TokIdentifier "=" ("{" RangeList "}" | RangePiece | Value)
foreach의 본문은 중괄호 안의 일련의 문장 또는 중괄호 없는 단일 문장입니다. 문장들은 범위 리스트, 범위 조각(range piece), 또는 단일 값의 각 값마다 한 번씩 재평가됩니다. 각 반복에서 TokIdentifier 변수는 해당 값으로 설정되며 문장에서 사용할 수 있습니다.
문장 리스트는 내부 스코프를 설정합니다. foreach에 로컬한 변수는 각 루프 반복의 끝에서 스코프를 벗어나므로, 값이 다음 반복으로 이어지지 않습니다. Foreach 루프는 중첩될 수 있습니다.
foreach i = [0, 1, 2, 3] in { def R#i : Register<...>; def F#i : Register<...>; }
이 루프는 R0, R1, R2, R3 및 F0, F1, F2, F3이라는 레코드를 정의합니다.
dump — stderr로 메시지 출력¶dump 문은 입력 문자열을 표준 에러로 출력합니다. 디버깅 목적을 위한 것입니다.
최상위에서는, 메시지가 즉시 출력됩니다.
레코드/클래스/multiclass 내부에서는, dump는 포함하는 레코드의 각 인스턴스화 지점에서 평가됩니다.
Dump ::= "dump" Value ";"
Value는 임의의 문자열 식입니다. 예를 들어 !repr과 함께 사용하여 multiclass에 전달되는 값을 조사할 수 있습니다:
multiclass MC<dag s> { dump "s = " # !repr(s); }
if — 테스트에 따른 문장 선택¶if 문은 식의 값에 따라 두 문장 그룹 중 하나를 선택할 수 있게 합니다.
If ::= "if" Value "then" IfBody
| "if" Value "then" IfBody "else" IfBody
IfBody ::= "{" Statement* "}" | Statement
값 식이 평가됩니다. 결과가 참이면(느낌표 연산자가 사용하는 것과 같은 의미의 참), then 예약어 뒤의 문장이 처리됩니다. 거짓이고 else 예약어가 있다면 else 뒤의 문장이 처리됩니다. 값이 거짓이고 else 절이 없으면 아무 문장도 처리되지 않습니다.
then 문장을 둘러싼 중괄호가 선택 사항이므로, 이 문법 규칙은 “매달린 else(dangling else)” 모호성을 가지며, 통상적인 방식으로 해결됩니다: if v1 then if v2 then {...} else {...} 같은 경우, else는 바깥쪽이 아니라 안쪽 if와 연관됩니다.
if의 then/else 팔의 IfBody는 내부 스코프를 설정합니다. 본문에서 정의된 모든 defvar 변수는 본문이 끝나면 스코프를 벗어납니다(레코드 본문에서의 Defvar 참조).
if 문은 레코드 Body에서도 사용할 수 있습니다.
assert — 조건이 참인지 검사¶assert 문은 불리언 조건이 참인지 확인하고, 그렇지 않으면 오류 메시지를 출력합니다.
Assert ::= "assert" Value "," Value ";"
첫 번째 Value는 불리언 조건입니다. 참이면 문은 아무 것도 하지 않습니다. 조건이 거짓이면 비치명적 오류 메시지를 출력합니다. 두 번째 Value는 메시지로, 임의의 문자열 식이 될 수 있습니다. 오류 메시지에 노트로 포함됩니다. assert 문의 정확한 동작은 위치에 따라 달라집니다.
최상위에서는, 어설션이 즉시 검사됩니다.
레코드 정의에서, 문은 저장되며 레코드가 완전히 구축된 후 모든 어설션이 검사됩니다.
클래스 정의에서, 어설션은 저장되어 해당 클래스를 상속하는 모든 서브클래스와 레코드로 상속됩니다. 어설션은 이후 레코드가 완전히 구축될 때 검사됩니다.
multiclass 정의에서, 어설션은 multiclass의 다른 구성 요소들과 함께 저장되며 defm으로 multiclass가 인스턴스화될 때마다 검사됩니다.
TableGen 파일에서 어설션을 사용하면 TableGen 백엔드에서의 레코드 검사를 단순화할 수 있습니다. 다음은 두 클래스 정의에서의 assert 예시입니다.
class PersonName<string name> { assert !le(!size(name), 32), "person name is too long: " # name; string Name = name; }
class Person<string name, int age> : PersonName<name> { assert !and(!ge(age, 1), !le(age, 120)), "person age is invalid: " # age; int Age = age; }
def Rec20 : Person<"Donald Knuth", 60> { ... }
유향 비순환 그래프는 dag 데이터 타입을 사용해 TableGen에서 직접 표현할 수 있습니다. DAG 노드는 연산자와 0개 이상의 인자(또는 피연산자)로 구성됩니다. 각 인자는 원하는 어떤 타입도 될 수 있습니다. 또 다른 DAG 노드를 인자로 사용함으로써 임의의 DAG 노드 그래프를 구성할 수 있습니다.
dag 인스턴스의 문법은 다음과 같습니다:
(operator argument1,argument2,…)
연산자는 반드시 존재해야 하며 레코드여야 합니다. 인자는 0개 이상일 수 있으며, 쉼표로 구분합니다. 연산자와 인자는 세 가지 형식을 가질 수 있습니다.
| Format | Meaning |
|---|---|
| value | 인자 값 |
value:name | 인자 값과 연관된 이름 |
| name | 설정되지 않은(초기화되지 않은) 값의 인자 이름 |
_value_는 어떤 TableGen 값도 될 수 있습니다. _name_이 있는 경우 TokVarName이어야 하며, 달러 기호($)로 시작합니다. 이름의 목적은 DAG의 연산자나 인자에 특정한 의미를 태그로 붙이거나, 한 DAG의 인자를 다른 DAG의 같은 이름을 가진 인자와 연관시키는 것입니다.
다음 느낌표 연산자들은 DAG 작업에 유용합니다: !con, !dag, !empty, !foreach, !getdagarg, !getdagname, !getdagop, !getdagopname, !setdagarg, !setdagname, !setdagop, !setdagopname, !size.
전역 변수를 정의하는 것 외에도, defvar 문은 클래스나 레코드 정의의 Body 내부에서 로컬 변수를 정의하는 데 사용할 수 있습니다. class 또는 multiclass의 템플릿 인자는 값 식에서 사용할 수 있습니다. 변수의 스코프는 defvar 문에서 본문의 끝까지입니다. 스코프 내에서 다른 값으로 설정할 수 없습니다. 또한 defvar 문은 범위를 설정하는 foreach의 문장 리스트에서도 사용할 수 있습니다.
내부 스코프의 V라는 변수는 외부 스코프의 V 변수들을 가립니다(숨깁니다). 특히 다음과 같은 경우들이 있습니다:
레코드 본문의 V는 전역 V를 가립니다.
레코드 본문의 V는 템플릿 인자 V를 가립니다.
템플릿 인자의 V는 전역 V를 가립니다.
foreach 문장 리스트의 V는 주변 레코드나 전역 스코프의 V를 가립니다.
foreach에서 정의된 변수는 각 루프 반복의 끝에서 스코프를 벗어나므로, 한 반복에서의 값이 다음 반복에서 사용 가능하지 않습니다. 다음 defvar는 작동하지 않습니다:
defvar i = !add(i, 1)
레코드가 생성될 때 TableGen이 수행하는 단계는 다음과 같습니다. 클래스는 단순히 추상 레코드이므로 동일한 단계를 거칩니다.
레코드 이름(NameValue)을 구성하고 빈 레코드를 생성합니다.
ParentClassList의 부모 클래스를 왼쪽에서 오른쪽으로 파싱하며, 각 부모 클래스의 조상 클래스를 위에서 아래로 방문합니다.
부모 클래스의 필드를 레코드에 추가합니다.
해당 필드에 템플릿 인자를 치환합니다.
부모 클래스를 레코드의 상속 클래스 목록에 추가합니다.
레코드에 최상위 let 바인딩을 적용합니다. 최상위 바인딩은 상속된 필드에만 적용됨을 상기하세요.
레코드의 본문을 파싱합니다.
필드를 레코드에 추가합니다.
로컬
let문에 따라 필드 값을 수정합니다.모든
defvar변수를 정의합니다.
모든 필드를 한 번 순회하여 필드 간 참조를 해소합니다.
레코드를 최종 레코드 리스트에 추가합니다.
let 바인딩이 적용된 후(3단계) 필드 간 참조가 해소되기 때문에(5단계), let 문은 독특한 힘을 가집니다. 예를 들어:
class C <int x> { int Y = x; int Yplus1 = !add(Y, 1); int xplus1 = !add(x, 1); }
let Y = 10 in { def rec1 : C<5> { } }
def rec2 : C<5> { let Y = 10; }
둘 다, 즉 최상위 let을 사용하여 Y를 바인딩한 경우와 로컬 let을 사용하는 경우 모두 결과는 같습니다:
def rec1 { // C int Y = 10; int Yplus1 = 11; int xplus1 = 6; } def rec2 { // C int Y = 10; int Yplus1 = 11; int xplus1 = 6; }
Yplus1은,
!add(Y,
1)
이 let Y가 적용된 후에 해소되기 때문에 11입니다. 이 능력을 현명하게 사용하세요.
단순 값에서 설명했듯이, 클래스는 식에서 호출되어 템플릿 인수를 전달받을 수 있습니다. 그러면 TableGen은 해당 클래스를 상속하는 새 익명 레코드를 생성합니다. 평소처럼 레코드는 클래스에서 정의한 모든 필드를 받습니다.
이 기능은 간단한 서브루틴 기능으로 사용할 수 있습니다. 클래스는 템플릿 인자를 사용해 다양한 변수와 필드를 정의할 수 있으며, 이는 익명 레코드에 들어갑니다. 그런 다음 클래스 호출 식에서 다음과 같이 해당 필드를 가져올 수 있습니다. 필드 ret가 서브루틴의 최종 값을 담고 있다고 가정합니다.
int Result = ... CalcValue<arg>.ret ...;
CalcValue 클래스는 템플릿 인자 arg로 호출됩니다. 이 클래스는 ret 필드 값을 계산하며, 이는 Result 필드 초기화에서 “호출 지점”에서 가져옵니다. 이 예시에서 생성된 익명 레코드는 결과 값을 운반하는 것 외에 다른 목적은 없습니다.
다음은 실용적인 예시입니다. isValidSize 클래스는 지정된 바이트 수가 유효한 데이터 크기인지 여부를 판단합니다. 비트 ret가 적절히 설정됩니다. 필드 ValidSize는 데이터 크기로 isValidSize를 호출하고 결과 익명 레코드에서 ret 필드를 가져와 초기값을 얻습니다.
class isValidSize<int size> { bit ret = !cond(!eq(size, 1): 1, !eq(size, 2): 1, !eq(size, 4): 1, !eq(size, 8): 1, !eq(size, 16): 1, true: 0); }
def Data1 { int Size = ...; bit ValidSize = isValidSize<Size>.ret; }
TableGen에 내장된 전처리기는 단순한 조건부 컴파일만을 의도합니다. 다음 지시어를 지원하며, 다소 비공식적으로 명시됩니다.
LineBegin ::= 줄의 시작
LineEnd ::= 줄바꿈 | 리턴 | EOF
WhiteSpace ::= 공백 | 탭
::= "/" ... "/"
::= "//" ... LineEnd
::= WhiteSpace | CComment
::= WhiteSpace | CComment | BCPLComment
MacroName ::= ualpha (ualpha | "0"..."9")*
PreDefine ::= LineBegin (WhiteSpaceOrCComment)*
"#define" (WhiteSpace)+ MacroName
(WhiteSpaceOrAnyComment)* LineEnd
PreIfdef ::= LineBegin (WhiteSpaceOrCComment)*
("#ifdef" | "#ifndef") (WhiteSpace)+ MacroName
(WhiteSpaceOrAnyComment)* LineEnd
PreElse ::= LineBegin (WhiteSpaceOrCComment)*
"#else" (WhiteSpaceOrAnyComment)* LineEnd
PreEndif ::= LineBegin (WhiteSpaceOrCComment)*
"#endif" (WhiteSpaceOrAnyComment)* LineEnd
MacroName는 TableGen 파일 어디에서나 정의될 수 있습니다. 이름은 값을 가지지 않으며, 정의되었는지 여부만 테스트할 수 있습니다.
매크로 테스트 영역은 #ifdef 또는 #ifndef 지시어로 시작합니다. 매크로 이름이 정의되어 있으면(#ifdef), 또는 정의되지 않았으면(#ifndef), 해당 지시어와 대응하는 #else 또는 #endif 사이의 소스 코드가 처리됩니다. 테스트가 실패했지만 #else 절이 존재하면 #else와 #endif 사이의 소스 코드가 처리됩니다. 테스트가 실패하고 #else 절도 없으면 테스트 영역의 소스 코드는 처리되지 않습니다.
테스트 영역은 중첩될 수 있으나, 적절히 중첩되어야 합니다. 한 파일에서 시작된 영역은 해당 파일에서 끝나야 합니다. 즉, 같은 파일에 #endif가 있어야 합니다.
MacroName는 *-tblgen 커맨드 라인에서 -D 옵션으로 외부에서 정의할 수 있습니다:
llvm-tblgen self-reference.td -Dmacro1 -Dmacro3
느낌표 연산자는 값 식에서 함수처럼 동작합니다. 느낌표 연산자는 하나 이상의 인자를 받아, 그 인자에 대해 연산을 수행하고 결과를 생성합니다. 연산자가 불리언 결과를 생성하는 경우, 결과 값은 참이면 1, 거짓이면 0입니다. 연산자가 불리언 인자를 테스트할 때는 0을 거짓으로, 0이 아닌 값을 참으로 해석합니다.
경고
!getop과 !setop 느낌표 연산자는 !getdagop과 !setdagop로 대체되어 더 이상 권장되지 않습니다.
!add(a,b, ...)
이 연산자는 a, b 등을 더하여 합계를 생성합니다.
!and(a,b, ...)
이 연산자는 a, b 등에 대해 비트 AND를 수행하고 결과를 생성합니다. 모든 인자가 0 또는 1이면 논리 AND를 수행할 수 있습니다. 이 연산자는 가장 왼쪽 피연산자가 0일 때 0으로 단락(short-circuit)됩니다.
!cast<type>(a)
이 연산자는 _a_에 대해 캐스트를 수행하고 결과를 생성합니다. _a_가 문자열이 아니면 int와 bit 간, 또는 레코드 타입 간과 같은 직접적인 캐스트가 수행됩니다. 이를 통해 레코드를 클래스로 캐스트할 수 있습니다. 레코드를 string으로 캐스트하면 레코드의 이름이 생성됩니다.
_a_가 문자열이면, 레코드 이름으로 취급되어 모든 정의된 레코드 목록에서 조회됩니다. 결과 레코드는 지정한 _type_이어야 합니다.
예를 들어, !cast<type>(name)가 multiclass 정의 안이나 multiclass 정의 안에서 인스턴스화된 클래스 안에 나타나고, _name_이 multiclass의 어떤 템플릿 인자도 참조하지 않으면, 해당 이름의 레코드는 소스 파일에서 더 이전에 인스턴스화되어 있어야 합니다. _name_이 템플릿 인자를 참조하면, 조회는 multiclass를 인스턴스화하는 defm 문까지(또는 그 이후, 만약 defm이 다른 multiclass에 있고 _name_이 참조하는 내부 multiclass의 템플릿 인자가 외부 multiclass의 템플릿 인자를 참조하는 값으로 치환되는 경우) 지연됩니다.
_a_의 타입이 _type_과 일치하지 않으면 TableGen은 오류를 발생시킵니다.
!con(a,b, ...)
이 연산자는 DAG 노드 a, b 등을 이어붙입니다. 이들의 연산자는 동일해야 합니다.
!con((op:$lhs a1:$name1, a2:$name2), (op:$rhs b1:$name3))
결과는 (op:$lhs a1:$name1, a2:$name2, b1:$name3) DAG 노드입니다. DAG 연산자의 이름은 설정되어 있다면 LHS DAG 노드에서, 그렇지 않으면 RHS DAG 노드에서 가져옵니다.
!cond(cond1:val1,cond2:val2, ...,condn:valn)
이 연산자는 _cond1_을 테스트하고 결과가 참이면 _val1_을 반환합니다. 거짓이면 _cond2_를 테스트하고 결과가 참이면 _val2_를 반환합니다. 계속해서 동일합니다. 어떤 조건도 참이 아니면 오류가 보고됩니다.
다음 예시는 정수의 부호 단어를 생성합니다:
!cond(!lt(x, 0) : "negative", !eq(x, 0) : "zero", true : "positive")
!dag(op,arguments,names)
이 연산자는 지정된 연산자와 인자로 DAG 노드를 생성합니다. _arguments_와 names 인자는 길이가 같거나 초기화되지 않은 값(?)이어야 합니다. names 인자는 list<string> 타입이어야 합니다.
타입 시스템의 제한으로 인해, _arguments_는 공통 타입의 항목 리스트여야 합니다. 실제로는 타입이 같거나 공통 부모 클래스를 가진 레코드여야 합니다. dag와 비-dag 항목을 혼합할 수 없습니다. 하지만 ?는 사용할 수 있습니다.
예: !dag(op, [a1, a2, ?], ["name1", "name2", "name3"])의 결과는 (op a1-value:$name1, a2-value:$name2, ?:$name3)입니다.
!div(a,b)
이 연산자는 _a_를 _b_로 부호 있는 나눗셈을 수행하여 몫을 생성합니다. 0으로 나누면 오류가 발생합니다. INT64_MIN을 -1로 나누면 오류가 발생합니다.
!empty(a)
이 연산자는 문자열, 리스트, 또는 DAG _a_가 비어 있으면 1을, 아니면 0을 생성합니다. DAG는 인자가 없으면 비어 있는 것으로 간주합니다. 연산자는 포함되지 않습니다.
!eq(a,b)
이 연산자는 _a_가 _b_와 같으면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, string, 또는 레코드 값이어야 합니다. 다른 타입의 객체 비교는 !cast<string>을 사용하세요.
!exists<type>(name)
이 연산자는 이름이 _name_이고 타입이 _type_인 레코드가 존재하면 1을, 아니면 0을 생성합니다. _name_은 string 타입이어야 합니다.
!filter(var,list,predicate)
이 연산자는 _list_의 요소를 필터링하여 새로운
list를 생성합니다. 필터링을 수행하기 위해, TableGen은 변수 _var_를 각 요소에 바인딩한 다음 predicate 식을 평가합니다(일반적으로 _var_를 참조합니다). 프레디케이트는 불리언 값(bit,bits,int)을 생성해야 합니다. 값은!if와 동일하게 해석됩니다: 값이 0이면 요소는 새 리스트에 포함되지 않습니다. 값이 그 외이면 요소는 포함됩니다.
!find(string1,string2[,start])
이 연산자는 _string1_에서 _string2_를 검색하고 그 위치를 생성합니다. 검색의 시작 위치는 _start_로 지정할 수 있으며, 0에서 _string1_의 길이 사이여야 합니다. 기본값은 0입니다. 문자열을 찾지 못하면 결과는 -1입니다.
!foldl(init,list,acc,var,expr)
이 연산자는 _list_의 항목에 대해 왼쪽 폴드 연산을 수행합니다. 변수 _acc_는 누산기로 작동하며 _init_으로 초기화됩니다. 변수 _var_는 _list_의 각 요소에 바인딩됩니다. 표현식은 각 요소에 대해 평가되며, 보통 _acc_와 _var_를 사용해 누산 값을 계산합니다. !foldl은 해당 값을 다시 _acc_에 저장합니다. _acc_의 타입은 _init_과 동일하며, _var_의 타입은 _list_의 요소와 동일합니다. _expr_은 _init_과 동일한 타입이어야 합니다.
다음 예시는 RecList의 레코드 리스트에서 Number 필드의 합계를 계산합니다:
int x = !foldl(0, RecList, total, rec, !add(total, rec.Number));
목표가 리스트를 필터링하여 일부 요소만 포함하는 새 리스트를 생성하는 것이라면 !filter를 참조하세요.
!foreach(var,sequence,expr)
이 연산자는 sequence list/dag의 각 요소에 대응하는 함수 값을 요소로 가지는 새로운 list/dag를 생성합니다. 함수를 수행하기 위해, TableGen은 변수 _var_를 요소에 바인딩하고 표현식을 평가합니다. 표현식은 일반적으로 변수 _var_를 참조하며 결과 값을 계산합니다.
단순히 동일한 값이 여러 번 반복된 특정 길이의 리스트를 만들고자 한다면 !listsplat을 참조하세요.
!ge(a,b)
이 연산자는 _a_가 b 이상이면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, 또는 string 값이어야 합니다.
!getdagarg<type>(dag,key)
이 연산자는 지정된 _key_로 지정된 주어진 dag 노드의 인자를 가져옵니다. _key_는 정수 인덱스거나 문자열 이름입니다. 해당 인자가 지정된 _type_으로 변환할 수 없으면 ?가 반환됩니다.
!getdagname(dag,index)
이 연산자는 지정된 _index_로 주어진 dag 노드의 인자 이름을 가져옵니다. 해당 인자에 이름이 없으면 ?가 반환됩니다.
!getdagop(dag) –또는– !getdagop<type>(dag)
이 연산자는 주어진 dag 노드의 연산자를 생성합니다. 예: !getdagop((foo 1, 2))의 결과는 foo입니다. DAG 연산자는 항상 레코드임을 기억하세요.
!getdagop의 결과는 어떤 레코드 클래스든 허용되는 문맥(보통 다른 dag 값에 넣을 때)에서는 직접 사용할 수 있습니다. 그러나 다른 문맥에서는 특정 클래스로 명시적으로 캐스트해야 합니다. 이를 쉽게 하기 위해 <type> 문법이 제공됩니다.
예를 들어, 결과를 BaseClass 타입의 값에 할당하려면 다음 중 하나를 사용할 수 있습니다:
BaseClass b = !getdagop<BaseClass>(someDag); BaseClass b = !cast<BaseClass>(!getdagop(someDag));
그러나 다른 DAG의 연산자를 재사용하여 새 DAG 노드를 만드는 경우에는 캐스트가 필요 없습니다:
dag d = !dag(!getdagop(someDag), args, names);
!getdagopname(dag)
이 연산자는 주어진 dag 연산자의 이름을 가져옵니다. 연산자에 이름이 없으면 ?가 반환됩니다.
!gt(a,b)
이 연산자는 _a_가 _b_보다 크면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, 또는 string 값이어야 합니다.
!head(a)
이 연산자는 리스트 _a_의 0번째 요소를 생성합니다.(!tail도 참고하세요.)
!if(test,then,else)
이 연산자는 _test_를 평가하며, 결과는 bit 또는 int여야 합니다. 결과가 0이 아니면 then 표현식을, 그렇지 않으면 else 표현식을 생성합니다.
!initialized(a)
이 연산자는 _a_가 초기화되지 않은 값(?)이 아니면 1, 그렇지 않으면 0을 생성합니다.
!instances<type>([regex])
이 연산자는 타입이 _type_인 레코드의 리스트를 생성합니다. _regex_가 제공되면 이름이 정규식 _regex_와 일치하는 레코드만 포함됩니다. _regex_의 형식은 ERE(확장 POSIX 정규식)입니다.
!instances가 클래스/multiclass/foreach 안에 있으면, 인스턴스화된 해당 시점까지의 type 레코드만 고려됩니다.
!interleave(list,delim)
이 연산자는 _list_의 항목을 연결하되, 각 쌍 사이에 delim 문자열을 삽입하여 결과 문자열을 생성합니다. 리스트는 string, int, bits, bit의 리스트일 수 있습니다. 빈 리스트는 빈 문자열을 결과로 줍니다. 구분자는 빈 문자열도 가능합니다.
!isa<type>(a)
이 연산자는 _a_의 타입이 주어진 _type_의 서브타입이면 1, 아니면 0을 생성합니다.
!le(a,b)
이 연산자는 _a_가 b 이하이면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, 또는 string 값이어야 합니다.
!listconcat(list1,list2, ...)
이 연산자는 리스트 인자 list1, list2 등을 이어붙여 결과 리스트를 생성합니다. 리스트의 요소 타입은 동일해야 합니다.
!listflatten(list)
이 연산자는 리스트의 리스트 _list_를 평탄화하여, 구성 리스트의 모든 요소를 이어붙인 리스트를 생성합니다. _list_가 list<list<X>> 타입이면 결과는 list<X> 타입입니다. _list_의 요소 타입이 리스트가 아니면 결과는 list 자체입니다.
!listremove(list1,list2)
이 연산자는 _list1_의 복사본에서 _list2_에도 존재하는 모든 요소를 제거하여 반환합니다. 리스트의 요소 타입은 동일해야 합니다.
!listsplat(value,count)
이 연산자는 길이가 _count_이고 모든 요소가 _value_와 동일한 리스트를 생성합니다. 예: !listsplat(42, 3)의 결과는 [42, 42, 42]입니다.
!logtwo(a)
이 연산자는 _a_의 밑 2 로그를 계산하여 정수 결과를 생성합니다. 0 또는 음수의 로그는 오류를 발생시킵니다. 바닥 함수(flooring) 연산입니다.
!lt(a,b)
이 연산자는 _a_가 _b_보다 작으면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, 또는 string 값이어야 합니다.
!match(str,regex)
이 연산자는 _str_이 정규식 _regex_와 일치하면 1을 생성합니다. _regex_의 형식은 ERE(확장 POSIX 정규식)입니다.
!mul(a,b, ...)
이 연산자는 a, b 등을 곱하여 곱(product)을 생성합니다.
!ne(a,b)
이 연산자는 _a_가 _b_와 같지 않으면 1, 아니면 0을 생성합니다. 인자는 bit, bits, int, string, 또는 레코드 값이어야 합니다. 다른 타입의 객체 비교는 !cast<string>을 사용하세요.
!not(a)
이 연산자는 _a_에 대해 논리 NOT을 수행합니다. _a_는 정수여야 합니다. 인자가 0이면 1(참), 다른 값이면 0(거짓)입니다.
!or(a,b, ...)
이 연산자는 a, b 등에 대해 비트 OR을 수행하고 결과를 생성합니다. 모든 인자가 0 또는 1이면 논리 OR을 수행할 수 있습니다. 이 연산자는 가장 왼쪽 피연산자가 -1(모든 비트 1)일 때 -1로 단락됩니다.
!range([start,]end[,step])
이 연산자는 반열린 구간 [start : end : step)을 list<int>로 생성합니다. _start_의 기본값은 0, _step_의 기본값은 1입니다. _step_은 음수일 수 있으며 0일 수는 없습니다. start<_end_이고 _step_이 음수이거나, start>_end_이고 _step_이 양수이면 결과는 빈 리스트 []<int>입니다.
예:
!range(4)는 !range(0, 4, 1)과 동일하며 결과는 [0, 1, 2, 3]입니다.
!range(1, 4)는 !range(1, 4, 1)과 동일하며 결과는 [1, 2, 3]입니다.
!range(0, 4, 2)의 결과는 [0, 2]입니다.
!range(0, 4, -1)과 !range(4, 0, 1)의 결과는 비어 있습니다.
!range(list)
!range(0, !size(list))와 동일합니다.
!repr(value)
_value_를 문자열로 표현합니다. 값의 문자열 형식은 안정적일 것을 보장하지 않습니다. 디버깅 목적에만 사용하십시오.
!setdagarg(dag,key,arg)
이 연산자는 _dag_와 동일한 연산자와 인자를 가지되, _key_로 지정된 인자의 값을 _arg_로 교체한 DAG 노드를 생성합니다. _key_는 정수 인덱스 또는 문자열 이름일 수 있습니다.
!setdagname(dag,key,name)
이 연산자는 _dag_와 동일한 연산자와 인자를 가지되, _key_로 지정된 인자의 이름을 _name_으로 교체한 DAG 노드를 생성합니다. _key_는 정수 인덱스 또는 문자열 이름일 수 있습니다.
!setdagop(dag,op)
이 연산자는 _dag_와 동일한 인자를 가지되, 연산자를 _op_로 교체한 DAG 노드를 생성합니다.
예: !setdagop((foo 1, 2), bar)의 결과는 (bar 1, 2)입니다.
!setdagopname(dag,name)
이 연산자는 _dag_와 동일한 연산자와 인자를 가지되, 연산자의 이름을 _name_으로 교체한 DAG 노드를 생성합니다.
!shl(a,count)
이 연산자는 _a_를 count 비트만큼 논리적으로 왼쪽으로 시프트하고 결과 값을 생성합니다. 연산은 64비트 정수에서 수행되며, 0…63 범위를 벗어난 시프트 카운트에 대해서는 결과가 정의되지 않습니다.
!size(a)
이 연산자는 문자열, 리스트, 또는 dag _a_의 크기를 생성합니다. DAG의 크기는 인자 수입니다. 연산자는 포함되지 않습니다.
!sra(a,count)
이 연산자는 _a_를 count 비트만큼 산술적으로 오른쪽 시프트하고 결과 값을 생성합니다. 연산은 64비트 정수에서 수행되며, 0…63 범위를 벗어난 시프트 카운트에 대해서는 결과가 정의되지 않습니다.
!srl(a,count)
이 연산자는 _a_를 count 비트만큼 논리적으로 오른쪽 시프트하고 결과 값을 생성합니다. 연산은 64비트 정수에서 수행되며, 0…63 범위를 벗어난 시프트 카운트에 대해서는 결과가 정의되지 않습니다.
!strconcat(str1,str2, ...)
이 연산자는 문자열 인자 str1, str2 등을 이어붙여 결과 문자열을 생성합니다.
!sub(a,b)
이 연산자는 _a_에서 _b_를 빼서 산술 차(difference)를 생성합니다.
!subst(target,repl,value)
이 연산자는 _value_에서 _target_의 모든 발생을 _repl_로 치환하여 결과 값을 생성합니다. _value_가 문자열이면 부분 문자열 치환이 수행됩니다.
_value_가 레코드 이름이면, target 레코드 이름이 value 레코드 이름과 같으면 repl 레코드를 생성하고, 그렇지 않으면 _value_를 생성합니다.
!substr(string,start[,length])
이 연산자는 주어진 _string_의 부분 문자열을 추출합니다. 부분 문자열의 시작 위치는 _start_로 지정하며, 0에서 문자열 길이 사이여야 합니다. 부분 문자열의 길이는 _length_로 지정하며, 지정되지 않으면 나머지 전체를 추출합니다. _start_와 length 인자는 정수여야 합니다.
!tail(a)
이 연산자는 리스트 _a_의 첫 번째(0번째 제외) 모든 요소를 가진 새 리스트를 생성합니다.(!head도 참고하세요.)
!tolower(a)
이 연산자는 문자열 입력 _a_를 소문자로 변환합니다.
!toupper(a)
이 연산자는 문자열 입력 _a_를 대문자로 변환합니다.
!xor(a,b, ...)
이 연산자는 a, b 등에 대해 비트 XOR을 수행하고 결과를 생성합니다. 모든 인자가 0 또는 1이면 논리 XOR을 수행할 수 있습니다.
다음은 레코드 이름에서 붙여넣기 연산자의 사용을 보여주는 예시입니다.
defvar suffix = "_suffstring"; defvar some_ints = [0, 1, 2, 3];
def name # suffix { }
foreach i = [1, 2] in { def rec # i { } }
첫 번째 def는 suffix 변수의 값을 사용하지 않습니다. 두 번째 def는 전역 이름이 아니므로 반복 변수 i의 값을 사용합니다. 생성되는 레코드는 다음과 같습니다.
def namesuffix { } def rec1 { } def rec2 { }
다음은 필드 값 식에서 붙여넣기 연산자를 보여주는 두 번째 예시입니다.
def test { string strings = suffix # suffix; list<int> integers = some_ints # [4, 5, 6]; }
strings 필드 식은 붙여넣기 연산자의 양쪽에서 suffix를 사용합니다. 왼쪽은 정상적으로 평가되지만 오른쪽은 글자 그대로 취급됩니다. integers 필드 식은 some_ints 변수의 값과 리터럴 리스트를 사용합니다. 생성되는 레코드는 다음과 같습니다.
def test { string strings = "_suffstringsuffix"; list<int> ints = [0, 1, 2, 3, 4, 5, 6]; }
LLVM이 지원하는 타깃 머신 중 하나는 Intel x86입니다. 다음 TableGen 출력은 32비트 레지스터-대-레지스터 ADD 명령을 나타내기 위해 생성되는 레코드를 보여줍니다.
def ADD32rr { // InstructionEncoding Instruction X86Inst I ITy Sched BinOpRR BinOpRR_RF int Size = 0; string DecoderNamespace = ""; list<Predicate> Predicates = []; string DecoderMethod = ""; bit hasCompleteDecoder = 1; string Namespace = "X86"; dag OutOperandList = (outs GR32:$dst); dag InOperandList = (ins GR32:$src1, GR32:$src2); string AsmString = "add{l} {$src2, $src1|$src1, $src2}"; EncodingByHwMode EncodingInfos = ?; list<dag> Pattern = [(set GR32:$dst, EFLAGS, (X86add_flag GR32:$src1, GR32:$src2))]; list<Register> Uses = []; list<Register> Defs = [EFLAGS]; int CodeSize = 3; int AddedComplexity = 0; bit isPreISelOpcode = 0; bit isReturn = 0; bit isBranch = 0; bit isEHScopeReturn = 0; bit isIndirectBranch = 0; bit isCompare = 0; bit isMoveImm = 0; bit isMoveReg = 0; bit isBitcast = 0; bit isSelect = 0; bit isBarrier = 0; bit isCall = 0; bit isAdd = 0; bit isTrap = 0; bit canFoldAsLoad = 0; bit mayLoad = ?; bit mayStore = ?; bit mayRaiseFPException = 0; bit isConvertibleToThreeAddress = 1; bit isCommutable = 1; bit isTerminator = 0; bit isReMaterializable = 0; bit isPredicable = 0; bit isUnpredicable = 0; bit hasDelaySlot = 0; bit usesCustomInserter = 0; bit hasPostISelHook = 0; bit hasCtrlDep = 0; bit isNotDuplicable = 0; bit isConvergent = 0; bit isAuthenticated = 0; bit isAsCheapAsAMove = 0; bit hasExtraSrcRegAllocReq = 0; bit hasExtraDefRegAllocReq = 0; bit isRegSequence = 0; bit isPseudo = 0; bit isExtractSubreg = 0; bit isInsertSubreg = 0; bit variadicOpsAreDefs = 0; bit hasSideEffects = ?; bit isCodeGenOnly = 0; bit isAsmParserOnly = 0; bit hasNoSchedulingInfo = 0; InstrItinClass Itinerary = NoItinerary; list<SchedReadWrite> SchedRW = [WriteALU]; string Constraints = "$src1 = $dst"; string DisableEncoding = ""; string PostEncoderMethod = ""; bits<64> TSFlags = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }; string AsmMatchConverter = ""; string TwoOperandAliasConstraint = ""; string AsmVariantName = ""; bit UseNamedOperandTable = 0; bit FastISelShouldIgnore = 0; bits<8> Opcode = { 0, 0, 0, 0, 0, 0, 0, 1 }; Format Form = MRMDestReg; bits<7> FormBits = { 0, 1, 0, 1, 0, 0, 0 }; ImmType ImmT = NoImm; bit ForceDisassemble = 0; OperandSize OpSize = OpSize32; bits<2> OpSizeBits = { 1, 0 }; AddressSize AdSize = AdSizeX; bits<2> AdSizeBits = { 0, 0 }; Prefix OpPrefix = NoPrfx; bits<3> OpPrefixBits = { 0, 0, 0 }; Map OpMap = OB; bits<3> OpMapBits = { 0, 0, 0 }; bit hasREX_WPrefix = 0; FPFormat FPForm = NotFP; bit hasLockPrefix = 0; Domain ExeDomain = GenericDomain; bit hasREPPrefix = 0; Encoding OpEnc = EncNormal; bits<2> OpEncBits = { 0, 0 }; bit HasVEX_W = 0; bit IgnoresVEX_W = 0; bit EVEX_W1_VEX_W0 = 0; bit hasVEX_4V = 0; bit hasVEX_L = 0; bit ignoresVEX_L = 0; bit hasEVEX_K = 0; bit hasEVEX_Z = 0; bit hasEVEX_L2 = 0; bit hasEVEX_B = 0; bits<3> CD8_Form = { 0, 0, 0 }; int CD8_EltSize = 0; bit hasEVEX_RC = 0; bit hasNoTrackPrefix = 0; bits<7> VectSize = { 0, 0, 1, 0, 0, 0, 0 }; bits<7> CD8_Scale = { 0, 0, 0, 0, 0, 0, 0 }; string FoldGenRegForm = ?; string EVEX2VEXOverride = ?; bit isMemoryFoldable = 1; bit notEVEX2VEXConvertible = 0; }
레코드의 첫 줄에서 ADD32rr 레코드가 8개의 클래스로부터 상속받았음을 볼 수 있습니다. 상속 계층은 복잡하지만, 각 명령에 대해 109개의 개별 필드를 지정하는 것보다 부모 클래스를 사용하는 것이 훨씬 간단합니다.
다음은 ADD32rr와 여러 다른 ADD 명령을 정의하는 데 사용된 코드 조각입니다:
defm ADD : ArithBinOp_RF<0x00, 0x02, 0x04, "add", MRM0r, MRM0m, X86add_flag, add, 1, 1, 1>;
defm 문은 ArithBinOp_RF가 multiclass임을 TableGen에 알려주며, 이는 BinOpRR_RF에서 상속하는 여러 구체 레코드 정의를 포함합니다. 이 클래스는 다시 BinOpRR에서 상속하고, 이는 ITy와 Sched에서 상속하며, 계속 이어집니다. 필드는 모든 부모 클래스로부터 상속됩니다. 예를 들어 IsIndirectBranch는 Instruction 클래스로부터 상속됩니다.