P4 Compiler 系列 – 建立一個 backend compiler

5 月 12, 2018

p4c (https://github.com/p4lang/p4c) 提供了 P4 Compiler 基本需要的函式庫如 IR, Frontend 及 Midend 等。

若要自行撰寫一個 Backend compiler,則只需要引用 p4c 中提供的函式庫即可。

p4c 專案裡提供了一套簡易的方法讓他人能夠在不更動 p4c 內容的方法來新增自定義的 backend(當然,若是真的有意義的 backend 也是可以提出 pull request 讓他們 merge 到該 repository 當中)

自定義的 backend copmiler 大致上架構如下:

$ tree three-table-arch
three-table-arch
├── CMakeLists.txt
├── LICENSE
├── README.md
├── driver
│   └── p4c.ftt.cfg
├── fixed_three_tables.cpp
├── fixed_three_tables.h
├── main.cpp
└── p4include
    └── FixedThreeTables.p4

檔案說明:

  • CMakeLists.txt: p4c 使用 cmake 來產生相關的 Makefile,需要預先定義編譯的規則,下面會說明
  • driver: 放置跟 p4c 有關的設定檔(python),已這個例子來說僅需要一個 p4c.ftt.cfg 即可
  • *.cpp, *.h: Backend 相關的程式碼
  • p4include: P4 架構定義檔案,由前一篇文章所描述

p4c.ftt.cfg

這一個檔案主要是給 p4c 看的,著要的功能是告訴 p4c 在編譯一個 P4 檔案時需要做什麼樣的步驟,以及他所對應的 target 及 architecture 為何。

這一個檔案中會定義一個 Backend 的 Python Class,並繼承 BackendDriver

class FTTBackend(BackendDriver):
    def __init__(self, target, arch, argParser):
        BackendDriver.__init__(self, target, arch, argParser)

        # commands
        self.add_command('preprocessor', 'cc')
        self.add_command('compiler',
                         os.path.join(os.environ['P4C_BIN_DIR'],'p4c-ftt'))
        # order of invocation
        self.enable_commands(['preprocessor', 'compiler'])

        # options
self.add_command_option('preprocessor', "-E -x c")

初始話時我們可以定義會使用的指令或而外的執行檔,舉例來說,我們可以藉由 ccgcc 來將 P4 裡面的 preprocessor 處理掉並產生單一檔案,上面的城市其實是基於 Bmv2Backend 去做修改的,可以看到這一個 Backend 設定包含了以 cc 為主的 preprocessor 以及 p4c-ftt 為主的 copmiler,而他們都可以在新增而外的參數。

enable_commands 用來表示執行這些指令的順序。

接著是覆寫原有的 process_command_line_options 函式,這部份也是基於 Bmv2Backend 所更改,這一個 function 會將執行時所帶的參數以自己想要的方式帶到 preprocessor 以及 compiler 中。

class FTTBackend(BackendDriver):
    # .........

    # Override from BackendDriver
    def process_command_line_options(self, opts):
        BackendDriver.process_command_line_options(self, opts)

        # process the options related to source file
        basepath = "{}/{}".format(opts.output_directory, self._source_basename)
        # preprocessor
        self.add_command_option('preprocessor', "-o")
        self.add_command_option('preprocessor', "{}.p4i".format(basepath))
        self.add_command_option('preprocessor', self._source_filename)

        # compiler
        self.add_command_option('compiler', "-o")
        self.add_command_option('compiler', "{}.json".format(basepath))
self.add_command_option('compiler', "{}.p4i".format(basepath))

最後是產生一個 Backend 的實體並且註冊到 config 當中:

fixed_three_tables_target = FTTBackend('ftt', 'ftt', argParser)
config.target.append(fixed_three_tables_target)

CMakeLists.txt

在 p4c 專案的 README 中有更詳盡的說明,這邊僅提供簡介

在 CMakeLists 中可以透過 set 來定義出需要的變數,這邊我們首先要定義有哪些原始碼需要被編譯:

set (FTT_ARCH_SRCS
  main.cpp
  fixed_three_tables.cpp
  )

set (FTT_ATCH_HDRS
  fixed_three_tables.h
  )

已這邊的例子來說,我們會需要編譯 3 個檔案,包含一個 main(main.cpp)以及兩個 backend 的實作(fixed_three_tables.cpp/h)

接下來官方建議這邊把所有的檔案透過 c++ linter 預先做個檢查已維護程式一致風格:

add_cpplint_files (${CMAKE_CURRENT_SOURCE_DIR} "${FTT_ARCH_SRCS};${FTT_ARCH_HDRS}")

接下來定義編譯時期所要需要的 dependency 以及目標:

add_executable(p4c-ftt ${FTT_ARCH_SRCS})
target_link_libraries (p4c-ftt ${P4C_LIBRARIES} ${P4C_LIB_DEPS})
add_dependencies(p4c-ftt genIR)

基本上先定義好目標,這編定好說編譯會透過剛剛定義好的 source 去產生 p4c-ftt 這一個可執行檔案

而這一個檔案會連結到 p4c(P4C_LIBRARIES)以及 p4c 需要的函式庫(P4C_LIB_DEPS)

最後是要確保 IR 會在整個編譯之前,主要是因為不同的 backend 有可能會有自定義的 IR(e.g. bmv2)而整個 IR 會需要重新產生才能給 Frontend/Mid/Backend 使用。

編譯完之後是安裝的步驟,也就是執行 make install 所需要的步驟,大致上分成以下幾個:

1. 把 binary 複製到預設值行的路徑中

install (TARGETS p4c-ftt RUNTIME DESTINATION ${P4C_RUNTIME_OUTPUT_DIRECTORY})

2. 將我們定義好的 FixedThreeTables.p4 放置到預設 p4c 目錄中

install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/p4include DESTINATION ${P4C_ARTIFACTS_OUTPUT_DIRECTORY})

