Учебное пособие по DynASM [Перевод]

Язык программирования Принцип составления
Учебное пособие по DynASM [Перевод]

Недавно я использую DynASM, и, кстати, я перевел руководство по неофициальной документации DynASM.DynASM — это препроцессор JIT-сборки и микробиблиотека времени выполнения, написанная для luajit (проще говоря, DynASM выполняет две задачи, одна — предварительная обработка, а вы пишете Инструкции по ассемблеру (да, без Эликсира DynASM не может напрямую превратить логику в ассемблер, вам нужно вручную переписать свою логику на ассемблере, так что производительность тоже зависит от того, насколько хорошо написан ваш ассемблерный код) в настоящий бинарный машинный код, другой — предоставить микросреду выполнения для обработки кода, который должен быть отложен до времени выполнения).

Некоторые языки, написанные на C или C++, и даже интерпретаторы протоколов (такие как обычные интерпретаторы или интерпретаторы json) могут быть объединены JIT с DynASM для повышения производительности.Например, этот проектGitHub.com/open остальное имеет/есть…то есть @agentzh Написан обычный JIT-интерпретатор для различных регулярных сопоставлений в конфигурации nginx.

В этом руководстве интерпретатор языка brainfuck (реализованный на языке C) был преобразован с помощью DynASM в JIT-интерпретатор brainfuck, а производительность была улучшена в 17 раз (выполните тест множества Мандельброта).

Чтобы сказать правду, этот учебник не может быть слишком «учебным пособием», вам нужно понять некоторые из базового дизайна, чтения и понимания и понимания исходного кода переводчика Brainfuck, а также соответствующую сборку, и что такое JIT. Последующий, я буду Также переведите некоторые простые учебники, заинтересованные, чтение одноклассников.

адрес репо:GitHub.com/КА Народный/…

Переведено на:karminski.github.io/dynasm-doc/

Оригинал документа находится по адресу:corsix.github.io/dynasm-doc/

Введение

мы начинаем сbrainfuckИнтерпретатор начинает наш учебник.

#include <stdio.h>
#include <stdlib.h>

#define TAPE_SIZE 30000
#define MAX_NESTING 100

typedef struct bf_state
{
  unsigned char* tape;
  unsigned char (*get_ch)(struct bf_state*);
  void (*put_ch)(struct bf_state*, unsigned char);
} bf_state_t;

#define bad_program(s) exit(fprintf(stderr, "bad program near %.16s: %s\n", program, s))

static void bf_interpret(const char* program, bf_state_t* state)
{
  const char* loops[MAX_NESTING];
  int nloops = 0;
  int n;
  int nskip = 0;
  unsigned char* tape_begin = state->tape - 1;
  unsigned char* ptr = state->tape;
  unsigned char* tape_end = state->tape + TAPE_SIZE - 1;
  for(;;) {
    switch(*program++) {
    case '<':
      for(n = 1; *program == '<'; ++n, ++program);
      if(!nskip) {
        ptr -= n;
        while(ptr <= tape_begin)
          ptr += TAPE_SIZE;
      }
      break;
    case '>':
      for(n = 1; *program == '>'; ++n, ++program);
      if(!nskip) {
        ptr += n;
        while(ptr > tape_end)
          ptr -= TAPE_SIZE;
      }
      break;
    case '+':
      for(n = 1; *program == '+'; ++n, ++program);
      if(!nskip)
        *ptr += n;
      break;
    case '-':
      for(n = 1; *program == '-'; ++n, ++program);
      if(!nskip)
        *ptr -= n;
      break;
    case ',':
      if(!nskip)
        *ptr = state->get_ch(state);
      break;
    case '.':
      if(!nskip)
        state->put_ch(state, *ptr);
      break;
    case '[':
      if(nloops == MAX_NESTING)
        bad_program("Nesting too deep");
      loops[nloops++] = program;
      if(!*ptr)
        ++nskip;
      break;
    case ']':
      if(nloops == 0)
        bad_program("] without matching [");
      if(*ptr)
        program = loops[nloops-1];
      else
        --nloops;
      if(nskip)
        --nskip;
      break;
    case 0:
      if(nloops != 0)
        program = "<EOF>", bad_program("[ without matching ]");
      return;
    }
  }
}

