V8 Torque 使用者手冊
V8 Torque 是一種語言,讓參與 V8 專案的開發人員能夠表達 VM 的變更,重點在於變更 VM 的「意圖」,而不是專注於不相關的實作細節。此語言的設計夠簡單,可輕鬆將 ECMAScript 規格 直接轉譯為 V8 中的實作,但功能強大到足以穩健地表達低階 V8 最佳化技巧,例如根據特定物件形狀的測試建立快速路徑。
Torque 對 V8 工程師和 JavaScript 開發人員來說很熟悉,它結合了類似 TypeScript 的語法,讓撰寫和理解 V8 程式碼更輕鬆,以及反映 CodeStubAssembler
中已普遍概念的語法和類型。Torque 透過強大的類型系統和結構化控制流程,確保正確性。Torque 的表達力足夠表達幾乎所有 目前在 V8 內建函式中找到 的功能。它也與以 C++ 撰寫的 CodeStubAssembler
內建函式和 巨集
具有高度互通性,讓 Torque 程式碼可以使用手寫 CSA 功能,反之亦然。
Torque 提供語言結構來表示 V8 實作中語意豐富的高階小片段,而 Torque 編譯器會使用 CodeStubAssembler
將這些小片段轉換成高效的組譯碼。Torque 的語言結構和 Torque 編譯器的錯誤檢查都能確保正確性,而這在以前直接使用 CodeStubAssembler
時是費力且容易出錯的。傳統上,V8 工程師必須具備大量的專業知識才能使用 CodeStubAssembler
撰寫最佳化程式碼,而且這些知識大多從未正式記載在任何書面文件中,因此實作中很容易出現微妙的陷阱。沒有這些知識,撰寫高效內建函式的學習曲線非常陡峭。即使具備必要的知識,不顯而易見且不受控的陷阱也常常導致正確性或 安全性 錯誤。有了 Torque,Torque 編譯器可以自動避免和辨識許多這些陷阱。
入門 #
大多數以 Torque 撰寫的原始碼都提交到 V8 儲存庫中的 src/builtins
目錄,檔案副檔名為 .tq
。V8 堆疊配置類別的 Torque 定義與其 C++ 定義並列,在 .tq
檔案中,其名稱與 src/objects
中對應的 C++ 檔案相同。實際的 Torque 編譯器可以在 src/torque
中找到。Torque 功能的測試提交到 test/torque
、test/cctest/torque
和 test/unittests/torque
中。
為了讓您體驗這門語言,我們來撰寫一個 V8 內建函式,用來印出「Hello World!」。為此,我們會在測試案例中新增一個 Torque 巨集
,並從 cctest
測試架構中呼叫它。
首先開啟 test/torque/test-torque.tq
檔案,並在結尾處新增以下程式碼(但要在最後一個 }
之前)
@export
macro PrintHelloWorld(): void {
Print('Hello world!');
}
接著開啟 test/cctest/torque/test-torque.cc
,並新增以下測試案例,它會使用新的 Torque 程式碼來建置一個程式碼存根
TEST(HelloWorld) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate, 0);
TestTorqueAssembler m(asm_tester.state());
{
m.PrintHelloWorld();
m.Return(m.UndefinedConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), 0);
ft.Call();
}
然後 建置 cctest
可執行檔,最後執行 cctest
測試來印出「Hello world」
$ out/x64.debug/cctest test-torque/HelloWorld
Hello world!
Torque 如何產生程式碼 #
Torque 編譯器並不會直接建立機器碼,而是會產生 C++ 程式碼,呼叫 V8 現有的 CodeStubAssembler
介面。CodeStubAssembler
使用 TurboFan 編譯器 的後端來產生有效率的程式碼。因此,Torque 編譯需要多個步驟
gn
建置會先執行 Torque 編譯器。它會處理所有*.tq
檔案。每個 Torque 檔案path/to/file.tq
會產生以下檔案path/to/file-tq-csa.cc
和path/to/file-tq-csa.h
,包含產生的 CSA 巨集。path/to/file-tq.inc
,包含在對應的標頭檔path/to/file.h
中,包含類別定義。path/to/file-tq-inl.inc
,包含在對應的內嵌標頭檔path/to/file-inl.h
中,包含類別定義的 C++ 存取函式。path/to/file-tq.cc
,包含產生的堆積驗證器、印表機等。
Torque 編譯器還會產生其他各種已知的
.h
檔案,供 V8 建置使用。gn
建置會將步驟 1 中產生的-csa.cc
檔案編譯成mksnapshot
可執行檔。當
mksnapshot
執行時,V8 的所有內建函式都會產生並封裝到快照檔案中,包括在 Torque 中定義的函式,以及任何使用 Torque 定義功能的其他內建函式。建置 V8 的其餘部分。所有 Torque 編寫的內建函式都可以透過連結到 V8 的快照檔案存取。它們可以像任何其他內建函式一樣被呼叫。此外,
d8
或chrome
可執行檔也會直接包含與類別定義相關的產生編譯單元。
以圖形方式表示,建置流程如下所示
Torque 工具 #
Torque 提供了基本的工具和開發環境支援。
- Torque 有 Visual Studio Code 外掛程式,它使用自訂語言伺服器來提供定義跳轉等功能。
- 還有一個格式化工具,應該在變更
.tq
檔案後使用:tools/torque/format-torque.py -i <filename>
涉及 Torque 的建置疑難排解 #
為何需要了解這一點?了解 Torque 檔案如何轉換為機器碼很重要,因為在將 Torque 轉譯為嵌入快照中的二進位元組的不同階段,可能會出現不同的問題(和錯誤)
- 如果 Torque 程式碼(例如
.tq
檔案)有語法或語意錯誤,Torque 編譯器會失敗。V8 建置會在此階段中止,而且您不會看到建置後續部分可能揭露的其他錯誤。 - 一旦您的 Torque 程式碼語法正確,並通過 Torque 編譯器(或多或少)嚴格的語意檢查,
mksnapshot
的建置仍然可能會失敗。這最常發生在.tq
檔案中提供的外部定義不一致的情況下。在 Torque 程式碼中標記為extern
關鍵字的定義,會向 Torque 編譯器發出訊號,表示所需功能的定義可以在 C++ 中找到。目前,.tq
檔案中的extern
定義與那些extern
定義所引用的 C++ 程式碼之間的連結很鬆散,而且在 Torque 編譯時不會驗證該連結。當extern
定義不匹配(或在最微妙的情況下遮罩)它們在code-stub-assembler.h
標頭檔案或其他 V8 標頭檔案中存取的功能時,mksnapshot
的 C++ 建置會失敗。 - 即使
mksnapshot
成功建置,它也可能在執行期間失敗。這可能是因為 Turbofan 無法編譯產生的 CSA 程式碼,例如因為 Torquestatic_assert
無法由 Turbofan 驗證。此外,在快照建立期間執行的 Torque 提供的內建函式可能會有錯誤。例如,由 Torque 編寫的內建函式Array.prototype.splice
會在 JavaScript 快照初始化程序中呼叫,以設定預設 JavaScript 環境。如果實作中有錯誤,mksnapshot
會在執行期間崩潰。當mksnapshot
崩潰時,有時呼叫mksnapshot
並傳遞--gdb-jit-full
旗標會很有用,這會產生額外的除錯資訊,提供有用的內容,例如gdb
堆疊追蹤中 Torque 產生的內建函式的名稱。 - 當然,即使 Torque 編寫的程式碼通過
mksnapshot
,它仍然可能是有錯誤的或會崩潰。將測試案例新增到torque-test.tq
和torque-test.cc
是確保您的 Torque 程式碼執行您實際預期的動作的好方法。如果您的 Torque 程式碼最終在d8
或chrome
中崩潰,--gdb-jit-full
旗標再次非常有用。
constexpr
:編譯時間相對於執行時間 #
了解 Torque 建置程序對於了解 Torque 語言中的核心功能:constexpr
也很重要。
Torque 允許在執行時間(例如當 V8 內建函式作為執行 JavaScript 的一部分執行時)評估 Torque 程式碼中的表達式。但是,它也允許在編譯時間執行表達式(例如作為 Torque 建置程序的一部分,且在 V8 函式庫和 d8
可執行檔建立之前)。
Torque 使用 constexpr
關鍵字來表示必須在建置時間評估表達式。它的用法有點類似於 C++ 的 constexpr
:除了從 C++ 借用 constexpr
關鍵字和一些語法之外,Torque 也類似地使用 constexpr
來表示編譯時間和執行時間評估之間的區別。
不過,Torque 的 constexpr
語意有一些細微的差異。在 C++ 中,constexpr
表達式可以由 C++ 編譯器完全評估。在 Torque 中,constexpr
表達式無法由 Torque 編譯器完全評估,而是對應到 C++ 類型、變數和表達式,這些類型、變數和表達式可以在執行 mksnapshot
時完全評估(而且必須評估)。從 Torque 撰寫者的角度來看,constexpr
表達式不會產生在執行階段執行的程式碼,因此在這個意義上,它們是編譯時間的,即使它們在技術上是由 Torque 外部的 C++ 程式碼評估,而 mksnapshot
會執行該程式碼。因此,在 Torque 中,constexpr
本質上表示「mksnapshot
時間」,而不是「編譯時間」。
結合泛型,constexpr
是強大的 Torque 工具,可用於自動產生多個非常有效率的特殊內建函數,這些函數彼此之間的差異在於少數具體的細節,而 V8 開發人員可以事先預期這些細節。
檔案 #
Torque 程式碼封裝在個別的來源檔案中。每個來源檔案都包含一系列宣告,這些宣告本身可以選擇包覆在名稱空間宣告中,以區分宣告的名稱空間。以下的語法說明可能已過時。真實來源是 Torque 編譯器中的語法定義,它使用無關情境的語法規則撰寫。
Torque 檔案是一系列宣告。可能的宣告列在 torque-parser.cc
中。
名稱空間 #
Torque 名稱空間允許宣告出現在獨立的名稱空間中。它們類似於 C++ 名稱空間。它們允許您建立在其他名稱空間中不會自動顯示的宣告。它們可以巢狀,而巢狀名稱空間中的宣告可以存取包含它們的名稱空間中的宣告,而不需要限定。未明確出現在名稱空間宣告中的宣告會放入共用的全域預設名稱空間中,所有名稱空間都可以看到該名稱空間。名稱空間可以重新開啟,讓它們可以在多個檔案中定義。
例如
macro IsJSObject(o: Object): bool { … } // In default namespace
namespace array {
macro IsJSArray(o: Object): bool { … } // In array namespace
};
namespace string {
// …
macro TestVisibility() {
IsJsObject(o); // OK, global namespace visible here
IsJSArray(o); // ERROR, not visible in this namespace
array::IsJSArray(o); // OK, explicit namespace qualification
}
// …
};
namespace array {
// OK, namespace has been re-opened.
macro EnsureWriteableFastElements(array: JSArray){ … }
};
宣告 #
類型 #
Torque 是強類型語言。它的類型系統是它提供的許多安全性與正確性保證的基礎。
對於許多基本類型,Torque 實際上並不知道很多關於它們的資訊。相反地,許多類型僅透過明確的類型對應關係與 CodeStubAssembler
和 C++ 類型鬆散地結合,並依賴 C++ 編譯器來執行該對應關係的嚴謹性。這些類型被實現為抽象類型。
抽象類型 #
Torque 的抽象類型直接對應到 C++ 編譯時期和 CodeStubAssembler 執行時期值。它們的宣告指定一個名稱和與 C++ 類型的關係
AbstractTypeDeclaration :
type IdentifierName ExtendsDeclaration opt GeneratesDeclaration opt ConstexprDeclaration opt
ExtendsDeclaration :
extends IdentifierName ;
GeneratesDeclaration :
generates StringLiteral ;
ConstexprDeclaration :
constexpr StringLiteral ;
IdentifierName
指定抽象類型的名稱,而 ExtendsDeclaration
則選擇性地指定宣告類型所衍生的類型。GeneratesDeclaration
選擇性地指定一個字串文字,它對應到 C++ TNode
類型,用於 CodeStubAssembler
程式碼中,以包含其類型的執行時期值。ConstexprDeclaration
是指定 C++ 類型的字串文字,它對應到 Torque 類型的 constexpr
版本,用於建置時期(mksnapshot
時期)評估。
以下是來自 base.tq
的範例,用於 Torque 的 31 和 32 位元有號整數類型
type int32 generates 'TNode<Int32T>' constexpr 'int32_t';
type int31 extends int32 generates 'TNode<Int32T>' constexpr 'int31_t';
聯合類型 #
聯合類型表示一個值屬於多個可能類型之一。我們只允許標記值使用聯合類型,因為它們可以在執行時期使用對應指標來加以區分。例如,JavaScript 數字可能是 Smi 值或配置的 HeapNumber
物件。
type Number = Smi | HeapNumber;
聯合類型滿足下列等式
A | B = B | A
A | (B | C) = (A | B) | C
A | B = A
如果B
是A
的子類型
只能從標記類型形成聯合類型,因為無法在執行時期區分未標記類型。
在將聯合類型對應到 CSA 時,會選取聯合類型中所有類型的最特定共用父類型,但 Number
和 Numeric
例外,它們會對應到對應的 CSA 聯合類型。
類別類型 #
類別類型讓您能夠從 Torque 程式碼定義、配置和操作 V8 GC 堆疊上的結構化物件。每個 Torque 類別類型都必須對應到 C++ 程式碼中的 HeapObject 子類別。為了將 V8 的 C++ 和 Torque 實作之間的樣板物件存取程式碼的開銷降到最低,Torque 類別定義會用於產生必要的 C++ 物件存取程式碼,只要有可能(而且適當),就能減少手動讓 C++ 和 Torque 同步的麻煩。
ClassDeclaration :
ClassAnnotation* extern opt transient opt class IdentifierName ExtendsDeclaration opt GeneratesDeclaration opt {
ClassMethodDeclaration*
ClassFieldDeclaration*
}
ClassAnnotation :
@doNotGenerateCppClass
@generateBodyDescriptor
@generatePrint
@abstract
@export
@noVerifier
@hasSameInstanceTypeAsParent
@highestInstanceTypeWithinParentClassRange
@lowestInstanceTypeWithinParentClassRange
@reserveBitsInInstanceType ( NumericLiteral )
@apiExposedInstanceTypeValue ( NumericLiteral )
ClassMethodDeclaration :
transitioning opt IdentifierName ImplicitParameters opt ExplicitParameters ReturnType opt LabelsDeclaration opt StatementBlock
ClassFieldDeclaration :
ClassFieldAnnotation* weak opt const opt FieldDeclaration;
ClassFieldAnnotation :
@noVerifier
@if ( Identifier )
@ifnot ( Identifier )
FieldDeclaration :
Identifier ArraySpecifier opt : Type ;
ArraySpecifier :
[ Expression ]
類別範例
extern class JSProxy extends JSReceiver {
target: JSReceiver|Null;
handler: JSReceiver|Null;
}
extern
表示這個類別是在 C++ 中定義,而不是只在 Torque 中定義。
類別中的欄位宣告會隱含地產生欄位取得器和設定器,它們可以用於 CodeStubAssembler,例如
// In TorqueGeneratedExportedMacrosAssembler:
TNode<HeapObject> LoadJSProxyTarget(TNode<JSProxy> p_o);
void StoreJSProxyTarget(TNode<JSProxy> p_o, TNode<HeapObject> p_v);
如上所述,在 Torque 類別中定義的欄位會產生 C++ 程式碼,可移除重複的樣板存取器和堆積參訪器程式碼。JSProxy 的手寫定義必須繼承自產生的類別範本,如下所示
// In js-proxy.h:
class JSProxy : public TorqueGeneratedJSProxy<JSProxy, JSReceiver> {
// Whatever the class needs beyond Torque-generated stuff goes here...
// At the end, because it messes with public/private:
TQ_OBJECT_CONSTRUCTORS(JSProxy)
}
// In js-proxy-inl.h:
TQ_OBJECT_CONSTRUCTORS_IMPL(JSProxy)
產生的類別提供轉型函式、欄位存取器函式和欄位偏移量常數(例如,在本例中為 kTargetOffset
和 kHandlerOffset
),代表每個欄位從類別開頭的位元組偏移量。
類別類型註解 #
有些類別無法使用上例所示的繼承模式。在這些情況下,類別可以指定 @doNotGenerateCppClass
,直接繼承自其超類別類型,並包含其欄位偏移量常數的 Torque 產生的巨集。此類類別必須實作自己的存取器和轉型函式。使用該巨集如下所示
class JSProxy : public JSReceiver {
public:
DEFINE_FIELD_OFFSET_CONSTANTS(
JSReceiver::kHeaderSize, TORQUE_GENERATED_JS_PROXY_FIELDS)
// Rest of class omitted...
}
@generateBodyDescriptor
使 Torque 在產生的類別中發出類別 BodyDescriptor
,它代表垃圾收集器應如何參訪物件。否則,C++ 程式碼必須定義自己的物件參訪,或使用現有模式之一(例如,繼承自 Struct
並將類別包含在 STRUCT_LIST
中表示預期類別僅包含標記值)。
如果加入 @generatePrint
註解,產生器將實作一個 C++ 函式,以 Torque 配置定義的方式列印欄位值。使用 JSProxy 範例,簽章將為 void TorqueGeneratedJSProxy<JSProxy, JSReceiver>::JSProxyPrint(std::ostream& os)
,它可以由 JSProxy
繼承。
Torque 編譯器也會為所有 extern
類別產生驗證程式碼,除非類別使用 @noVerifier
註解退出。例如,上述 JSProxy 類別定義將產生一個 C++ 方法 void TorqueGeneratedClassVerifiers::JSProxyVerify(JSProxy o, Isolate* isolate)
,它會驗證其欄位是否根據 Torque 類型定義有效。它也會在產生的類別上產生一個對應函式 TorqueGeneratedJSProxy<JSProxy, JSReceiver>::JSProxyVerify
,它會從 TorqueGeneratedClassVerifiers
呼叫靜態函式。如果您想要為類別加入額外驗證(例如數字上可接受的範圍,或欄位 foo
在欄位 bar
非空時為真等需求),請將 DECL_VERIFIER(JSProxy)
加入 C++ 類別(它會隱藏繼承的 JSProxyVerify
)並在 src/objects-debug.cc
中實作它。任何此類自訂驗證的第一個步驟都應呼叫產生的驗證器,例如 TorqueGeneratedClassVerifiers::JSProxyVerify(*this, isolate);
。(要在每次 GC 前後執行這些驗證器,請使用 v8_enable_verify_heap = true
建置並使用 --verify-heap
執行。)
@abstract
表示類別本身未實例化,且沒有自己的實例類型:邏輯上屬於類別的實例類型是衍生類別的實例類型。
@export
註解會導致 Torque 編譯器產生具體的 C++ 類別(例如上述範例中的 JSProxy
)。這顯然僅在您不希望新增 Torque 產生的程式碼提供的任何 C++ 功能時才會有用。不可與 extern
連用。對於僅在 Torque 中定義和使用的類別,最適合不使用 extern
或 @export
。
@hasSameInstanceTypeAsParent
表示與其父類別具有相同執行個體類型的類別,但重新命名一些欄位,或可能具有不同的對應。在這種情況下,父類別不是抽象的。
註解 @highestInstanceTypeWithinParentClassRange
、@lowestInstanceTypeWithinParentClassRange
、@reserveBitsInInstanceType
和 @apiExposedInstanceTypeValue
都會影響執行個體類型的產生。通常您可以忽略這些並沒問題。Torque 負責為 v8::internal::InstanceType
枚舉中的每個類別指定一個唯一值,以便 V8 能在執行階段確定 JS 堆中的任何物件類型。Torque 的執行個體類型指定在絕大多數情況下都應該是足夠的,但在某些情況下,我們希望特定類別的執行個體類型在不同建置之間保持穩定,或位於指定給其超類別的執行個體類型範圍的開始或結束,或成為可以在 Torque 外部定義的保留值範圍。
類別欄位 #
除了純值(如上例所示)之外,類別欄位可能包含索引資料。以下是一個範例
extern class CoverageInfo extends HeapObject {
const slot_count: int32;
slots[slot_count]: CoverageInfoSlot;
}
這表示 CoverageInfo
的執行個體大小會根據 slot_count
中的資料而有所不同。
與 C++ 不同,Torque 不會在欄位之間隱式新增填補;相反,如果欄位未正確對齊,它將失敗並發出錯誤。Torque 還要求強欄位、弱欄位和標量欄位與欄位順序中同類別的其他欄位放在一起。
const
表示欄位無法在執行階段(或至少不容易)進行變更;如果您嘗試設定它,Torque 將會編譯失敗。這對於長度欄位而言是個好主意,因為它們只能在非常小心的情況下重設,因為它們需要釋放任何已釋放的空間,並且可能會導致與標記執行緒發生資料競爭。
事實上,Torque 要求用於索引資料的長度欄位為 const
。
欄位宣告開頭的 weak
表示欄位是自訂的弱參照,與弱欄位的 MaybeObject
標記機制相反。
此外,weak
會影響常數的產生,例如 kEndOfStrongFieldsOffset
和 kStartOfWeakFieldsOffset
,這是某些自訂 BodyDescriptor
中使用的舊功能,目前仍需要將標記為 weak
的欄位分組在一起。我們希望在 Torque 能夠完全產生所有 BodyDescriptor
之後移除這個關鍵字。
如果儲存在欄位中的物件可能是 MaybeObject
類型的弱參照(設定第二個位元),則應在類型中使用 Weak<T>
,並且不應使用 weak
關鍵字。這個規則仍有一些例外,例如來自 Map
的這個欄位,它可以包含一些強類型和一些弱類型,並且也標記為 weak
以包含在弱區段中
weak transitions_or_prototype_info: Map|Weak<Map>|TransitionArray|
PrototypeInfo|Smi;
@if
和 @ifnot
標記應包含在某些建置組態中但不包含在其他建置組態中的欄位。它們接受 src/torque/torque-parser.cc
中 BuildFlags
清單中的值。
完全在 Torque 外部定義的類別 #
有些類別未在 Torque 中定義,但 Torque 必須知道每個類別,因為它負責指定執行個體類型。對於這種情況,類別可以宣告為沒有主體,而 Torque 除了執行個體類型之外不會為它們產生任何東西。範例
extern class OrderedHashMap extends HashTable;
形狀 #
定義 shape
的方式看起來就像定義 class
,只不過它使用關鍵字 shape
而不是 class
。shape
是 JSObject
的子類型,表示物件內部屬性的時間點配置(在規範中,這些是「資料屬性」,而不是「內部插槽」)。shape
沒有自己的執行個體類型。具有特定形狀的物件可能會隨時變更並失去該形狀,因為物件可能會進入字典模式,並將其所有屬性移到一個獨立的後端儲存區。
結構 #
struct
是資料的集合,可以輕鬆地一起傳遞。(與名為 Struct
的類別完全無關。)與類別一樣,它們可以包含對資料執行的巨集。與類別不同,它們也支援泛型。語法看起來類似於類別
@export
struct PromiseResolvingFunctions {
resolve: JSFunction;
reject: JSFunction;
}
struct ConstantIterator<T: type> {
macro Empty(): bool {
return false;
}
macro Next(): T labels _NoMore {
return this.value;
}
value: T;
}
結構註解 #
任何標記為 @export
的結構都會包含在已產生檔案 gen/torque-generated/csa-types.h
中,並具有可預測的名稱。名稱會加上 TorqueStruct
前綴,因此 PromiseResolvingFunctions
會變成 TorqueStructPromiseResolvingFunctions
。
結構欄位可以標記為 const
,表示不應寫入。整個結構仍然可以覆寫。
結構作為類別欄位 #
結構可以用作類別欄位的類型。在這種情況下,它表示類別中已封裝、已排序的資料(否則,結構沒有對齊需求)。這對於類別中的索引欄位特別有用。例如,DescriptorArray
包含一個由三值結構組成的陣列
struct DescriptorEntry {
key: Name|Undefined;
details: Smi|Undefined;
value: JSAny|Weak<Map>|AccessorInfo|AccessorPair|ClassPositions;
}
extern class DescriptorArray extends HeapObject {
const number_of_all_descriptors: uint16;
number_of_descriptors: uint16;
raw_number_of_marked_descriptors: uint16;
filler16_bits: uint16;
enum_cache: EnumCache;
descriptors[number_of_all_descriptors]: DescriptorEntry;
}
參照和切片 #
Reference<T>
和 Slice<T>
是特殊結構,表示指向堆積物件內部資料的指標。它們都包含一個物件和一個偏移量;Slice<T>
也包含一個長度。與其直接建構這些結構,您可以使用特殊語法:&o.x
會建立一個 Reference
,指向物件 o
內的欄位 x
,或一個 Slice
,指向資料,如果 x
是索引欄位。對於參照和切片,都有常數和可變版本。對於參照,這些類型寫成 &T
和 const &T
,分別代表可變和常數參照。可變性是指它們指向的資料,可能不會在全域性上成立,也就是說,您可以建立指向可變資料的常數參照。對於切片,沒有類型的特殊語法,兩個版本寫成 ConstSlice<T>
和 MutableSlice<T>
。參照可以使用 *
或 ->
取消參照,與 C++ 一致。
指向未標記資料的參照和切片也可以指向堆外資料。
位元欄位結構 #
位元欄位結構
表示已封裝成單一數值資料的數值資料集合。其語法看起來類似於一般的 結構
,但增加了每個欄位的位元數。
bitfield struct DebuggerHints extends uint31 {
side_effect_state: int32: 2 bit;
debug_is_blackboxed: bool: 1 bit;
computed_debug_is_blackboxed: bool: 1 bit;
debugging_id: int32: 20 bit;
}
如果位元欄位結構(或任何其他數值資料)儲存在 Smi 中,可以使用類型 SmiTagged<T>
來表示它。
函式指標類型 #
函式指標只能指向 Torque 中定義的內建函式,因為這保證了預設 ABI。它們特別有用於減少二進位程式碼大小。
函數指標類型為匿名(就像在 C 中),它們可以繫結到類型別名(就像 C 中的 typedef
)。
type CompareBuiltinFn = builtin(implicit context: Context)(Object, Object, Object) => Number;
特殊類型 #
有兩種特殊類型,由關鍵字 void
和 never
指示。void
用作不傳回值的呼叫函數的傳回類型,而 never
用作從未實際傳回(即僅透過例外路徑結束)的呼叫函數的傳回類型。
暫時類型 #
在 V8 中,堆疊物件可以在執行階段變更配置。為了表達在類型系統中可能變更或其他暫時假設的物件配置,Torque 支援「暫時類型」的概念。在宣告抽象類型時,加入關鍵字 transient
將其標記為暫時類型。
// A HeapObject with a JSArray map, and either fast packed elements, or fast
// holey elements when the global NoElementsProtector is not invalidated.
transient type FastJSArray extends JSArray
generates 'TNode<JSArray>';
例如,在 FastJSArray
的情況下,如果陣列變更為字典元素或如果全域 NoElementsProtector
失效,則暫時類型會失效。若要在 Torque 中表達這一點,請將所有可能這樣做的呼叫函數註解為 transitioning
。例如,呼叫 JavaScript 函數可以執行任意 JavaScript,因此它是 transitioning
。
extern transitioning macro Call(implicit context: Context)
(Callable, Object): Object;
在類型系統中監控此方式的方法是,禁止在過渡操作中存取暫時類型的值。
const fastArray : FastJSArray = Cast<FastJSArray>(array) otherwise Bailout;
Call(f, Undefined);
return fastArray; // Type error: fastArray is invalid here.
列舉 #
列舉提供一種方式來定義一組常數,並將它們分組在類似
C++ 中的列舉類別的名稱下。宣告由 enum
關鍵字引入,並遵循下列
語法結構
EnumDeclaration :
extern enum IdentifierName ExtendsDeclaration opt ConstexprDeclaration opt { IdentifierName list+ (, ...) opt }
基本範例如下所示
extern enum LanguageMode extends Smi {
kStrict,
kSloppy
}
此宣告定義一個新的類型 LanguageMode
,其中 extends
子句指定基礎
類型,也就是用於表示列舉值的執行階段類型。在此範例中,這是 TNode<Smi>
,
因為這是類型 Smi
generates
的內容。constexpr LanguageMode
會轉換為 LanguageMode
在產生的 CSA 檔案中,因為沒有在列舉上指定 constexpr
子句來取代預設名稱。
如果省略 extends
子句,Torque 將只會產生類型的 constexpr
版本。extern
關鍵字會告訴 Torque 此列舉有一個 C++ 定義。目前,僅支援 extern
列舉。
Torque 會為列舉的每個項目產生一個不同的類型和常數。這些會定義
在與列舉名稱相符的命名空間內。FromConstexpr<>
的必要專業化是
產生以將條目的 constexpr
類型轉換為列舉類型。在 C++ 檔案中為條目產生的值為 <enum-constexpr>::<entry-name>
,其中 <enum-constexpr>
是為列舉產生的 constexpr
名稱。在上述範例中,這些名稱為 LanguageMode::kStrict
和 LanguageMode::kSloppy
。
Torque 的列舉與 typeswitch
建構搭配使用時效果非常好,因為
這些值使用不同的類型定義
typeswitch(language_mode) {
case (LanguageMode::kStrict): {
// ...
}
case (LanguageMode::kSloppy): {
// ...
}
}
如果列舉的 C++ 定義包含多於 .tq
檔案中使用的值,Torque 需要知道這一點。這會透過在最後一個條目後加上 ...
來宣告列舉為「開放」來完成。例如,考慮 ExtractFixedArrayFlag
,其中只有部分選項可從
Torque
enum ExtractFixedArrayFlag constexpr 'CodeStubAssembler::ExtractFixedArrayFlag' {
kFixedDoubleArrays,
kAllFixedArrays,
kFixedArrays,
...
}
可呼叫函式 #
可呼叫函式在概念上類似於 JavaScript 或 C++ 中的函式,但它們具有一些額外的語意,讓它們可以透過有用的方式與 CSA 程式碼和 V8 執行時期互動。Torque 提供了幾種不同類型的可呼叫函式:巨集
、內建
、執行時期
和 內在
。
CallableDeclaration :
MacroDeclaration
BuiltinDeclaration
RuntimeDeclaration
IntrinsicDeclaration
巨集
可呼叫函式 #
巨集是一種可呼叫函式,對應到產生的 CSA C++ 程式碼區塊。巨集
可以在 Torque 中完全定義,這種情況下 CSA 程式碼會由 Torque 產生,或標記為 extern
,這種情況下實作必須在 CodeStubAssembler 類別中提供為手寫的 CSA 程式碼。在概念上,將 巨集
視為可內嵌 CSA 程式碼區塊會很有用,這些區塊會在呼叫位置內嵌。
Torque 中的 巨集
宣告採用下列形式
MacroDeclaration :
transitioning opt macro IdentifierName ImplicitParameters opt ExplicitParameters ReturnType opt LabelsDeclaration opt StatementBlock
extern transitioning opt macro IdentifierName ImplicitParameters opt ExplicitTypes ReturnType opt LabelsDeclaration opt ;
每個非 extern
Torque 巨集
都使用 巨集
的 StatementBlock
主體在其名稱空間的產生的 Assembler
類別中建立產生 CSA 的函式。這段程式碼看起來就像您可能在 code-stub-assembler.cc
中找到的其他程式碼,儘管它有點難讀,因為它是機器產生的。標記為 extern
的 巨集
沒有在 Torque 中寫入主體,只提供手寫 C++ CSA 程式碼的介面,以便可以從 Torque 使用它。
巨集
定義指定隱含和明確參數、選用回傳類型和選用標籤。參數和回傳類型將在下面更詳細地討論,但目前只要知道它們的工作方式有點像 TypeScript 參數就夠了,如 TypeScript 文件 在此處 的函式類型區段中所討論的。
標籤是從 巨集
異常離開的機制。它們與 CSA 標籤一一對應,並作為 CodeStubAssemblerLabels*
型態參數新增到為 巨集
產生的 C++ 方法。它們的確切語意在下面討論,但對於 巨集
宣告而言,巨集
標籤的逗號分隔清單會選用 labels
關鍵字提供,並置於 巨集
的參數清單和回傳類型之後。
以下是 base.tq
中外部和 Torque 定義的 巨集
範例
extern macro BranchIfFastJSArrayForCopy(Object, Context): never
labels Taken, NotTaken;
macro BranchIfNotFastJSArrayForCopy(implicit context: Context)(o: Object):
never
labels Taken, NotTaken {
BranchIfFastJSArrayForCopy(o, context) otherwise NotTaken, Taken;
}
內建
可呼叫函式 #
內建
函式類似於 巨集
,它們可以完全定義在 Torque 中,或標記為 extern
。在基於 Torque 的內建函式案例中,內建函式的本體用於產生一個 V8 內建函式,該函式可以像任何其他 V8 內建函式一樣被呼叫,包括自動在 builtin-definitions.h
中加入相關資訊。與 巨集
類似,標記為 extern
的 Torque 內建
函式沒有基於 Torque 的本體,僅提供一個介面到現有的 V8 內建
函式,以便它們可以從 Torque 程式碼中使用。
Torque 中的 內建
函式宣告具有以下格式
MacroDeclaration :
transitioning opt javascript opt builtin IdentifierName ImplicitParameters opt ExplicitParametersOrVarArgs ReturnType opt StatementBlock
extern transitioning opt javascript opt builtin IdentifierName ImplicitParameters opt ExplicitTypesOrVarArgs ReturnType opt ;
Torque 內建函式只有一個程式碼副本,而且在產生的內建函式程式碼物件中。與 巨集
不同,當 內建
函式從 Torque 程式碼中呼叫時,CSA 程式碼不會在呼叫位置內嵌,而是會產生一個呼叫到內建函式的動作。
內建
函式不能有標籤。
如果您正在編寫 內建
函式的實作,您可以建立一個到內建函式或執行時期函式的 尾呼叫,前提是(且僅限於)它是內建函式中的最後呼叫。在這種情況下,編譯器可能會避免建立一個新的堆疊框架。只需在呼叫之前加入 tail
,例如 tail MyBuiltin(foo, bar);
。
執行時期
可呼叫函式 #
執行時期
函式類似於 內建
函式,它們可以向 Torque 公開一個介面到外部功能。然而,執行時期
函式提供的功能並非在 CSA 中實作,而必須始終在 V8 中作為標準執行時期回呼實作。
Torque 中的 執行時期
函式宣告具有以下格式
MacroDeclaration :
extern transitioning opt runtime IdentifierName ImplicitParameters opt ExplicitTypesOrVarArgs ReturnType opt ;
指定名稱 IdentifierName 的 extern runtime
對應到由 Runtime::kIdentifierName
指定的執行時期函式。
與 內建
函式類似,執行時期
函式不能有標籤。
您也可以在適當的時候呼叫 執行時期
函式作為尾呼叫。只需在呼叫之前加入 tail
關鍵字即可。
執行時期函式宣告通常放置在稱為 runtime
的命名空間中。這會讓它們與同名的內建函式區分開來,並讓呼叫位置更容易看出我們正在呼叫執行時期函式。我們應該考慮讓這成為強制性的。
內部
可呼叫函式 #
內部
函式是內建的 Torque 可呼叫函式,提供存取無法在 Torque 中實作的其他內部功能。它們在 Torque 中宣告,但未定義,因為實作是由 Torque 編譯器提供的。內部
函式宣告使用以下語法
IntrinsicDeclaration :
intrinsic % IdentifierName ImplicitParameters opt ExplicitParameters ReturnType opt ;
大部分情況下,「使用者」Torque 程式碼很少需要直接使用 intrinsic
。
以下是部分受支援的 intrinsic
// %RawObjectCast downcasts from Object to a subtype of Object without
// rigorous testing if the object is actually the destination type.
// RawObjectCasts should *never* (well, almost never) be used anywhere in
// Torque code except for in Torque-based UnsafeCast operators preceeded by an
// appropriate type assert()
intrinsic %RawObjectCast<A: type>(o: Object): A;
// %RawPointerCast downcasts from RawPtr to a subtype of RawPtr without
// rigorous testing if the object is actually the destination type.
intrinsic %RawPointerCast<A: type>(p: RawPtr): A;
// %RawConstexprCast converts one compile-time constant value to another.
// Both the source and destination types should be 'constexpr'.
// %RawConstexprCast translate to static_casts in the generated C++ code.
intrinsic %RawConstexprCast<To: type, From: type>(f: From): To;
// %FromConstexpr converts a constexpr value into into a non-constexpr
// value. Currently, only conversion to the following non-constexpr types
// are supported: Smi, Number, String, uintptr, intptr, and int32
intrinsic %FromConstexpr<To: type, From: type>(b: From): To;
// %Allocate allocates an unitialized object of size 'size' from V8's
// GC heap and "reinterpret casts" the resulting object pointer to the
// specified Torque class, allowing constructors to subsequently use
// standard field access operators to initialize the object.
// This intrinsic should never be called from Torque code. It's used
// internally when desugaring the 'new' operator.
intrinsic %Allocate<Class: type>(size: intptr): Class;
如同 builtin
和 runtime
,intrinsic
無法有標籤。
明確參數 #
Torque 定義的 Callable 宣告,例如 Torque macro
和 builtin
,有明確的參數清單。它們是識別碼和類型配對的清單,使用類似於 TypeScript 函式參數清單的語法,但 Torque 不支援選用參數或預設參數。此外,Torque 實作的 builtin
可以選擇性地支援 rest 參數,如果 builtin 使用 V8 的內部 JavaScript 呼叫慣例(例如標記為 javascript
關鍵字)。
ExplicitParameters :
( ( IdentifierName : TypeIdentifierName ) list* )
( ( IdentifierName : TypeIdentifierName ) list+ (, ... IdentifierName ) opt )
舉例來說
javascript builtin ArraySlice(
(implicit context: Context)(receiver: Object, ...arguments): Object {
// …
}
隱含參數 #
Torque callable 可以使用類似於 Scala 隱含參數 的方式指定隱含參數。
ImplicitParameters :
( implicit ( IdentifierName : TypeIdentifierName ) list* )
具體來說:macro
可以宣告隱含參數,除了明確參數之外。
macro Foo(implicit context: Context)(x: Smi, y: Smi)
對應到 CSA 時,隱含參數和明確參數會以相同方式處理,並形成一個聯合參數清單。
隱含參數不會在呼叫位置提及,而是會隱含地傳遞:Foo(4, 5)
。要讓這項功能運作,Foo(4, 5)
必須在提供名為 context
的值的內容中呼叫。範例
macro Bar(implicit context: Context)() {
Foo(4, 5);
}
與 Scala 相反,如果隱含參數的名稱不相同,我們會禁止這種情況。
由於重載解析可能會導致混淆的行為,我們確保隱含參數完全不會影響重載解析。也就是說:在比較重載組的候選時,我們不會考慮呼叫位置可用的隱含繫結。只有在找到單一最佳重載後,我們才會檢查隱含參數的隱含繫結是否可用。
將隱含參數放在明確參數的左側與 Scala 不同,但更符合 CSA 中現有的慣例,將 context
參數放在最前面。
js-implicit
#
對於在 Torque 中定義的具有 JavaScript 連結的內建函式,您應使用關鍵字 js-implicit
,而不是 implicit
。引數僅限於呼叫慣例的這四個組成部分
- context:
NativeContext
- receiver:
JSAny
(JavaScript 中的this
) - target:
JSFunction
(JavaScript 中的arguments.callee
) - newTarget:
JSAny
(JavaScript 中的new.target
)
它們不必全部宣告,僅宣告您要使用的部分即可。以下為 Array.prototype.shift
的範例程式碼
// https://tc39.es/ecma262/#sec-array.prototype.shift
transitioning javascript builtin ArrayPrototypeShift(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
...
請注意,context
引數是 NativeContext
。這是因為 V8 中的內建函式會在其封閉中始終嵌入原生 context。在 js-implicit 慣例中編碼此項可讓程式設計人員消除從函式 context 載入原生 context 的作業。
重載解析 #
Torque macro
和運算子(僅是 macro
的別名)允許引數類型重載。重載規則的靈感來自 C++ 的規則:如果重載嚴格優於所有替代方案,則選取該重載。這表示它至少在一個參數中必須嚴格優於其他參數,且在所有其他參數中都必須優於或等於其他參數。
在比較兩個重載的相應參數對時…
- …如果
- 它們相等;
- 兩者都需要一些隱式轉換。
- …則它們被視為等同;
- 如果
- 它是其他項目的嚴格子類型;
它不需要隱式轉換,而其他項目需要。
…則一個被視為較佳;
如果沒有重載嚴格優於所有替代方案,則會導致編譯錯誤。
let k: Number = 0;
try {
return FastArrayForEach(o, len, callbackfn, thisArg)
otherwise Bailout;
}
label Bailout(kValue: Smi) deferred {
k = kValue;
}
這裡是另一個範例,其中字典元素大小寫標記為遞延,以改善較可能案例的程式碼產生(來自 Array.prototype.join
實作)
if (IsElementsKindLessThanOrEqual(kind, HOLEY_ELEMENTS)) {
loadFn = LoadJoinElement<FastSmiOrObjectElements>;
} else if (IsElementsKindLessThanOrEqual(kind, HOLEY_DOUBLE_ELEMENTS)) {
loadFn = LoadJoinElement<FastDoubleElements>;
} else if (kind == DICTIONARY_ELEMENTS)
deferred {
const dict: NumberDictionary =
UnsafeCast<NumberDictionary>(array.elements);
const nofElements: Smi = GetNumberDictionaryNumberOfElements(dict);
// <etc>...
將 CSA 程式碼移植到 Torque #
移植 Array.of
的程式碼修補作為將 CSA 程式碼移植到 Torque 的最小範例。