基本上編譯器的架構已經確定了,好多功能亟待實現。 閱讀全文
基本上編譯器的架構已經確定了,好多功能亟待實現。 閱讀全文
該項目簡報包含了近9個月SALVIA項目的工作進展。
項目與配置管理:
- 源代碼管理系統由SVN更換至Mercurial。
- SoftArt更名為SALVIA。工程、命名空間及相關文檔的更新將在Release之前完成。
- SALVIA啟用了LOGO。LOGO在Release之前將視效果酌情調整。
- 提交版本的LOG信息中啟用符合Issue Tracker的格式,使得版本更新可以反映到Issue中。
- 添加了完整的Unit Test工程,并啟用Auto Test系統完成黑盒和回歸測試。
- 合并了LLVM的源碼至版本庫中;LLVM升級至2.9并進行了適當修改以和Boost的類型系統相一致。
- Boost升級至1.44或以上。
- 添加了一個用于分析代碼行數的小工具。
- 更新了部分文檔。
Graphics管線:
- 繪制部分支持OpenGL。
- 對Subdivision-based Rasterizer進行了進一步優化。
- 更新了EFLIB的命名空間和文件組織。
- 支持Centroid Sampling。
- 支持2x與4xMSAA
SALVIA Shading Language(SASL):
- 使用新的Combinator-based Parser系統以替換Boost.Spirit,提升了編譯速度并降低了目標文件大小。
- 基于Boost.Wave,SASL提供了和C一致的預處理能力。
- 添加了完整的Name Mangling和函數重載的能力。
- 設計并完成了Shader與Host ABI的Memory Layout。現在Shader可以被高效的調用。
- 提供了JIT的支持。
- 提供了多編譯器的Port。
- 添加了命令行方式的Compiler。該Compiler接受源代碼并輸出為LLVM IR。
- 設計并實現了多語言(General,SIMD Shader,SISD Shader)的編譯框架。
- 提供了可測試可回歸的編譯器實現。
- 實現了下列語言要素:
- 基本標量類型及其變量定義
- 基本矢量類型及其變量定義
- 結構體定義及其變量定義
- 順序語句
- 函數聲明與定義
- 算術運算符
- 成員運算符(.)
- Swizzle與Write Mask
這個PPT主要描述了SALVIA的一些設計細節。這是關于軟件渲染器SALVIA迄今為止最詳細的文檔,也是我近四年來的主要工作和研究內容之一。感謝MSRA的龔敏敏為此文做出的審校工作,他也是此項目的Owner之一。
在線的PPT:
原PPT下載:
http://www.shnenglu.com/Files/lingjingqiu/Introduction%20to%20SALVIA.zip
文章內容包括:
1. 什么是Shader的入口函數
2. 如何生成與管線相兼容的標準C函數
3. 如何在shader code中操作圖形管線語義值(SV_*) 閱讀全文
表達式值的存儲
LLVM中基本數據類型及存儲類型
值是編譯器所需要處理的基本數據。它出現在各個角落,條件分支、表達式、返回語句。甚至是函數地址也可以被視作是值類型。
對于編譯器而言,最基本的值是整數和浮點。其他的值都可以用這兩者來表達,例如布爾和指針。如果你是從一個最基本的指令集開始寫起,那么整數和浮點的數值運算、轉換、基于整數寄存器的跳轉和地址取值是一個寄存器機的最基本操作。所有更加高級的操作,例如數組、結構體、指針、函數、對象等,都可以建立在這一基礎上。
如果一個指令系統在整數和浮點數之外,額外提供了布爾、分支、函數調用和結構體的支持,那么它與高級語言將會貼近更多,生成代碼的方式也更加簡單。
在高級語義的數據結構上,LLVM提供了相當良好的支持。它支持的原生類型(First class)包括: 各種精度的整型和浮點數,指針、向量,結構體和數組。這些類型的數據存取和運算都是有指令直接支撐,而不需要自行計算并生成更加原始的指令。
在存儲類型上,LLVM提供了Value, Argument, Alloca, GlobalVariable, Pointer五種存儲類型。Value是右值,它不可取引用,不可更改。Argument表示了函數實參,它是Value的一個派生類。所以對參數的任何更改行為實際上都是不被允許的。Alloca保存了棧地址,GlobalVariable保存了全局變量的地址,Pointer則是一般意義上的指針。
除了存儲指令,LLVM所有的指令都是針對Value的操作,并返回一個Value。所以
Var a = Alloca int
Var b = Alloca int
Var c = Alloca int
c = ADD a, b
這樣的操作,在LLVM中實際上是將a和b的地址相加,并把C從變量替換成一個左值(注意,是替換,變量的值沒有任何變化)。
在LLVM中,正確的做法應當類似于下面這樣:
a = Alloca int b = Alloca int c = Alloca int a_v = load a b_v = load b c_v = ADD a, b store c, c_v
要先將值從變量中讀出,進行操作,再保存到另外一個變量中。
表達式值的數據結構
一個的表達式參數或結果可能是左值或右值。例如++x輸入一個左值返回一個左值,而x++就返回一個右值。A+B則是需要兩個右值并返回一個右值。
一個左值可以很方便的轉化為右值,但是右值轉化成左值通常是很困難的。地址信息被丟棄了,或者它根本就是一個字面常量,都會導致一個右值將永遠是右值。將右值構造成左值的唯一辦法,就是構造臨時對象并將右值賦予左值。當這個左值被讀取時,如果臨時對象除了初始化之外從未被寫過,并且它關聯的右值依然有效,那么這個操作會被優化成直接返回那個原始的右值,從而避免臨時左值的讀寫操作。
在Clang(一個C++編譯器的前端)中對左值和右值進行了嚴格的區分。這是由于C++需要額外的處理臨時對象。臨時對象意味著盡管它有右值的語義,但是實際上是左值的存儲。這是需要將真正的左值和臨時的左值區分開,并提供特定語境下的轉化。
SASL沒有處理復雜的臨時對象問題,因此它使用了一個相對簡單的辦法來解決左右值的判定和存儲。
我們設計了一個數據結構,用于保存任何可能的值。
struct Data{ bool isRef; Value* rval; Alloca* local; GlobalVariable* global; struct Aggregated{ Data* parent; int index; } agg; };
rval用于處理Argument和右值時的情況。Local意味著它是一個局部變量,global說明它是一個全局變量,agg則用于處理structure member。Parent指向包含當前變量的聚合變量,index則指明了當前變量在聚合變量中的位次。
SASL提供了load, load_ptr 和 store 來數據的存取,而不要關心它的具體存儲類型。
左值/右值語義
在Data這個結構中,rval, local, global和agg四個值是互斥的。當然這里的我們也可以選擇union+enum的方式來表達。
首先來看,這個結構如何表達左值/右值語義。
來看isRef,這是一個標記位。它表示了data存儲的值究竟是值本身還是地址。如果是isref為真,那么data便可以被認為是一個左值。Isref為假,那么當它是rval的時候,它就是一個真正的右值了。如果是Alloca或者GlobalVariable,因為它們本身就代表了地址,那么它仍然是一個右值。如果是agg,那么要取決于它的聚合量是左值還是右值。
如果參數需要左值,那么可以直接從data拷貝,或者使用load_ptr + isRef創建一個新的右值Data。如果參數需要右值,那么可以通過load的方式獲取一個右值。
數據存取的實現
llvm::Value* load( cgllvm_sctxt* data ){ assert(data); Value* val = data->val; do{ if( val ){ break; } if( data->local ){ val = builder()->CreateLoad( data->local ); break; } if( data->global ){ val = builder()->CreateLoad( data->global ); break; } if( data.agg.parent ){ val = load( data->agg.parent ); val = builder()->CreateExtractValue( val, data->agg.index ); break; } } while(0); if( data->is_ref ){val = builder()->CreateLoad( val );} return val; } llvm::Value* load_ptr( cgllvm_sctxt* data ){ Value* addr = NULL; if( data->val ){ addr = NULL; } if( data->local ){ addr = data->local; } if( data->global ){ addr = data->global; } if( data->agg.parent ){ addr = builder()->CreateGEP( load_ptr(data->agg.parent), 0, data->arg.index ); } if( data->is_ref ){ if( !addr ){ addr = data->val; } else { addr = builder()->CreateLoad( addr ); } } return addr; } void store( llvm::Value* v, cgllvm_sctxt* data ){ Value* addr = load_ptr( data ); builder()->CreateStore( v, addr ); }
這是我為部門內部介紹C++0x(11)時所使用的PPT。
示例代碼在
http://www.shnenglu.com/Files/lingjingqiu/Cpp11SamplesGCC.zip
與
http://www.shnenglu.com/Files/lingjingqiu/Cpp11Samples.zip
如果有任何問題,歡迎跟帖詢問,我將盡可能的做出解答。
大家在公司用到的VCS,一般都是和Issue tracker / Bug tracker 關聯到一起的。
比方說,我在VCS里面填上update log:
Fixed error about animiation matrix updating. Bug 223789 updated. Review 1776. |
然后將這個change提交,此時Bug tracker上便會更新,變成這樣:
Issues: 987654 |
一般這里的Bug號,review號和Issue號都會有相應的鏈接,連接到對應的內容上以方便查閱。
那么在Google code里如何實現這一點呢?
首先,如果你在log中涉及了某個issue,并以issue nnn或者是issue #nnn 填上對應的Issue號,那么在SVN的瀏覽中,自然會產生某個鏈接,如SALVIA里面的:
Log messageSASL: Add syntax_tree_builder into syntax_tree project. Add parse_api into parser. Add parse_api into syntax tree. Issue 49 updated. |
此時,Google的Change viewer還會提供相應的超鏈接。但是當你點到Issue49之后,會發現Issue49并沒有更新當前revision的信息。這是怎么回事呢?
答案是,如果想經由Log去更新Issue tracker,必須要按照一定的格式。
完整的文章,可以參見google的幫助。
這里給大家提供一個快速教程:
首先,整個命令的格式大致如下:
$COMMAND$ issue $ISSUE ID$ $FIELD$: $FIELD$ $CONTENT$ $DESCRIPTION$ |
COMMAND一共有三種,New,Update,Fixes。含義就不說了,大家都懂。
Field,就是類似于Summary,Owner一類,在Issue Editor的界面上大家都能看到。
Description呢,一方面用于你這次更新的詳細說明,另一方面也會提交到Issue Tracker中。
注意,在Update的時候,Field也好,Description也好,至少要填一項,否則Issue tracker是不會給你更新信息的。
下面咱們來舉個例子:
Log messageSALVIA: Update issue 53 (注意,這里沒有句號) Issue 53 will be added an new comments. |
這個時候,Issue track就會更新為:
Comment 1 by project member wuye9036, Today (43 minutes ago) This issue was updated by revision r462. Issue 53 will be added an new comments. |
你看,自動更新了吧。但是注意哦。Update那條命令之前的內容,并沒有被更新到Issue tracker中。
哈哈,Have fun!
這個是我為Autodesk Technical Summit 2011,一個公開的Autodesk技術宣講會準備的PPT的5分鐘簡短介紹。在這個Presentation中,將主要討論關于光柵化的軟件渲染器的主要架構,以及在實現和優化方面的技術細節。
http://www.shnenglu.com/Files/lingjingqiu/TemplateSample.zip
代碼在這里。演示了如何講一個模板類中的普通成員函數和模板函數進行實例化,以實現分離編譯。
沒有對實例化的原理、編譯器關鍵字和參數進行詳細解釋,因此僅供對模板有基本常識的人參考。
這段代碼本來是在我所在的Team內部供同事參考并用作寫一些Demo以測試編譯器特性用的。
但是考慮到示例不牽涉到公司產品,并且對大家理解模板規則,縮短富模板代碼的編譯時間,減少中間文件大小是有一定幫助的,故在博客上放出。
如有不妥之處,請指正。
代碼在MSVC10和MINGW GCC 4.5.0下通過。
如果有希望其他示例或在其他編譯器(平臺限Linux,Mac OS,Windows;編譯器限GCC 4.0+(含4.0,下同), MSVC 2005+,Intel 11.0+)上測試不通過的,請聯系我,謝謝。
最近一段時間,SoftArt的所有更新,都集中在編譯器上。雖然沒有辦法趕上在2011年的SoftArt第一版本的alpha發布(也許會更名為Salvia,版本代號為Cryptic Era),但是整體進度還是比較理想的。今天提交了r425,這是一個比較重要的更新。到這個更新為止,編譯器在后端的所有API便基本確定了。除去以后對參數和簽名的小改動,基本結構已經成型。
Parser,AST,Semantic和Code gen都已經有了相應的原型和對應的測試。接下來基本就是堆代碼的體力活了。
接下來,有以下工作比較重要:
0. 未實現的語言特性及對應的測試用例。
1. automatic regression tests的框架。這個框架的名稱就叫fart,framework of auto regression tests的簡寫。這個Test與現有的unit tests稍有不同,他在第一次生成代碼的時候,是要人工去檢查并執行驗證的。以后所有的測試,都是將結論與藍本進行比較。
2. semantic階段的出錯處理,以及code generate階段的容錯機制。這一部分的將隨著編譯器的逐步完善而完善。
3. 并行化的代碼生成。生成以SIMD方式執行的代碼是這個編譯器開發的初衷,它也是整個編譯器功能中最重要的部分。
4. Host和Runtime部分。這一部分主要負責編譯器的初始化,內建函數的注冊,與腳本的交互等功能。在這一部分完成后,SoftArt就可以正式將SASL集成進來。
5. Shader API的設計和實現。
簡易的計劃表:
1.0(~Jun 2011):
不隨SoftArt發布。完成進度:0: >60% 1: >80% 2: >20% 3: – 4: – 5: -
2.0(~Q1 2012):
編譯器隨SoftArt發布。未集成。進度: 0: >75% 1: >90% 2: >40% 3: – 4: – 5: -
3.0(~2013):
集成進softart。進度: 0: >85% 1: >90% 2: >50% 3: – 4: >50% 5: >30%