3. 將 p4c.ftt.cfg 放置到 p4c 設定檔目錄中

install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/driver/p4c.ftt.cfg DESTINATION ${P4C_ARTIFACTS_OUTPUT_DIRECTORY}/p4c_src)

官方文件有提到說可以新增一些跟測試有關的設定,這邊就不多加以描述了。

在 CMakeLists 檔案最後可以加一些自定的命令,例如把編譯好的執行檔案 link 到 build 資料夾當中

add_custom_target(linkbinary
  COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/p4c-ftt ${P4C_BINARY_DIR}/p4c-ftt
)

add_dependencies(p4c_driver linkbinary)

編譯流程

完成 CMakeList 之後,就可以透過指令去編譯。

$ cd p4c
$ mkdir -p extensions
$ ln -s three-table-arch extensions/three-table-arch
$ ./bootstrap
$ cd build && make -j4
$ make install

基本上將自己定義好的 backend 資料夾透過 symbolic link 放置到 extensions 資料夾中即可。

編譯好之後,就會發現 build 資料夾下會多一個 p4c-tff 執行檔。

而由於這邊有先撰寫好 config 檔案,所以可以使用 p4c 來進行編譯:

$ p4c -b ftt -a ftt test.p4
Hello world

由於目前 main.cpp 裡面沒有寫任何東西,執行時只會輸出 Hello world 文字。

而如果要知道他實際做了什麼,可以加上 -### 去看他會執行哪些指令:

$ p4c -### -b ftt -a ftt test.p4
preprocessor:
cc -E -x c -I /usr/local/share/p4c/p4include -o ./test.p4i test.p4
compiler:
/usr/local/bin/p4c-ftt -I /usr/local/share/p4c/p4include --p4v=16 -o ./test.json ./test.p4i

相關實作會在接下來幾個章節說明。

相關資料

p4c document: https://github.com/p4lang/p4c/tree/master/docs

本篇的範例程式

範例程式會隨著已完成的教學文件更新:
https://github.com/TakeshiTseng/p4c-ftt

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料