크롬 V8을 이해하자--제21장: 컴파일러 작업 흐름: 토큰, AST

40701 단어
원본 소스: https://javascript.plainenglish.io/lets-understand-chrome-v8-compiler-workflow-token-ast-8f629bd79803
Let’s Understand Chrome V8의 다른 장에 오신 것을 환영합니다.

지난 기사에서 우리는 파서에 대해 이야기했고 스캐너가 수동적이며 파서에 의해 깨어날 필요가 있다는 것을 알았습니다. 여기서는 컴파일러 작업 흐름, 토큰 관찰 및 AST를 자세히 안내합니다.

1. 스캐너와 토큰



다음 함수는 AST 생성을 담당하며 스캐너 초기화, 스캐너 실행 및 파서 토큰을 포함하며 여기에서 컴파일러 워크플로를 자세히 볼 수 있습니다.

1.  FunctionLiteral* Parser::ParseProgram(Isolate* isolate, ParseInfo* info) {
2.  //omit.................
3.    scanner_.Initialize();
4.    scanner_.SkipHashBang();
5.    FunctionLiteral* result = DoParseProgram(isolate, info);
6.    MaybeResetCharacterStream(info, result);
7.    MaybeProcessSourceRanges(info, result, stack_limit_);
8.    HandleSourceURLComments(isolate, info->script());
9.  //omit..................
10.    return result;
11.  }


3행은 스캐너 초기화이며, 아래는 소스 코드입니다.

1.  void Scanner::Initialize() {
2.    Init();
3.    next().after_line_terminator = true;
4.    Scan();
5.  }


3행에서 Init()는 스캐너를 초기화합니다. 5행에서 Scan()은 토큰만 생성합니다. 스캐너는 파서에 의해 구동된다는 점을 기억하세요. 이 토큰은 먼저 파서를 깨우고 곧 토큰 캐시 미스를 트리거하는 데 사용되며 결국 컴파일러 파이프라인을 시작합니다. . Init()에 대해 더 자세히 살펴보겠습니다.

1.  void Init() {
2.    Advance();
3.    current_ = &token_storage_[0];
4.    next_ = &token_storage_[1];
5.    next_next_ = &token_storage_[2];
6.    found_html_comment_ = false;
7.    scanner_error_ = MessageTemplate::kNone;
8.  }
9.  //.........................
10.  template <bool capture_raw = false>
11.  void Advance() {
12.    if (capture_raw) {
13.      AddRawLiteralChar(c0_);
14.    }
15.    c0_ = source_->Advance();
16.  }
17.  //................
18.  inline uc32 Advance() {
19.     uc32 result = Peek();
20.     buffer_cursor_++;
21.     return result;
22.   }
23.   //...................
24.   inline uc32 Peek() {
25.     if (V8_LIKELY(buffer_cursor_ < buffer_end_)) {
26.       return static_cast<uc32>(*buffer_cursor_);
27.     } else if (ReadBlockChecked()) {
28.       return static_cast<uc32>(*buffer_cursor_);
29.     } else {
30.       return kEndOfInput;
31.     }
32.   }


2행에서 Advance()는 개발자가 실제로 작성하는 JavaScript 코드에서 문자를 가져옵니다.
라인 11은 Advance() 소스 코드를 보여줍니다. 15행에서 ​​c0_ 키워드는 Advance()가 꺼낼 다음 문자를 가리킵니다. source_->Advance의 정의는 18행에 있습니다. 24행을 보십시오. Peek()는 JavaScript에서 문자를 가져오는 실제 함수입니다. 당신의 자바스크립트.
void Scanner::Initialize()로 돌아가서 Scan()에 대해 살펴보겠습니다.

1.  void Scanner::Scan() { Scan(next_); }
2.  //....................
3.  void Scanner::Scan(TokenDesc* next_desc) {
4.    next_desc->token = ScanSingleToken();
5.    DCHECK_IMPLIES(has_parser_error(), next_desc->token == Token::ILLEGAL);
6.    next_desc->location.end_pos = source_pos();
7.  }
8.  //.....................
9.  V8_INLINE Token::Value Scanner::ScanSingleToken() {
10.    Token::Value token;
11.    do {
12.      next().location.beg_pos = source_pos();
13.      if (V8_LIKELY(static_cast<unsigned>(c0_) <= kMaxAscii)) {
14.        token = one_char_tokens[c0_];
15.        switch (token) {//omit.....................
16.          case Token::IDENTIFIER:
17.            return ScanIdentifierOrKeyword();
18.          default:
19.            UNREACHABLE();
20.        }
21.      }
22.      if (IsIdentifierStart(c0_) ||
23.          (CombineSurrogatePair() && IsIdentifierStart(c0_))) {
24.        return ScanIdentifierOrKeyword();
25.      }
26.      if (c0_ == kEndOfInput) {
27.        return source_->has_parser_error() ? Token::ILLEGAL : Token::EOS;
28.      }
29.      token = SkipWhiteSpace();
30.      // Continue scanning for tokens as long as we're just skipping whitespace.
31.    } while (token == Token::WHITESPACE);
32.    return token;
33.  }


