Недавно я использую 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