static void bf_putchar(bf_state_t* s, unsigned char c)
{
  putchar((int)c);
}

static unsigned char bf_getchar(bf_state_t* s)
{
  return (unsigned char)getchar();
}

static void bf_run(const char* program)
{
  bf_state_t state;
  unsigned char tape[TAPE_SIZE] = {0};
  state.tape = tape;
  state.get_ch = bf_getchar;
  state.put_ch = bf_putchar;
  bf_interpret(program, &state);
}

int main(int argc, char** argv)
{
  if(argc == 2) {
    long sz;
    char* program;
    FILE* f = fopen(argv[1], "r");
    if(!f) {
      fprintf(stderr, "Cannot open %s\n", argv[1]);
      return 1;
    }
    fseek(f, 0, SEEK_END);
    sz = ftell(f);
    program = (char*)malloc(sz + 1);
    fseek(f, 0, SEEK_SET);
    program[fread(program, 1, sz, f)] = 0;
    fclose(f);
    bf_run(program);
    return 0;
  } else {
    fprintf(stderr, "Usage: %s INFILE.bf\n", argv[0]);
    return 1;
  }
}

В этом уроке мы используем DynASM для написания этого интерпретатора brainfuck в JIT-компилятор Brainfuck.Давайте посмотрим, будет ли он работать быстрее.

Сначала клонируйте этот репозиторий, затем изbf_c.c(Это большой фрагмент кода выше) Запуск:

$ git clone https://github.com/corsix/dynasm-doc.git
$ cd dynasm-doc
$ git submodule update --init
$ cp bf_c.c tutorial.c

Мы демонстрируем функциональность, запустив эту программу, которая медленно визуализирует множество Мандельброта:

$ gcc -o tutorial tutorial.c
$ ./tutorial mandelbrot.bf

(P.S. Мой процессор Intel(R) Xeon(R) CPU E5-2680 v2 @ 2,80 ГГц, макс. 3,5 ГГц, ниже результат вывода, рендеринг занимает 35,4 с)

[root@m01 dynasm-doc]# time ./tutorial mandelbrot.bf
AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEGFFEEEEDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
A                                                                                                 PLJHGGFFEEEDDDDDDDCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB

real	0m35.466s
user	0m35.462s
sys	0m0.002s

Основные работы по земляным работам

Прежде чем шоу продолжится, нам нужно сделать некоторые подготовительные работы.

Includes

Во-первых, нам нужно#includeЗаголовочные файлы для DynASM:

+ #include "luajit-2.0/dynasm/dasm_proto.h"
+ #include "luajit-2.0/dynasm/dasm_x86.h"

Как написано в справочной документации,dasm_proto.hопределить API DynASM,dasm_x86.hОн содержит реализацию вышеуказанного API (x86/x64).

Types

Далее мы будемbf_interpretпереименованbf_compileи измените определение его типа:

- static void bf_interpret(const char* program, bf_state_t* state)
+ static void(* bf_compile(const char* program) )(bf_state_t*)

перед фиксациейbf_interpretможет принимать параметрыconst char*а такжеbf_state_t*, после редактированияbf_compileпринимает только параметрыconst char*раздел и возвращает указатель функции на JIT-скомпилированный код.
bf_interpretФункцию также необходимо изменить:

- bf_interpret(program, &state);
+ bf_compile(program)(&state);

Initialisation

Когда основная работа сделана, следующим шагом будет создание и инициализация состояния DynASM.

Variables

Нам нужен типdasm_State*Эта переменная содержит состояние DynASM, и необходимы еще два других, которые мы объясним позже.
А также нужно удалить переменную интерпретатора:

- int nskip = 0;
+ dasm_State* d;
+ unsigned npc = 8;
+ unsigned nextpc = 0;

.arch

Теперь мы сначала прикоснем директиву Dynnasm, которая является директивой препроцессора Dynnasm. Здесь мы определяем архитектуру платформы, на которой генерируется целевой машинный код, X86 или X64:

+ |.if X64
+ |.arch x64
+ |.else
+ |.arch x86
+ |.endif

Начальные вертикальные полосы распознаются препроцессором DynASM..if, .else, .endifИнструкции будут обрабатываться препроцессором DynASM так же, как и в препроцессоре языка C.#if, #else, #endifАналогично Результат выполнения только один.archКоманда вступит в силу.