4행에서 ScanSingleToken()은 Advance()를 사용하여 종료자를 만날 때까지 문자를 하나씩 가져옵니다.
우리의 경우 시작 문자는 f이고 f는 다음 문자에 따라 키워드 기능 또는 일반 사용자 정의 변수를 의미할 수 있음을 알고 있습니다. 우리의 경우는 키워드 함수이므로 16-17행으로 이동합니다.
스캐너의 기본은 유한 상태 자동 장치이며, V8은 사전 정의된 매크로 템플릿과 스위치 케이스를 사용하여 문자를 스캔한 다음 관련 문자를 캐시에 채워질 토큰으로 함께 구성합니다.
ScanIdentifierOrKeyword()를 살펴보겠습니다.

1.  V8_INLINE Token::Value Scanner::ScanIdentifierOrKeywordInner() {
2.    STATIC_ASSERT(arraysize(character_scan_flags) == kMaxAscii + 1);
3.    if (V8_LIKELY(static_cast<uint32_t>(c0_) <= kMaxAscii)) {
4.      if (V8_LIKELY(c0_ != '\\')) {
5.        uint8_t scan_flags = character_scan_flags[c0_];
6.        DCHECK(!TerminatesLiteral(scan_flags));
7.  //omit....................
8.         AdvanceUntil([this, &scan_flags](uc32 c0) {
9.           if (V8_UNLIKELY(static_cast<uint32_t>(c0) > kMaxAscii)) {
10.              scan_flags |=
11.                  static_cast<uint8_t>(ScanFlags::kIdentifierNeedsSlowPath);
12.              return true;
13.            }
14.            uint8_t char_flags = character_scan_flags[c0];
15.            scan_flags |= char_flags;
16.          });
17.          if (V8_LIKELY(!IdentifierNeedsSlowPath(scan_flags))) {
18.             //omit...................
19.          }
20.          can_be_keyword = CanBeKeyword(scan_flags);
21.        } else {
22.    //omit................
23.        }
24.      }
25.      return ScanIdentifierOrKeywordInnerSlow(escaped, can_be_keyword);
26.    }


ScanIdentifierOrKeywordInner()는 한 번의 실행에서 하나의 토큰을 생성합니다.

2 AST 생성



DoParseProgram()은 AST 트리 생성을 담당합니다.

1.  FunctionLiteral* Parser::DoParseProgram(Isolate* isolate, ParseInfo* info) {
2.    ParsingModeScope mode(this, allow_lazy_ ? PARSE_LAZILY : PARSE_EAGERLY);
3.    FunctionLiteral* result = nullptr;
4.    {
5.      DeclarationScope* scope = outer->AsDeclarationScope();
6.      scope->set_start_position(0);
7.      FunctionState function_state(&function_state_, &scope_, scope);
8.      ScopedPtrList<Statement> body(pointer_buffer());
9.      int beg_pos = scanner()->location().beg_pos;
10.      if (parsing_module_) {
11.  //omit................
12.      } else if (info->is_wrapped_as_function()) {
13.        ParseWrapped(isolate, info, &body, scope, zone());
14.      } else {
15.        this->scope()->SetLanguageMode(info->language_mode());
16.        ParseStatementList(&body, Token::EOS);
17.      }
18.      scope->set_end_position(peek_position());
19.      if (is_strict(language_mode())) {
20.        CheckStrictOctalLiteral(beg_pos, end_position());
21.      }
22.      if (is_sloppy(language_mode())) {
23.        InsertSloppyBlockFunctionVarBindings(scope);
24.      }
25.      if (info->is_eval()) {
26.        DCHECK(parsing_on_main_thread_);
27.        info->ast_value_factory()->Internalize(isolate);
28.      }
29.      CheckConflictingVarDeclarations(scope);
30.    }
31.    info->set_max_function_literal_id(GetLastFunctionLiteralId());
32.    return result;
33.  }


