NAME¶
perlcompile - 關於 Perl
編譯器和翻譯器的介紹
DESCRIPTION 描述¶
Perl
一直是有一個編譯器的:你的源檔案會被編譯成一種內部格式(一種語法分析樹),並且在運行前還會被優化。從5.005版本起,Perl
在發行時就帶有一個模塊可以檢查優化過的語法分析樹(該模塊稱作B模塊("B")),它被用來編寫許多有用的功能,包括一個可以將你的Perl轉成C原始碼的模塊,這樣再編譯後就可以得到一個可執行的檔案了。
"B"
模塊提供了訪問語法分析樹的方法,
其它的一些模塊(「後端」)則對這個樹進行操作。一些把它(語法樹)以字節碼的形式輸出,還有以C原始碼形式的輸出的,後者以半可讀的文本形式輸出的。另一些遍歷整棵語法樹以建立一個關於所使用的子程式,格式及變量的交叉引用表。還有另外一些檢查你的代碼,看看有沒有模棱兩可的構造。另一些則重新將語法樹導出成Perl代碼,可以起代碼美化或是消除混亂的代碼的作用。
因為 "B"
模塊的最初目的是提供一種能將Perl程式轉為對應C代碼的方法,接著就能把它變成可執行檔案了,所以
"B"
模塊和它的那些後端模塊就被認為是「編譯器」了,即使它們實際上沒有做任何編譯方面的事。這個編譯器的各個部分精確的說應該是個「翻譯器」,或者一個「檢視器」,但是用Perl的人們想要一個「編譯選項」而不是一個叫做「檢視器」的小玩藝。你能怎麼辦呢?
這篇文章的主要內容是講Perl編譯器的用法:它包含的模塊,怎樣使用那些最重要的後端模塊,它們有什麼問題,如何讓它們工作。
Layout 布局
編譯器的後端放在 "B::"
裏面,而前端(就是你,編譯器的使用者,有時候要與之交互的)是
O 模塊。一些後端(如
"B::C"))提供了一些程式(如
perlcc)來隱藏模塊的復雜性。
這裏是一些值得知道的重要後端,並附有它們目前的狀態,用0到10的整數表示。(狀態0表示目前該部分功能只是有一個框架,還沒有實現;狀態10則表示如果還有Bug的話,我們會感到很奇怪的):
- B::Bytecode
- 將語法樹存成機器相關的格式,可供BtyeLoader模塊可以在以後重新裝入。狀態:5(一些部分可以工作,一些不可以,還有一些還沒有測試)
- B::C
- 創建C代碼檔案,其中包括了重建語法樹和恢復解釋器的代碼。狀態:6(許多情況下可以正常工作,包括使用了Tk的程式)。
- B::CC
- 按照語法樹中運行期代碼的路徑創建C代碼檔案。這是最像
Perl - C
翻譯器的一個,但是它生成的代碼幾乎是不能看懂的,因為它把語法樹翻譯成了一個巨大的switch結構來操作Perl中的結構。最終的目的是在perl程式中給出足夠的類型信息後,可以將
perl
數據結構的操作轉換為
c 級別的數據結構,對
int 和 float
的操作。狀態:5
(有些可以工作,包括不復雜的
Tk 示例).
- B::Lint
- 當發現你的代碼中有模棱兩可的構造時會發出警告。狀態:6(許多情況下可以正常工作,僅僅在很少數的領域內它會停止工作)。
- B::Deparse
- 重新生成Perl代碼,試著把代碼用一致的格式寫出來。狀態:8(它工作得很好,只是會略去一些晦澀難懂的部分)。
- B::Xref
- 生成關於申明和關於變量以及子程式的使用情況的報告。狀態:8(它工作得很好,只是仍有一點延遲方面的bugs)。
Using The Back Ends 使用後端¶
接下來的部分介紹怎樣使用各種各樣的編譯器後端。介紹的順序按照後端的成熟程度排列,所以最為穩定的,經過了驗証的後端會最先介紹,還在試驗中和沒有完成的後端就放到後面描述了。
O模塊預設讓
-c
開關有效,這防止Perl在編譯完代碼後運行程式。這也是為什麼所有的後端在產生任何輸出前都會列印一句:
myperlprogram syntax OK
The Cross Referencing Back End
交叉引用後端
交叉引用後端(B::Xref)生成一個關於你的程式的報表,把各個申明以及子程式,變量(包括格式)的使用情況存入檔案中去。舉例來說,這有一段摘自對pod2man程式分析後生成的報表(該程式是Perl自帶的一個例程):
Subroutine clear_noremap
Package (lexical)
$ready_to_print i1069, 1079
Package main
$& 1086
$. 1086
$0 1086
$1 1087
$2 1085, 1085
$3 1085, 1085
$ARGV 1086
%HTML_Escapes 1085, 1085
這裏展示了"clear_noremap"
子程式中變量的使用情況。就像變量
$ready_to_print 是
my() (詞法)
的一個變量,在第1069行被引入(
原文用的詞是introduced,也就是在
my()
中第一次被定義的意思
),然後在第1079行該變量被使用了。從主包(main
package)中來的變量 $&
又在第1086行被使用,
等等。
行號前面可能會有一個字母作為前綴,它們的意思是:
- i
- 變量首次被引入
(在my()中申明) 。
- &
- 子程式或者方法的引用。
- s
- 定義的子程式。
- r
- 定義的格式。
交叉引用中最為有用的選項就是把報表存入不同的檔案,例如要把關於
myperlprogram 的報表存入檔案
report 中:
$ perl -MO=Xref,-oreport myperlprogram
The Decompiling Back End 反編譯後端
反編譯後端將把你的Perl語法樹重新變成原始碼。生成的原始碼會按照某種格式組織,所以這個後端可以用來消除代碼中的混亂部分。此後端的基本使用方法如下:
$ perl -MO=Deparse myperlprogram
你也許馬上會發現Perl並不知道如何給你的代碼分段。你要自己手動添入新行來把這大斷的代碼分開。然而現在,讓我們看看代碼只有一行時情況怎樣,這個後端會做些什麼:
$ perl -MO=Deparse -e '$op=shift⎪⎪die "usage: $0
code [...]";chomp(@ARGV=<>)unless@ARGV; for(@ARGV){$was=$_;eval$op;
die$@ if$@; rename$was,$_ unless$was eq $_}'
-e syntax OK
$op = shift @ARGV ⎪⎪ die("usage: $0 code [...]");
chomp(@ARGV = <ARGV>) unless @ARGV;
foreach $_ (@ARGV) {
$was = $_;
eval $op;
die $@ if $@;
rename $was, $_ unless $was eq $_;
}
這個後端也有幾條選項控制生成的代碼,舉例說,你可以把縮進的尺寸設在4(最大)到2之間:
$ perl -MO=Deparse,-si2 myperlprogram
-p
開關控制在常常可以不加圓括號的地方加上它們:
$ perl -MO=Deparse -e 'print "Hello, world\n"'
-e syntax OK
print "Hello, world\n";
$ perl -MO=Deparse,-p -e 'print "Hello, world\n"'
-e syntax OK
print("Hello, world\n");
要知道更多,請參考
B::Deparse
Lint 後端
lint 後端 (B::Lint)
檢察程式中不好的程式風格。一個程式認為的不好風格可能對另外一個程式員來說是用起來很有效的工具,所以有選項讓你設定哪些東東將會受到檢查。
要運行一個風格檢查器檢察你的代碼:
$ perl -MO=Lint myperlprogram
要取消對上下文和沒有定義的子程式的檢查:
$ perl -MO=Lint,-context,-undefined-subs myperlprogram
要知道更多的選項信息,請看
B::Lint
The Simple C Back End 簡化的C後端
這個模塊用來把你的Perl程式的內部編譯狀態存儲到一個C代碼檔案中去,而生成的C代碼就可以被特定平台上的C編譯器轉換成一個可執行檔案了。最後的程式還會和Perl解釋器的庫檔案靜態鏈接起來,所以它不會節省你的磁碟空間(除非你的Perl是用共享的庫檔案創建的)或是程式大小,然而,另一方面,程式啟動起來會快一些。
"perlcc"
工具預設是生成以下的可執行檔案。
perlcc myperlprogram.pl
The Bytecode Back End 字節碼後端
這個模塊只有在你能夠找到一種方法來裝入並運行它生成的字節碼時才會顯得有用。ByteLoader模塊提供了這項功能。
要把Perl轉換成可執行的字節碼,你可以使用
"perlcc" 的 "-B" 開關:
perlcc -B myperlprogram.pl
字節碼是和機器類型無關的,所以一旦你編譯了一個模塊或是程式,它就可以像Perl原始碼一樣具有可移植性。(假設那個模塊或者程式的使用者有一個足夠新的Perl解釋器來對字節碼進行解碼)
有一些選項用來控制要生成的字節碼的性質和關於優化方面的參數,要知道這些選項的詳細情況,請參考
B::Bytecode
The Optimized C Back End 優化的C後端
優化的C後端按照語法樹中運行期代碼的路徑將你的Perl程式轉換成等效的(但是被優化了的)C代碼檔案。這個C程式會直接對Perl的數據結構進行操作,而且也會鏈接Perl的解釋器的庫檔案,以支持
eval(), "s///e", "require" 等等。
"perlcc" 工具使用 -O
開關生成這種可執行檔案。要編譯一個Perl程式(以".pl"
或者".p" 結尾):
perlcc -O myperlprogram.pl
從Perl模塊創建一個共享庫檔案(以
".pm" 結尾):
perlcc -O Myperlmodule.pm
知道更多,請參考 perlcc 和
B::CC.
Module List for the Compiler Suite 編譯套件的模塊列表¶
- B
- 這個模塊是一個自省的(introspective,用Java的術語說就是「reflective」)模塊,允許Perl程式審視自己的內部。後端模塊都是通過這個模塊來訪問語法分析樹的。而你,後端模塊的使用者,就不用和B模塊打交道了。
- O
- 這個模塊是編譯器的那些後端的前端,一般像這樣進行調用:
$ perl -MO=Deparse myperlprogram
這與在這個Perl程式中使用
"use O 'Deparse'" 相同。
- B::Asmdata
- 這個模塊被 B::Assembler
模塊使用,而 B::Assembler
又接著被 B::Bytecode
模塊使用,B::Bytecode中有一個字節碼形式存放的語法分析樹以便以後裝入。B::Asmdata自己並不算是一個後端,也許說它是後端的一個組件比較好。
- B::Assembler
- 這個模塊可以將語法樹轉為適合存儲和恢復的數據形式。它本身不是一個後端,但是算是某個後端的一個組件。
assemble
程式用它來生成字節碼。
- B::Bblock
- 這個模塊被 B::CC
後端使用。它被用來運行「基本塊」。一個基本塊就是一段從頭到尾的操作,中間是不可能停下來或出現分支的。
- B::Bytecode
- 這個模塊可以由程式的語法樹生成字節碼。生成的字節碼會被寫入到檔案中,以後還能被重新恢復成語法樹。總的目標就是為了只進行一次費時的程式編譯工作,然後把解釋器的狀態存入檔案中,運行程式時再把狀態從檔案中恢復。
具體的用法請參考 "The
Bytecode Back End" 。
- B::C
- 這個模塊按照語法樹和其他一些解釋器的內部數據結構生成C代碼。然後你再編譯生成的C代碼,就可以得到一個可執行檔案了。運行時這個可執行檔案會恢復解釋器和內部的數據結構來轉動程式。要知道細節請參考
"The Simple C Back End"。
- B::CC
- 這個模塊按照你程式中的操作生成C代碼。不像
B::C
模塊只是把解釋和它的狀態存入C程式中,
B::CC
模塊生成的是不包含解釋器的C
程式,所以用 B::CC
翻譯的C
程式運行速度比一般的解釋執行的程式速度要快,具體用法請參考
"The Optimized C Back End" 。
- B::Concise
- 這個模塊輸出一個簡潔的
(但是完整的) Perl
分析樹。它的輸出比
B::Terse 或者 B::Debug
的結果更容易定制
(並且也可以模仿它們)。這個模塊對書寫自己的後端,或者學習
Perl
實現的人有用。它對一般的程式員沒有用處。
- B::Debug
- 這個模塊把Perl語法分析樹非常詳細地輸出到標準輸出上去。這對正在編寫自己的後端程式,或正在深入Perl內部機制的人們來說是非常有用的。對普通程式員來說則沒什麼用。
- B::Deparse
- 這個模塊將編譯了的語法樹反向分析得出Perl原始碼,這在調試或是反編譯他人代碼的時候會是非常有用的。另外讓它為你自己的代碼做一些美化工作也是可以的。要知道細節請參考
"The Decompiling Back End"。
- B::Disassembler
- 這個模塊把字節碼恢復成語法樹,它本身不是一個後端,而是某個後端的一個組件。它會被和字節碼在一起的
disassemble 程式使用。
- B::Lint
- 這個模塊審視你的代碼編譯後的格式,並且找到那些容易讓人皺眉,卻又不至於引起警告的地方。舉例來說,使用一個標量內容(scalar
context)的數組,而不顯式地申明成
"scalar(@array)"
。這種情況是會被 Lint
標示出來的。要知道細節請參考
"The Lint Back End"。
- B::Showlex
- 這個模塊列印出 my()
中的變量在函數或是檔案中的使用情況,以得到一份關於
my()
中的變量在定義於檔案
myperlprogram 中的子程式 mysub()
中的使用情況的列表:
$ perl -MO=Showlex,mysub myperlprogram
要得到一份關於 my()
中的變量在檔案myperlprogram中的使用情況的列表:
$ perl -MO=Showlex myperlprogram
[BROKEN]
- B::Stackobj
- 這個模塊被 B::CC
模塊調用。它本身不是後端,但是是某個後端的一個組件。
- B::Stash
- 這個模塊被 perlcc
程式調用,而perlcc可以把一個模塊編譯成可執行檔案。B::Stash
把程式使用的符號表列印出來,並被用來阻止
B::CC 為 B::* 或是 O 模塊生成C
代碼。它本身不是後端,但是是某個後端的一個組件。
- B::Terse
- 這個模塊用來列印語法樹的內容,但是信息不會有B::Debug列印的那麼多。對比來說,"print
"Hello, world."" 會讓 B::Debug
產生96行輸出, 但是
B::Terse只會有6行。
這個模塊對正在編寫自己的後端程式,或正在深入Perl內部機制的人們來說是非常有用的。對普通程式員來說則沒什麼用。
- B::Xref
- 這個模塊列印一個報表列出在程式中哪裏定義和使用了哪些變量,子程式或格式,報表還會列出程式裝入的模塊。要知道詳細的使用方法,請參考
"The Cross Referencing Back End" 。
KNOWN PROBLEMS 已知的問題¶
簡單 C
後端目前只保存以字符和數字命名的類型說明
優化的 C
後端會為一些不該為之輸出的模塊(比如說
DirHandle)輸出代碼。而且它不太可能正確地處理正在執行的子程式外部的goto語句(goto
&sub is OK)。目前 "goto LABEL"
語句在這個後端中完全不會工作。他還會生成讓C
編譯器頭痛無比的巨大的初始化函數。如果把這個初始化函數分割開是能得到比目前更好的效果的。另外的問題包括:處理無符號的數學問題時不能正確工作;一些操作碼如果按照預設的操作碼機制處理也會有非正常的結果。
BEGIN{}
塊會在編譯你的代碼的時候被執行。所有的在BEGIN{}
中初始化的外部狀態,如打開的檔案,初始的數據庫連結等等,會有不正確的表現。為了解決這個問題,Perl中又提供了一個
INIT{}
塊來對應程式編譯之後,正式運行之前要執行的那段代碼。執行的順序是:BEGIN{},
(後端編譯程式可能這時會保存狀態),
INIT{}, 程式運行, END{}。
AUTHOR 作者¶
這篇文章最初是由 Nathan
Torkington
編寫,現在由郵件列表(perl5-porters@perl.org.)維護
郭銳(sunny65535) <sunny65535@263.net>