[WebKit核心] JavaScript引擎深度解析--基礎篇(一)位元組碼生成及語法樹的構建詳情分析
看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。
JSC對JavaScript的處理,其實與Webkit對CSS的處理許多地方是類似的,它這麼幾個部分:
(1)詞法分析->出來詞語(Token);
(2)語法分析->出來抽象語法樹(AST:Abstract Syntax Tree);
(3)遍歷抽象語法樹->生成位元組碼(Bytecode);
(4)用直譯器(LLInt:Low Level Interpreter)執行位元組碼;
(5)如果效能不夠好就用Baseline JIT編譯位元組碼生成機器碼、然後執行此機器碼;
(6)如果效能還不夠好,就用DFG JIT重新編譯位元組碼生成更好的機器碼、然後執行此機器碼;
(7)最後,如果還不好,就祭出重器--虛擬器(LLVM:Low Level Virtual Machine)來編譯DFG的中間表示程式碼、生成更高優化的機器碼並執行。接下來,我將會用一下系列文章描述此過程。
其中,步驟1、2是類似的,3、4、5步的思想,CSS JIT也是採用類似方法,請參考[1]。想寫寫JSC的文章,用菜鳥和愚公移山的方式,敲開JSC的冰山一角。
本篇主要描述詞法和語法解析的細節。 一、 JavaScriptCore的詞法分析器工作流程分析
W3C是這麼解釋詞法和語法工作流程的:
詞法器Tokenizer的工作過程如下,就是不斷從字串中尋找一個個的詞(Token),比如找到連續的“true”字串,就建立一個TokenTrue。詞法器工作過程如下:
JavaScriptCore/interpreter/interpreter.cpp: template <typename CharType> template <ParserMode mode> TokenType LiteralParser<CharType>::Lexer::lex(LiteralParserToken<CharType>& token) { while (m_ptr < m_end && isJSONWhiteSpace(*m_ptr)) ++m_ptr; if (m_ptr >= m_end) { token.type = TokEnd; token.start = token.end = m_ptr; return TokEnd; } token.type = TokError; token.start = m_ptr; switch (*m_ptr) { case '[': token.type = TokLBracket; token.end = ++m_ptr; return TokLBracket; case ']': token.type = TokRBracket; token.end = ++m_ptr; return TokRBracket; case '(': token.type = TokLParen; token.end = ++m_ptr; return TokLParen; case ')': token.type = TokRParen; token.end = ++m_ptr; return TokRParen; case ',': token.type = TokComma; token.end = ++m_ptr; return TokComma; case ':': token.type = TokColon; token.end = ++m_ptr; return TokColon; case '"': return lexString<mode, '"'>(token); case 't': if (m_end - m_ptr >= 4 && m_ptr[1] == 'r' && m_ptr[2] == 'u' && m_ptr[3] == 'e') { m_ptr += 4; token.type = TokTrue; token.end = m_ptr; return TokTrue; } break; case '-': case '0': case '9': return lexNumber(token); } if (m_ptr < m_end) { if (*m_ptr == '.') { token.type = TokDot; token.end = ++m_ptr; return TokDot; } if (*m_ptr == '=') { token.type = TokAssign; token.end = ++m_ptr; return TokAssign; } if (*m_ptr == ';') { token.type = TokSemi; token.end = ++m_ptr; return TokAssign; } if (isASCIIAlpha(*m_ptr) || *m_ptr == '_' || *m_ptr == '$') return lexIdentifier(token); if (*m_ptr == '\'') { return lexString<mode, '\''>(token); } } m_lexErrorMessage = String::format("Unrecognized token '%c'", *m_ptr).impl(); return TokError; }
經過此過程,一個完整的JSC世界的Token就生成了。然後,再進行語法分析,生成抽象語法樹.下圖就是JavaScriptCore世界語法節點的靜態類關係:
下面我們看看,語法解析具體過程:
JavaScriptCore/parser/parser.cpp:
PassRefPtr<ParsedNode> Parser<LexerType>::parse(JSGlobalObject* lexicalGlobalObject, Debugger* debugger, ExecState* debuggerExecState, JSObject** exception)</span>
{
ASSERT(lexicalGlobalObject);
ASSERT(exception && !*exception);
int errLine;
UString errMsg;
if (ParsedNode::scopeIsFunction)
m_lexer->setIsReparsing();
m_sourceElements = 0;
errLine = -1;
errMsg = UString();
UString parseError = parseInner();
。。。
}
建立抽象語法樹Builder,並用來解析、生成語法節點:
UString Parser<LexerType>::parseInner(){
UString parseError = UString();
unsigned oldFunctionCacheSize = m_functionCache ? m_functionCache->byteSize() : 0;
//抽象語法樹Builder:
ASTBuilder context(const_cast<JSGlobalData*>(m_globalData), const_cast<SourceCode*>(m_source));
if (m_lexer->isReparsing())
m_statementDepth--;
ScopeRef scope = currentScope();
//開始解析生成語法樹的一個節點:
SourceElements* sourceElements = parseSourceElements<CheckForStrictMode>(context);
if (!sourceElements || !consume(EOFTOK))
}
舉例說來,根據Token的型別,JSC認為輸入的Token是一個常量宣告,就會使用如下的模板函式生成語法節點(Node),然後放入ASTBuilder裡面,我們先看ASTBuilder的結構:
class ASTBuilder {
......
Scope m_scope;
Vector<BinaryOperand, 10> m_binaryOperandStack;
Vector<AssignmentInfo, 10> m_assignmentInfoStack;
Vector<pair<int, int>, 10> m_binaryOperatorStack;
Vector<pair<int, int>, 10> m_unaryTokenStack;
int m_evalCount;
};
再看主要語法解析過程(Parser/parser.cpp):
template <typename LexerType>
template <SourceElementsMode mode, class TreeBuilder> TreeSourceElements Parser<LexerType>::parseSourceElements(TreeBuilder& context)
{
const unsigned lengthOfUseStrictLiteral = 12; // "use strict".length
TreeSourceElements sourceElements = context.createSourceElements();
bool seenNonDirective = false;
const Identifier* directive = 0;
unsigned directiveLiteralLength = 0;
unsigned startOffset = m_token.m_info.startOffset;
unsigned oldLastLineNumber = m_lexer->lastLineNumber();
unsigned oldLineNumber = m_lexer->lineNumber();
bool hasSetStrict = false;
//解析語法節點--語句
while (TreeStatement statement = parseStatement(context, directive, &directiveLiteralLength)) {
if (mode == CheckForStrictMode && !seenNonDirective) {
if (directive) {
// "use strict" must be the exact literal without escape sequences or line continuation.
if (!hasSetStrict && directiveLiteralLength == lengthOfUseStrictLiteral && m_globalData->propertyNames->useStrictIdentifier == *directive) {
setStrictMode();
hasSetStrict = true;
failIfFalse(isValidStrictMode());
m_lexer->setOffset(startOffset);
next();
m_lexer->setLastLineNumber(oldLastLineNumber);
m_lexer->setLineNumber(oldLineNumber);
failIfTrue(m_error);
continue;
}
} else
seenNonDirective = true;
}
context.appendStatement(sourceElements, statement); //新增語法節點到ASTBuilder
}
if (m_error)
fail();
return sourceElements;
}
解析語句就是各種switch case,效率不高啊!
template <typename LexerType>
template <class TreeBuilder> TreeStatement Parser<LexerType>::parseStatement(TreeBuilder& context, const Identifier*& directive, unsigned* directiveLiteralLength)
{
DepthManager statementDepth(&m_statementDepth);
m_statementDepth++;
directive = 0;
int nonTrivialExpressionCount = 0;
failIfStackOverflow();
switch (m_token.m_type) {
case OPENBRACE:
return parseBlockStatement(context);
case VAR:
return parseVarDeclaration(context);
case CONSTTOKEN:
return parseConstDeclaration(context);
case FUNCTION:
failIfFalseIfStrictWithMessage(m_statementDepth == 1, "Functions cannot be declared in a nested block in strict mode");
return parseFunctionDeclaration(context);
case SEMICOLON:
next();
return context.createEmptyStatement(m_lexer->lastLineNumber());
case IF:
return parseIfStatement(context);
case DO:
return parseDoWhileStatement(context);
case WHILE:
return parseWhileStatement(context);
case FOR:
return parseForStatement(context);
case CONTINUE:
return parseContinueStatement(context);
case BREAK:
return parseBreakStatement(context);
case RETURN:
return parseReturnStatement(context);
case WITH:
return parseWithStatement(context);
case SWITCH:
return parseSwitchStatement(context);
case THROW:
return parseThrowStatement(context);
case TRY:
return parseTryStatement(context);
case DEBUGGER:
return parseDebuggerStatement(context);
case EOFTOK:
case CASE:
case CLOSEBRACE:
case DEFAULT:
// These tokens imply the end of a set of source elements
return 0;
case IDENT:
return parseExpressionOrLabelStatement(context);
case STRING:
directive = m_token.m_data.ident;
if (directiveLiteralLength)
*directiveLiteralLength = m_token.m_info.endOffset - m_token.m_info.startOffset;
nonTrivialExpressionCount = m_nonTrivialExpressionCount;
default:
TreeStatement exprStatement = parseExpressionStatement(context);
if (directive && nonTrivialExpressionCount != m_nonTrivialExpressionCount)
directive = 0;
return exprStatement;
}
}
舉其中一個例子: JavaScriptCore/parser/parser.cpp:
template <typename LexerType>
template <class TreeBuilder> TreeConstDeclList Parser<LexerType>::parseConstDeclarationList(TreeBuilder& context)
{
failIfTrue(strictMode());
TreeConstDeclList constDecls = 0;
TreeConstDeclList tail = 0;
do {
next();
matchOrFail(IDENT);
//取出詞(Token):
const Identifier* name = m_token.m_data.ident;
next();
//是一個=嗎?
bool hasInitializer = match(EQUAL);
//
declareVariable(name);
context.addVar(name, DeclarationStacks::IsConstant | (hasInitializer ? DeclarationStacks::HasInitializer : 0));
TreeExpression initializer = 0;
if (hasInitializer) {
next(TreeBuilder::DontBuildStrings); // consume '='
initializer = parseAssignmentExpression(context);
}
<span style="white-space:pre"> </span>新建一個“常量申明節點”放入ASTBuilder裡面:
tail = context.appendConstDecl(m_lexer->lastLineNumber(), tail, name, initializer);
if (!constDecls)
constDecls = tail;
} while (match(COMMA));
return constDecls;
}
ASTBuilder.h:
ConstDeclNode* appendConstDecl(int lineNumber, ConstDeclNode* tail, const Identifier* name, ExpressionNode* initializer)
{
ConstDeclNode* result = new (m_globalData) ConstDeclNode(lineNumber, *name, initializer);
if (tail)
tail->m_next = result;
return result;
}
呼叫堆疊 如下:
#0 JSC::ASTBuilder::BinaryExprContext::BinaryExprContext (this=0x7fffffffbb6f) at JavaScriptCore/parser/ASTBuilder.h:85
#1 JSC::Parser<JSC::Lexer<unsigned char> >::parseBinaryExpression<JSC::ASTBuilder> (this=0x7fffffffc330, context=...)JavaScriptCore/parser/Parser.cpp:1143
#2 JSC::Parser<JSC::Lexer<unsigned char> >::parseConditionalExpression<JSC::ASTBuilder> (this=0x7fffffffc330, context=...) at JavaScriptCore/parser/Parser.cpp:1109
#3 JSC::Parser<JSC::Lexer<unsigned char> >::parseAssignmentExpression<JSC::ASTBuilder> (this=0x7fffffffc330, context=...)
at /opt/src/opt/src/mp50/framework/webkit/WebKit_123412/Source/JavaScriptCore/parser/Parser.cpp:1051
#4 JSC::Parser<JSC::Lexer<unsigned char> >::parseVarDeclarationList<JSC::ASTBuilder> (this=, context=..., [email protected]: 1, [email protected]: 0xdb3060, [email protected]: 0x0, [email protected]: 5, [email protected]: 5, [email protected]: 5) at parser/Parser.cpp:263
#5 JSC::Parser<JSC::Lexer<unsigned char> >::parseVarDeclaration<JSC::ASTBuilder> (this=0x7fffffffc330, context=...) at JavaScriptCore/parser/Parser.cpp:181
#6 JSC::Parser<JSC::Lexer<unsigned char> >::parseStatement<JSC::ASTBuilder> (this=0x7fffffffc330, context=..., directive=: 0x0,directiveLiteralLength=) Parser.cpp:682
#7 JSC::Parser<JSC::Lexer<unsigned char> >::parseSourceElements<(JSC::SourceElementsMode)0, JSC::ASTBuilder> (this, context=...) at parser/Parser.cpp:145
#8 JSC::Parser<JSC::Lexer<unsigned char> >::parseInner (this=0x7fffffffc330) at Parser.cpp:93
#9 JSC::Parser<JSC::Lexer<unsigned char> >::parse<JSC::ProgramNode> (this=, lexicalGlobalObject=, debugger=0x0, debuggerExecState=, exception=) Parser.h:990
#10 JSC::parse<JSC::ProgramNode> (globalData=, lexicalGlobalObject=source,parameters, strictness=JSParseNormal,parserMode=JSParseProgramCode, debugger, execState=, exception=) Parser.h:1048
#11 JSC::ProgramExecutable::compileInternal (this=, exec=, scopeChainNode=, jitType=JSC::JITCode::BaselineJIT) at JavaScriptCore/runtime/Executable.cpp:338
#12 JSC::ProgramExecutable::compile (this=0x7ffff7fbb580, exec=0x7ffff7f9fb90, scopeChainNode=0x7ffff7f7ffc0)JavaScriptCore/runtime/Executable.h:446
#13 JSC::Interpreter::execute (this=, program=, callFrame=, scopeChain=, thisObj=0x7ffff7f9f980) at JavaScriptCore/interpreter/Interpreter.cpp:1224
#14 JSC::evaluate (exec=, scopeChain=, source=..., thisValue=..., returnedException=) JavaScriptCore/runtime/Completion.cpp:75
#15 runWithScripts (globalObject=0x7ffff7f9f980, scripts=, dump=false) at JavaScriptCore/jsc.cpp:545
#16 jscmain (argc=2, argv=0x7fffffffdc88) at JavaScriptCore/jsc.cpp:733
#17 main (argc=2, argv=0x7fffffffdc88) atavaScriptCore/jsc.cpp:510
接下來,就會呼叫BytecodeGenerator::generate生成位元組碼,具體分下節分析。我們先看看下面來自JavaScript的一個個語法樹節點生成位元組碼的過程:
JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
RegisterID* BooleanNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
if (dst == generator.ignoredResult())
return 0;
return generator.emitLoad(dst, m_value);
}
以下是我準備寫的文章題目:
一、 JavaScriptCore的詞法分析器工作流程分析;
二、 JavaScriptCore的語法分析器工作流程分析;
三、 JavaScriptCore的位元組碼生成流程分析;
四、 LLInt直譯器工作流程分析;
五、 Baseline JIT編譯器的工作流程分析;
六、 DFG JIT編譯器的工作流程分析;
七、LLVM虛擬機器的工作流程分析;
八、JavaScriptCore的未來展望;
文筆粗糙,不善表達,希望能越寫越好。
原創,轉載請註明:http://blog.csdn.net/lichwei1983/article/details/44658533
第一時間獲得部落格更新提醒,以及更多技術資訊分享,歡迎關注個人微信公眾平臺:程式設計師互動聯盟(coder_online),掃一掃下方二維碼或搜尋微訊號coder_online即可關注,我們可以線上交流。
引用:
1 https://www.webkit.org/blog/3271/webkit-css-selector-jit-compiler/
2 http://blog.csdn.net/horkychen/article/details/8928578
相關推薦
[WebKit核心] JavaScript引擎深度解析--基礎篇(一)位元組碼生成及語法樹的構建詳情分析
看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。 JSC對
[WebKit核心] JavaScriptCore深度解析--基礎篇(一)位元組碼生成及語法樹的構建詳情分析
看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。 JS
網絡基礎篇(一)
分時 osi模型 郵件 高層 不用 電子設備 2msl 基於 三種 OSI模型的七層結構 1、物理層: 二進傳輸 為啟動、維護以及關閉物理鏈路定義了電氣規範、機械規範、過程規範、和功能規範。 2、數據鏈路層: 訪問介質: 定義如何格式化數據以便進行傳輸以及如何控制對網絡的訪
RecyclerView 全面使用及分析 - 基礎篇(一)
一、RecyclerView 介紹 在 RecyclerView 出來之前,大家都在使用 ListView、GridView,當然 RecyclerView 出來之後,基本上都轉向了 RecyclerView,從名字上可以看出,它能夠實現view 的複用,同樣 ListView
numpy學習基礎篇(一)
安裝好Numpy模組後,開始在jupyter上執行沒有問題,後來我開了一個pycharm,建立numpy.py這個檔案後,執行開始報如下錯誤:AttributeError: module ‘numpy‘ has no attribute ‘array‘ 由於下載的模組名也為numpy.p
深度學習基礎概念(一)(科普入門)
1、深度學習(Deep Learning): 是建立在計算機神經網路理論和機器學習理論上的系統科學,它使用建立在複雜的機器結構上的多處理層,結合非線性轉換方法演算法,對高層複雜資料模型進行抽象。 深度學習有兩大要素: (1)資料表示:資料是機器學習
Java多執行緒-基礎篇(一)
多執行緒---基礎篇,本文主要針對多執行緒的基礎進行復習,內容包括執行緒的理解,建立方式,Thread類函式理解; 1、執行緒的理解:OS系統角度:每個執行的程式都會被作業系統建立對應的程序(包括分配的資源、PCB等),程序是作業系統分配資源的基本單位(理解:程式執行需要空間,作業系統建立對應程序時
Java基礎篇(一)--- 資料型別、函式、控制符
基本資料型別 1.整數型別: byte(1 位元組 ), short(2 位元組 ), int(4 位元組 ), long(8 位元組 ) 1位元組=8位,而每一個數的第一位為符號位,並且-0(負零)用-128表示,所以byte的範圍為:-2^(位元組8-1) --> 2^(位元組
JavaScript學習之小白篇(一)
好好學習 ,天天向上。Are you ready? 一、JS概述 1. 什麼是JS? Javascript是(基於物件)和(事件驅動)的(客戶端指令碼)語言。 2. 哪一年?哪家公司?誰?第一個名字? 1995 網景 布蘭登 livescript 3. W3C第一套標準:ECMA-262
深度學習基礎系列(一)| 一文看懂用kersa構建模型的各層含義(掌握輸出尺寸和可訓練引數數量的計算方法)
我們在學習成熟神經模型時,如VGG、Inception、Resnet等,往往面臨的第一個問題便是這些模型的各層引數是如何設定的呢?另外,我們如果要設計自己的網路模型時,又該如何設定各層引數呢?如果模型引數設定出錯的話,其實模型也往往不能運行了。 所以,我們需要首
nodejs之elasticsearch使用:基礎篇(一)
nodejs之elasticsearch使用:基礎篇(一) 前言 程式碼 前言 本節只是嘗試使用nodejs中的elasticsearch模組實現elasticsearch在node環境下的基本增刪改查。 具體方法詳情
深度學習筆記篇(一)VGG + 動量法
新開個坑,作為最近一直忙不寫部落格的一個補償。 在筆記篇中,主要會以流水賬(日記)的形式,覺得囉嗦的朋友可以關注我的後續篇節,有一些Project會單獨拿出來說。 先講個題外話。 因為經常需要更新github上老師的程式碼,於是很多人想要在本地與專案保持更新 通過 g
Java多執行緒之基礎篇(一)
一、併發和並行 1.1 概念 1.2 比較 1.3 程序和執行緒 二、基礎概念 2.1 執
ActiveMQ·基礎篇(一)
JMS 叫做 Java 訊息服務(Java Message Service),是 Java 平臺上有關面向 MOM 的技術規範,旨在通過提供標準的產生、傳送、接收和處理訊息的 API 簡化企業應用的開發,類似於 JDBC 和關係型資料庫通訊方式的抽象。
跟我一起學Mysql基礎篇(一)
資料庫常用的術語 DB:資料庫(databases)儲存資料的“倉庫”。它儲存了一系列有組織的資料 DBMS:資料庫管理系統(Database Management System)。資料庫是通過DBMS
Spring Boot 入門之基礎篇(一)
一、前言 Spring Boot 是由 Pivotal 團隊提供的全新框架,其設計目的是用來簡化新 Spring 應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。 本系列以快速入門為主,可當作工具小手冊閱讀
docker學習 基礎篇(一)
備註:博主腦袋一熱,想做個軟體專案託管平臺,於是踏上了不歸路... 1. 前提知識 1.1 強制,熟悉linux命令和相關背景知識 1.2 建議,Git相關知識 2.docker簡介 2.1是什麼 &nb
python 基礎篇(一)基本資料型別的宣告和定義
最近對python比較感興趣:所以試著先學習了一下基礎部分;順便寫一下大致感受:至於python的環境變數以及開發工具IDE就不多做解釋; helloWord print("helloWord"); python 註釋:
Android JNI基礎篇(一)
Android JNI基礎篇 前言 JNI學習其實並不難,在這裡,我將引導大家學習JNI的基礎知識,認真學完本教程,你將更加堅信我說的話。來吧,我們一起學習! JNI基礎 JNI是什麼? JNI的全稱就是Java Native Interface,顧名思義,就是Java和C
深度影象基礎知識(一)
深度影象(depth image)也被稱為距離影像(range image),是指將從影象採集器到場景中各點的距離(深度)作為畫素值的影象,它直接反映了景物可見表面的幾何形狀。深度影象經過座標轉換可以計算為點雲資料,有規則及必要資訊的點雲資料也可以反算為深度影象資料。