IntelliJ Plugin for TableGen


IntelliJ plugin kotlin java TableGen llvm

I recently been working on creating a LLVM backend for the Z80 CPU architechture. (A blog series to come) In the process of describing the architechture, LLVM use a DSL called TableGen Target Description Language to describe the target architechture. Everything from registers, instructions to calling conventions are first described in this TableGen format. Although there are existing plugins for VSCode, Vim, and Emacs, my main IDE for working on the project is CLion. Thus I would like to be able to have some simple syntax highlighting, code commenting, and code completion if possible.

While this plugin will probably only benifit the few who develops LLVM backends, I thought documenting the steps and caviats of creating an IntelliJ plugin might help others.

Preface

Before I dive into the guide, I just want to mention that there is an excellent guide from Jetbrains that cover the basics of setting up a project etc. I recommend reading the documentation to help you get started.

This post will be updated as I progress through the project.

Architecture

Let’s take a look at how a language plugin is structure in IntelliJ. At the very top, you have a plugin.xml file which helps the IDE to register your plugin. As for a language plugin, it is supported by the Jetbrains Grammar Kit lexer parser toolkit. It is essentially a beefed up version of JFlex and yacc. Once you have a lexer and parser defined, you can simply register a file type with the parser this will be a very basic language support plugin. At this point, you can begin to add custom classes for syntax highlighting based on tokens.

TableGen Language

As this DSL is quite obscure, I will attach my bnf file used by GrammarKit for you reference. The syntax of TableGen is highly inspired by C++ and Basic.

The latest version used in the plugin is availiable on Github: codetector1374/tablegen-intellij

Sample TableGen File
include "TL45Operators.td"


class InstTL45<dag outs, dag ins, bits<5> binOpcode, string opcodestr, string argstr, list<dag> pattern> : Instruction
{
  field bits<32> Inst;

  let Namespace = "TL45";
  dag OutOperandList = outs;
  dag InOperandList = ins;
  let AsmString = opcodestr # "\t" # argstr;
  let Pattern = pattern;
  let Size = 4; //???
  let Inst{31-27} = binOpcode;

  // SoftFail is a field the disassembler can use to provide a way for
  // instructions to not match without killing the whole decode process. It is
  // mainly used for ARM, but Tablegen expects this field to exist or it fails
  // to build the decode table.
  field bits<32> SoftFail = 0;
}


class TL45PseudoInstr<dag outs, dag ins, string opcodestr, string argstr, list<dag> pattern> : InstTL45<outs, ins, 0, opcodestr, argstr, pattern> {
  let isPseudo = 1;
}

// Pseudo instructions
class Pseudo<dag outs, dag ins, list<dag> pattern, string opcodestr = "", string argstr = "">
    : InstTL45<outs, ins, 0, opcodestr, argstr, pattern> {
  let isPseudo = 1;
  let isCodeGenOnly = 1;
}

// 5 - op code  [31-27]
// 3 - mode     [26-24] {imm?, hilo, signed}
// 4 - dr       [23-20]
// 4 - sr1      [19-16]
// 4 - sr2      [15-12]  OR  16 - immediate [15 - 0]
// 12 - padding [11-0]       ...

class RRRTypeInstr<bits<5> opcode, string opcodestr, list<dag> pattern> : InstTL45<(outs GRRegs:$dr), (ins GRRegs:$sr1, GRRegs:$sr2), opcode, opcodestr, "$dr, $sr1, $sr2", pattern> {
  bits<4> dr;
  bits<4> sr1;
  bits<4> sr2;
  bits<3> mode = 0b000;

  let Inst{26-24} = mode;
  let Inst{23-20} = dr;
  let Inst{19-16} = sr1;
  let Inst{15-12} = sr2;
  let Inst{11-0} = 0; // padding
  // let isCommutable = 1;
}