dasm_init

мы определяемdasm_State*, теперь нам нужно выделить место в памяти, чтобы поместить его. Вызовdasm_initТолько что:

+ |.section code
+ dasm_init(&d, DASM_MAXSECTION);

Обрати внимание наdasm_State**Такой же,dasm_initнужен одинintegerПараметры, определяющие раздел сгенерированного машинного кода. Нам нужен только раздел кода, поэтому мы передаем параметр в.section, так что препроцессор DynASM обработает его как#define DASM_MAXSECTION 1(между прочим), может быть, датьdasm_initпроходитьDASM_MAXSECTIONнет прямой передачи1Так интуитивно понятно, но это хорошая практика, потому что, возможно, в будущем нам понадобится больше разделов.

dasm_setupglobal

dasm_initбудет выделеноdasm_State, но это не полная инициализация.Для инициализации состояния нам нужно вызвать несколько функций.Первая из нихdasm_setupglobal:

+ |.globals lbl_
+ void* labels[lbl__MAX];
+ dasm_setupglobal(&d, labels, lbl__MAX);

с параметрамиlbl_из.globalsИнструкция DynASM будет предварительно обработана, чтобы содержать структуруenumтипа, один из которыхlbl__MAX, Это значение должно быть той же длины, что иvoid*массив переданdasm_setupglobal. Позже мы будем использоватьlablesмножество.

dasm_setup

Следующий вызов в процессе инициализации — (dasm_setup)[C или DM.GitHub.IO/Что такое боеприпасы-doc/…].

+ |.actionlist bf_actions
+ dasm_setup(&d, bf_actions);

поясbf_actionsпараметрический.actionlistДирективы переписываются препроцессором DynASM дляbf_actionsпеременная и должна быть передана вdasm_setup.

dasm_growpc

При нормальных обстоятельствах,dasm_StateНа этом узле он был полностью инициализирован, но так как нам также нужно использовать динамические метки, нам нужно вызватьdasm_growpcИнициализируйте его снова:

+ dasm_growpc(&d, npc);

Переходим в ранее определенноеnpcПараметр, этот параметр представляет количество динамических меток.Также существует зависимая переменная, называемаяnextpcиспользуется для отслеживания количества используемых нами ярлыков.Эти динамические ярлыки будут использоваться при компиляции[, ]когда это работает.

резюме

Соответствующий код до сих пор:

static void(* bf_compile(const char* program) )(bf_state_t*)
{
  unsigned loops[MAX_NESTING];
  int nloops = 0;
  int n;
  dasm_State* d;
  unsigned npc = 8;
  unsigned nextpc = 0;
  |.if X64
  |.arch x64
  |.else
  |.arch x86
  |.endif
  |.section code
  dasm_init(&d, DASM_MAXSECTION);
  |.globals lbl_
  void* labels[lbl__MAX];
  dasm_setupglobal(&d, labels, lbl__MAX);
  |.actionlist bf_actions
  dasm_setup(&d, bf_actions);
  dasm_growpc(&d, npc);

Abstractions

Прежде чем мы выполним машинный код, давайте определим некоторые абстракции, давайте определим некоторые абстракции, которые делают регистры более значимыми:

Abstraction Corresponding Interpreter Variable Definition
aState state ebx or rbx
aPtr ptr ebp or r12
aTapeBegin tape_begin esi or rsi or r13
aTapeEnd tape_end edi or rdi or r14

Затем определите некоторые вызовы функций:

Abstraction Description
prologue Set up the stack frame, and set aState from the passed parameter.
prepcall1 arg1 Prepare to call a function with one argument, arg1.
prepcall2 arg1, arg2 Prepare to call a function with two arguments, arg1 and arg2.
postcall n Do cleanup after a call to a function with n arguments.
epilogue Tear down the stack frame.

Эти определения.define(обычно) или.macro(в более сложных случаях) и определения разные под x86, x64 POSIX, x64 Windows.

+ |.if X64
+   |.define aPtr, rbx
+   |.define aState, r12
+   |.if WIN
+     |.define aTapeBegin, rsi
+     |.define aTapeEnd, rdi
+     |.define rArg1, rcx
+     |.define rArg2, rdx
+   |.else
+     |.define aTapeBegin, r13
+     |.define aTapeEnd, r14
+     |.define rArg1, rdi
+     |.define rArg2, rsi
+   |.endif
+   |.macro prepcall1, arg1
+     | mov rArg1, arg1
+   |.endmacro
+   |.macro prepcall2, arg1, arg2
+     | mov rArg1, arg1
+     | mov rArg2, arg2
+   |.endmacro
+   |.define postcall, .nop
+   |.macro prologue
+     | push aPtr
+     | push aState
+     | push aTapeBegin
+     | push aTapeEnd
+     | push rax
+     | mov aState, rArg1
+   |.endmacro
+   |.macro epilogue
+     | pop rax
+     | pop aTapeEnd
+     | pop aTapeBegin
+     | pop aState
+     | pop aPtr
+     | ret
+   |.endmacro
+ |.else
+   |.define aPtr, ebx
+   |.define aState, ebp
+   |.define aTapeBegin, esi
+   |.define aTapeEnd, edi
+   |.macro prepcall1, arg1
+     | push arg1
+   |.endmacro
+   |.macro prepcall2, arg1, arg2
+     | push arg2
+     | push arg1
+   |.endmacro
+   |.macro postcall, n
+     | add esp, 4*n
+   |.endmacro
+   |.macro prologue
+     | push aPtr
+     | push aState
+     | push aTapeBegin
+     | push aTapeEnd
+     | mov aState, [esp+20]
+   |.endmacro
+   |.macro epilogue
+     | pop aTapeEnd
+     | pop aTapeBegin
+     | pop aState
+     | pop aPtr
+     | ret 4
+   |.endmacro
+ |.endif

После определения всех этих архитектурных и системных определений для DynASM также необходимо проверить, что эти архитектуры и системы, указанные для DynASM, соответствуют тем, которые известны препроцессору C:

+ ||#if ((defined(_M_X64) || defined(__amd64__)) != X64) || (defined(_WIN32) != WIN)
+ #error "Wrong DynASM flags used: pass `-D X64` and/or `-D WIN` to dynasm.lua as appropriate"
+ #endif

Те, которые начинаются с двух вертикальных полос, будут заменены препроцессором DynASM с.define(Также, если он есть, его также можно заменить на.macro), но другие не изменяются препроцессором DynASM.
При определенных обстоятельствах, еслиx64и/илиWINОпределяется во время предварительной обработки DynASM (здесь1), то он будет заменен на1, Если он не определен во время предварительной обработки DynASM, он остается как есть и заменяется препроцессором C с0.

Emitting Code

После всех этих операций мы, наконец, можем написать машинный код.

Prologue

Первое, что нам нужно выполнить, это некоторый код инициализации, который заменяет часть предыдущего кода интерпретатора:

- unsigned char* tape_begin = state->tape - 1;
- unsigned char* ptr = state->tape;
- unsigned char* tape_end = state->tape + TAPE_SIZE - 1;
+ |.type state, bf_state_t, aState

+ dasm_State** Dst = &d;
+ |.code
+ |->bf_main:
+ | prologue
+ | mov aPtr, state->tape
+ | lea aTapeBegin, [aPtr-1]
+ | lea aTapeEnd, [aPtr+TAPE_SIZE-1]

давайте сначала посмотрим.typeдиректива, которая позволяет нам использоватьstate->tapeЭкспресс как стенография[aState + offsetof(bf_state_t,tape)].

Следующая строка определяетDstи использовать&dЭто делается потому, что препроцессор DynASM перезапишет последующие строки какdasm_put(Dst, ...)формировать звонки и работать с нимиdasm_Как и в функциях, первый параметр должен быть&d.

Далее следует включить.codeЭта строка, указанная здесь инструкция, определяется предыдущей.section codeВводятся инструкции, а выполняемые состояния должны быть помещены в секцию кода (именно с этой частью мы и имеем дело).

После этого мы определяем этикетку->bf_mainКогда мы выполним полный машинный код, вы сможете получить адрес этой глобальной метки и преобразовать ее в указатель на функцию.

Затем мы вызываем ранее определенныйprologueмакрос, который выполняет эти инструкции.

Последние несколько строкmovа такжеleaинструкция, соответствующая удаленным строкам кода интерпретатора.

Как я только что сказал,state->tapeстановится операндомmovОкончательное исполнение[aState + offsetof(bf_state_t,tape)]. Уведомлениеoffsetof(bf_state_t,tape)а такжеTAPE_SIZE-1(часть операнда lea) — это так называемые константы времени кода: DynASM не знает, что это такое, поэтому компилятор C должен их вычислить. Оба значения являются константами времени компиляции в C, и код константы -time не обязательно должны быть константами времени компиляции (поясняется позже с примерами).

Tape Movement

Теперь, когда мы переходим к этапу интерпретатора, первая задача состоит в том, чтобы объяснить<Часть кода заменена на:

- if(!nskip) {
-   ptr -= n;
-   while(ptr <= tape_begin)
-     ptr += TAPE_SIZE;
- }
+ | sub aPtr, n%TAPE_SIZE
+ | cmp aPtr, aTapeBegin
+ | ja >1
+ | add aPtr, TAPE_SIZE
+ |1:

Обратите внимание, что компилятор не имеет понятия о пропуске кода, как это делает интерпретатор, поэтому поместите приведенное вышеifДетали были полностью удалены.ptr -= n;и следующий цикл становится| sub aPtr, n%TAPE_SIZE.
n%TAPE_SIZEявляется константой этапа кода, а не константой времени компиляции C. DynASM также не понимает значения операндов. Но в этом случае, когдаbf_compileОкончательная среда выполнения вычисляет окончательное значение операнда.

компилировать временной цикл%TAPE_SIZEПо истечении определенного периода может потребоваться выполнение итерации во время выполнения, посколькуcmp, ja, addДиректива Примечание Заявление>1Перейти к месту, где определена метка 1, т.е.addследующая строка .

>То же верно и для операторов, за исключением того, чтоadd, subЭта часть перевернута:

- if(!nskip) {
-   ptr += n;
-   while(ptr > tape_end)
-     ptr -= TAPE_SIZE;
- }
+ | add aPtr, n%TAPE_SIZE
+ | cmp aPtr, aTapeEnd
+ | jbe >1
+ | sub aPtr, TAPE_SIZE
+ |1:

Arithmetic

Следующая команда для перезаписи+, относительно просто:

- if(!nskip)
-   *ptr += n;
+ | add byte [aPtr], n

Обратите внимание, что только операторы памяти[aPtr]Предыдущий дескриптор размера памятиbyteПоскольку ни операнды памяти, ни непосредственные операнды не имеют реальных размеров операндов, DynASM необходимо указать явно.
Обратите внимание, что операнд памяти, который мы использовали ранее, не требует спецификатора размера памяти:leaинструкции не требуются, операнды памяти не являются доступом к памяти.mov aPtr, state->tapeЭто и не нужно, так как размер операндов памяти можно вывести из размера операндов регистра, они равны.

-То же самое касается инструкций:

- if(!nskip)
-   *ptr -= n;
+ | sub byte [aPtr], n

I/O

Далее, (read char), .(написать char), стоит отметить, что они нужны для вызова других функций.,:

- if(!nskip)
-   *ptr = state->get_ch(state);
+ | prepcall1 aState
+ | call aword state->get_ch
+ | postcall 1
+ | mov byte [aPtr], al

Обратите внимание на абстрактное определение вызоваprepcall1а такжеpostcallМы определили его ранее. Также обратите внимание, чтоstate->get_chда[aState + offsetof(bf_state_t,get_ch)]сокращенное представление , введенное ранее.typeМы сказали, что при использовании этих сокращенных обозначений.И спецификаторы размера памяти по-прежнему требуются при использовании этих сокращенных обозначений.Размер операндов памяти не определяется автоматически как тот же размер, что и член структуры C с тем же именем.awordСпецификатор (слово размером с адрес) относится либо к 4 байтам (x86), либо к 8 байтам (x64).

.То же самое верно и для преобразования:

- if(!nskip)
-   state->put_ch(state, *ptr);
+ | movzx r0, byte [aPtr]
+ | prepcall2 aState, r0
+ | call aword state->put_ch
+ | postcall 2

Уведомлениеr0В качестве операнда регистра: относится к eax (x86) или rax (x64).

Loops

Теперь настала очередь самой смешной команды[, ]. в[Довольно сложно:

- loops[nloops++] = program;
- if(!*ptr)
-   ++nskip;
+ if(program[0] == '-' && program[1] == ']') {
+   program += 2;
+   | xor eax, eax
+   | mov byte [aPtr], al
+ } else {
+   if(nextpc == npc) {
+     npc *= 2;
+     dasm_growpc(&d, npc);
+   }
+   | cmp byte [aPtr], 0
+   | jz =>nextpc+1
+   |=>nextpc:
+   loops[nloops++] = nextpc;
+   nextpc += 2;
+ }

Во-первых, мы идентифицируем инструкцию[, ]И генерировать для него оптимизированный машинный код, но чтобы исключить частные случаи, обычно требуются два динамических тега:
нужно[прыгать в]после (ранее через интерпретаторnskipосуществленный). другой из]прыгать в[позади (ранее черезloopsреализуется стеком).

Если мы использовали количество выделенных нами динамических меток, мы также можем вызватьdasm_growpcПродолжайте распределять.
Затем мы выпускаемcmpдиректива, которая действует так, как это буквально означает.[aPtr]Байт внутри равен 0, мы переходим к динамической метке=>nextpc+1(мы позже]операторная логика).
Затем мы определяем динамическую метку=>nextpc (]где вам нужно отпрыгнуть назад).nextpc+1а такжеnextpcявляются константами времени кодирования.