2행 get out lazy 옵션, 기본적으로 lazy는 true이며 flag-definitions.h에 정의되어 있습니다. 8행은 빈 AST 본문을 작성합니다.
16행에서 ParseStatementList()는 토큰을 구문 분석하고 AST를 생성합니다.

1.  void ParserBase<Impl>::ParseStatementList(StatementListT* body,
2.                                            Token::Value end_token) {
3.    DCHECK_NOT_NULL(body);
4.    while (peek() == Token::STRING) {
5.  //omit.........
6.    }
7.    TargetScopeT target_scope(this);
8.    while (peek() != end_token) {
9.      StatementT stat = ParseStatementListItem();
10.      if (impl()->IsNull(stat)) return;
11.      if (stat->IsEmptyStatement()) continue;
12.      body->Add(stat);
13.    }
14.  }


4행에서 peek()는 현재 토큰의 유형을 반환합니다. 우리의 경우 첫 번째 토큰은 Token::FUNCTION이며 Token::STRING도 end_token도 아니므로 ParseStatementListItem()으로 이동합니다.

1.  ParserBase<Impl>::ParseStatementListItem() {
2.    switch (peek()) {
3.      case Token::FUNCTION:
4.        return ParseHoistableDeclaration(nullptr, false);
5.      case Token::CLASS:
6.        Consume(Token::CLASS);
7.        return ParseClassDeclaration(nullptr, false);
8.      case Token::VAR:
9.      case Token::CONST:
10.        return ParseVariableStatement(kStatementListItem, nullptr);
11.      case Token::LET:
12.        if (IsNextLetKeyword()) {
13.          return ParseVariableStatement(kStatementListItem, nullptr);
14.        }
15.        break;
16.      case Token::ASYNC:
17.        if (PeekAhead() == Token::FUNCTION &&
18.            !scanner()->HasLineTerminatorAfterNext()) {
19.          Consume(Token::ASYNC);
20.          return ParseAsyncFunctionDeclaration(nullptr, false);
21.        }
22.        break;
23.      default:
24.        break;
25.    }
26.    return ParseStatement(nullptr, nullptr, kAllowLabelledFunctionStatement);
27.  }


ParseStatementListItem에서 토큰 유형에 따라 다음 작업을 수행합니다. 정확하게 ParseStatementListItem은 미리 정의된 매크로 템플릿과 스위치 케이스를 사용하여 문자를 일치시키고 분석하는 유한 상태 자동 장치이기도 합니다.
그림 1은 호출 스택을 보여줍니다.


JavaScript에서 명령문은 변수 정의, 함수 또는 명령문 블록일 수 있으므로 ParseStatementListItem()은 자주 자신을 재귀적으로 호출합니다.
테이크아웃
(1) 컴파일러의 기본은 유한 상태 자동 장치를 사용하여 문자를 분석합니다. 특히 V8은 매크로 템플릿과 스위치 케이스를 사용합니다.
(2) 컴파일러의 최소 세분성은 JavaScript 함수입니다.
(3) 지연 컴파일이 여기에 있기 때문에 함수가 곧 실행될 때 컴파일됩니다.
(4) FunctionLiteral은 JavaScript 함수를 나타내는 AST 트리를 보유합니다.
(5) 다음은 Token을 정의하는 매크로 템플릿입니다.

1.  #define IGNORE_TOKEN(name, string, precedence)
2.  /* Binary operators */
3.  /* ADD and SUB are at the end since they are UnaryOp */
4.  #define BINARY_OP_TOKEN_LIST(T, E) \
5.  #define EXPAND_BINOP_ASSIGN_TOKEN(T, name, string, precedence) \
6.    T(ASSIGN_##name, string "=", 2)
7.  #define EXPAND_BINOP_TOKEN(T, name, string, precedence) \
8.    T(name, string, precedence)
9.  #define TOKEN_LIST(T, K)                                           \
10.    T(TEMPLATE_SPAN, nullptr, 0)                                     \
11.    T(TEMPLATE_TAIL, nullptr, 0)                                     \
12.    /* BEGIN Property */                                             \
13.    T(PERIOD, ".", 0)                                                \
14.    T(LBRACK, "[", 0)                                                \
15.    /* END Property */                                               \
16.    /* END Member */                                                 \
17.    T(QUESTION_PERIOD, "?.", 0)                                      \
18.    T(LPAREN, "(", 0)                                                \
19.    /* END PropertyOrCall */                                         \


자, 이것으로 이번 공유를 마치겠습니다. 다음에 뵙겠습니다 여러분 건강하세요!

문제가 있으면 저에게 연락해 주세요. 위챗: qq9123013 이메일: [email protected]

좋은 웹페이지 즐겨찾기