После]:

- if(*ptr)
-   program = loops[nloops-1];
- else
-   --nloops;
- if(nskip)
-   --nskip;
+ --nloops;
+ | cmp byte [aPtr], 0
+ | jnz =>loops[nloops]
+ |=>loops[nloops]+1:

Обратите внимание, что условный переход к динамической метке=>loops[nloops](соответственно в[определяется для перехода к=>nextpc), Затем динамическая метка=>loops[nloops]+1(соответственно в[Определение в том, чтобы перейти кjz =>nextpc+1).

Epilogue

После того, как все инструкции выполнены, осталось только собрать и извлечь указатель функции из DynASM:

- return;
+ | epilogue
+ link_and_encode(&d);
+ dasm_free(&d);
+ return (void(*)(bf_state_t*))labels[lbl_bf_main];

Первая строка вызывает наш определенныйepilogueмакрос, вызывается на следующей строкеlink_and_encode(дана через минуту), потом звонитеdasm_free, используемый для сброса состояния DynASM. Наконец, мыlabelsмассив переданdasm_setupglobal, индекс массиваlbl_bf_main(Зависит от.globals lbl_определены и с глобальными тегами-> bf_mainсоответствующий) и преобразовать его в указатель функции .

link_and_encodeФункция определяется следующим образом:

+ #if _WIN32
+ #include <Windows.h>
+ #else
+ #include <sys/mman.h>
+ #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+ #define MAP_ANONYMOUS MAP_ANON
+ #endif
+ #endif

+ static void* link_and_encode(dasm_State** d)
+ {
+   size_t sz;
+   void* buf;
+   dasm_link(d, &sz);
+ #ifdef _WIN32
+   buf = VirtualAlloc(0, sz, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ #else
+   buf = mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ #endif
+   dasm_encode(d, buf);
+ #ifdef _WIN32
+   {DWORD dwOld; VirtualProtect(buf, sz, PAGE_EXECUTE_READ, &dwOld); }
+ #else
+   mprotect(buf, sz, PROT_READ | PROT_EXEC);
+ #endif
+   return buf;
+ }

Стоит отметить, чтоdasm_linkа такжеdasm_encodeОставшиеся вызовы функций используют возможности операционной системы для выделения блока памяти для чтения-записи, который затем преобразуется в чтение-выполнение.
Обратите внимание, что мы можем выделить блок памяти для чтения-записи-исполнения, но, как правило, наличие как доступной для записи, так и исполняемой памяти не является хорошим тоном.

Compiling

Согласно приведенному выше руководству, теперьtutorial.cЭто выглядит так:

||#if ((defined(_M_X64) || defined(__amd64__)) != X64) || (defined(_WIN32) != WIN)
#error "Wrong DynASM flags used: pass `-D X64` and/or `-D WIN` to dynasm.lua as appropriate"
#endif
#include <stdio.h>
#include <stdlib.h>
#include "luajit-2.0/dynasm/dasm_proto.h"
#include "luajit-2.0/dynasm/dasm_x86.h"
#if _WIN32
#include <Windows.h>
#else
#include <sys/mman.h>
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
#endif

static void* link_and_encode(dasm_State** d)
{
  size_t sz;
  void* buf;
  dasm_link(d, &sz);
#ifdef _WIN32
  buf = VirtualAlloc(0, sz, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
  buf = mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#endif
  dasm_encode(d, buf);
#ifdef _WIN32
  {DWORD dwOld; VirtualProtect(buf, sz, PAGE_EXECUTE_READ, &dwOld); }
#else
  mprotect(buf, sz, PROT_READ | PROT_EXEC);
#endif
  return buf;
}

#define TAPE_SIZE 30000
#define MAX_NESTING 100

typedef struct bf_state
{
  unsigned char* tape;
  unsigned char (*get_ch)(struct bf_state*);
  void (*put_ch)(struct bf_state*, unsigned char);
} bf_state_t;

#define bad_program(s) exit(fprintf(stderr, "bad program near %.16s: %s\n", program, s))

static void(* bf_compile(const char* program) )(bf_state_t*)
{
  unsigned loops[MAX_NESTING];
  int nloops = 0;
  int n;
  dasm_State* d;
  unsigned npc = 8;
  unsigned nextpc = 0;
  |.if X64
  |.arch x64
  |.else
  |.arch x86
  |.endif
  |.section code
  dasm_init(&d, DASM_MAXSECTION);
  |.globals lbl_
  void* labels[lbl__MAX];
  dasm_setupglobal(&d, labels, lbl__MAX);
  |.actionlist bf_actions
  dasm_setup(&d, bf_actions);
  dasm_growpc(&d, npc);
  |.if X64
    |.define aPtr, rbx
    |.define aState, r12
    |.if WIN
      |.define aTapeBegin, rsi
      |.define aTapeEnd, rdi
      |.define rArg1, rcx
      |.define rArg2, rdx
    |.else
      |.define aTapeBegin, r13
      |.define aTapeEnd, r14
      |.define rArg1, rdi
      |.define rArg2, rsi
    |.endif
    |.macro prepcall1, arg1
      | mov rArg1, arg1
    |.endmacro
    |.macro prepcall2, arg1, arg2
      | mov rArg1, arg1
      | mov rArg2, arg2
    |.endmacro
    |.define postcall, .nop
    |.macro prologue
      | push aPtr
      | push aState
      | push aTapeBegin
      | push aTapeEnd
      | push rax
      | mov aState, rArg1
    |.endmacro
    |.macro epilogue
      | pop rax
      | pop aTapeEnd
      | pop aTapeBegin
      | pop aState
      | pop aPtr
      | ret
    |.endmacro
  |.else
    |.define aPtr, ebx
    |.define aState, ebp
    |.define aTapeBegin, esi
    |.define aTapeEnd, edi
    |.macro prepcall1, arg1
      | push arg1
    |.endmacro
    |.macro prepcall2, arg1, arg2
      | push arg2
      | push arg1
    |.endmacro
    |.macro postcall, n
      | add esp, 4*n
    |.endmacro
    |.macro prologue
      | push aPtr
      | push aState
      | push aTapeBegin
      | push aTapeEnd
      | mov aState, [esp+20]
    |.endmacro
    |.macro epilogue
      | pop aTapeEnd
      | pop aTapeBegin
      | pop aState
      | pop aPtr
      | ret 4
    |.endmacro
  |.endif

  |.type state, bf_state_t, aState
  
  dasm_State** Dst = &d;
  |.code
  |->bf_main:
  | prologue
  | mov aPtr, state->tape
  | lea aTapeBegin, [aPtr-1]
  | lea aTapeEnd, [aPtr+TAPE_SIZE-1]
  for(;;) {
    switch(*program++) {
    case '<':
      for(n = 1; *program == '<'; ++n, ++program);
      | sub aPtr, n%TAPE_SIZE
      | cmp aPtr, aTapeBegin
      | ja >1
      | add aPtr, TAPE_SIZE
      |1:
      break;
    case '>':
      for(n = 1; *program == '>'; ++n, ++program);
      | add aPtr, n%TAPE_SIZE
      | cmp aPtr, aTapeEnd
      | jbe >1
      | sub aPtr, TAPE_SIZE
      |1:
      break;
    case '+':
      for(n = 1; *program == '+'; ++n, ++program);
      | add byte [aPtr], n
      break;
    case '-':
      for(n = 1; *program == '-'; ++n, ++program);
      | sub byte [aPtr], n
      break;
    case ',':
      | prepcall1 aState
      | call aword state->get_ch
      | postcall 1
      | mov byte [aPtr], al
      break;
    case '.':
      | movzx r0, byte [aPtr]
      | prepcall2 aState, r0
      | call aword state->put_ch
      | postcall 2
      break;
    case '[':
      if(nloops == MAX_NESTING)
        bad_program("Nesting too deep");
      if(program[0] == '-' && program[1] == ']') {
        program += 2;
        | xor eax, eax
        | mov byte [aPtr], al
      } else {
        if(nextpc == npc) {
          npc *= 2;
          dasm_growpc(&d, npc);
        }
        | cmp byte [aPtr], 0
        | jz =>nextpc+1
        |=>nextpc:
        loops[nloops++] = nextpc;
        nextpc += 2;
      }
      break;
    case ']':
      if(nloops == 0)
        bad_program("] without matching [");
      --nloops;
      | cmp byte [aPtr], 0
      | jnz =>loops[nloops]
      |=>loops[nloops]+1:
      break;
    case 0:
      if(nloops != 0)
        program = "<EOF>", bad_program("[ without matching ]");
      | epilogue
      link_and_encode(&d);
      dasm_free(&d);
      return (void(*)(bf_state_t*))labels[lbl_bf_main];
    }
  }
}

static void bf_putchar(bf_state_t* s, unsigned char c)
{
  putchar((int)c);
}

static unsigned char bf_getchar(bf_state_t* s)
{
  return (unsigned char)getchar();
}

static void bf_run(const char* program)
{
  bf_state_t state;
  unsigned char tape[TAPE_SIZE] = {0};
  state.tape = tape;
  state.get_ch = bf_getchar;
  state.put_ch = bf_putchar;
  bf_compile(program)(&state);
}

int main(int argc, char** argv)
{
  if(argc == 2) {
    long sz;
    char* program;
    FILE* f = fopen(argv[1], "r");
    if(!f) {
      fprintf(stderr, "Cannot open %s\n", argv[1]);
      return 1;
    }
    fseek(f, 0, SEEK_END);
    sz = ftell(f);
    program = (char*)malloc(sz + 1);
    fseek(f, 0, SEEK_SET);
    program[fread(program, 1, sz, f)] = 0;
    fclose(f);
    bf_run(program);
    return 0;
  } else {
    fprintf(stderr, "Usage: %s INFILE.bf\n", argv[0]);
    return 1;
  }
}

Если вы не отставали, вы также можете получить код отсюда:

$ git clone https://github.com/corsix/dynasm-doc.git
$ cd dynasm-doc
$ git submodule update --init
$ cp bf_dynasm.c tutorial.c

чтобы скомпилироватьtutorial.c, нам сначала нужно запустить его через препроцессор DynASM.Препроцессор написан на Lua, поэтому сначала компилируем минимальный интерпретатор Lua (если у вас есть luajit, вы также можете запустить dynasm.lua напрямую с помощью luajit, этот шаг можно пропустить) :

$ gcc -o minilua luajit-2.0/src/host/minilua.c

Затем запустите препроцессор DynASM:

$ ./minilua luajit-2.0/dynasm/dynasm.lua -o tutorial.posix64.c -D X64 tutorial.c

После завершения предварительной обработки вызовите компилятор C:

$ gcc -o tutorial tutorial.posix64.c

Затем мы можем запустить полученный исполняемый файл, который вскоре запустит набор Мандельброта:

$ ./tutorial mandelbrot.bf

(Мой бегущий результат 2.129с, исходная программа 35.466с, расход времени 6% от исходного, а производительность улучшена в 17 раз)

[root@m01 dynasm-doc]# time ./tutorial mandelbrot.bf
AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEGFFEEEEDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
A                                                                                                 PLJHGGFFEEEDDDDDDDCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB

real	0m2.129s
user	0m2.126s
sys	0m0.003s