2014-11-24 11 views
7

Я пишу ассемблер/дизассемблер многоархитектуры в Common Lisp (SBCL 1.1.5 в 64-разрядном Debian GNU/Linux), в настоящее время ассемблер создает правильный код для подмножество x86-64. Для сборки x86-64 кода сборки я использую хэш-таблицу, в которой инструкция сборки мнемоника (строки), такие как "jc-rel8" и "stosb" являются ключи, которые возвращают список 1 или более функций кодирования, как те ниже:CLOS make-instance очень медленный и вызывает изнурение кучи в SBCL

 
(defparameter *emit-function-hash-table-x64* (make-hash-table :test 'equalp)) 
(setf (gethash "jc-rel8" *emit-function-hash-table-x64*) (list #'jc-rel8-x86)) 
(setf (gethash "stosb" *emit-function-hash-table-x64*) (list #'stosb-x86)) 

функции кодирования, как эти (некоторые являются более сложными, хотя):

 
(defun jc-rel8-x86 (arg1 &rest args) 
    (jcc-x64 #x72 arg1)) 

(defun stosb-x86 (&rest args) 
    (list #xaa)) 

Теперь я пытаюсь включить полную инструкцию x86-64, установленный с помощью NASM's (NASM 2.11.06) кодирования инструкции данных (файл insns.dat) преобразуется в Common Lisp CLOS синтаксис. Это означало бы замену регулярных функций, используемых для испускания двоичного кода (например, вышеперечисленных функций) с экземплярами пользовательского класса x86-asm-instruction (очень простой класс, до 20 слотов с :initarg, :reader, :initform и т. Д.), В котором метод emit с аргументами будут использоваться для испускания двоичного кода для данной инструкции (мнемоника) и аргументов. Преобразованные данные команд выглядят так (но это более 40 000 строк и ровно 7193 make-instance и 7193 setf).

 

;; first mnemonic + operand combination instances (:is-variant t). 
;; there are 4928 such instances for x86-64 generated from NASM's insns.dat. 

(eval-when (:compile-toplevel :load-toplevel :execute) 

(setf Jcc-imm-near (make-instance 'x86-asm-instruction 
:name "Jcc" 
:operands "imm|near" 
:code-string "[i: odf 0f 80+c rel]" 
:arch-flags (list "386" "BND") 
:is-variant t)) 

(setf STOSB-void (make-instance 'x86-asm-instruction 
:name "STOSB" 
:operands "void" 
:code-string "[ aa]" 
:arch-flags (list "8086") 
:is-variant t)) 

;; then, container instances which contain (or could be refer to instead) 
;; the possible variants of each instruction. 
;; there are 2265 such instances for x86-64 generated from NASM's insns.dat. 

(setf Jcc (make-instance 'x86-asm-instruction 
         :name "Jcc" 
         :is-container t 
         :variants (list Jcc-imm-near 
             Jcc-imm64-near 
             Jcc-imm-short 
             Jcc-imm 
             Jcc-imm 
             Jcc-imm 
             Jcc-imm))) 

(setf STOSB (make-instance 'x86-asm-instruction 
          :name "STOSB" 
          :is-container t 
          :variants (list STOSB-void))) 

;; thousands of objects more here... 

) ; this bracket closes (eval-when (:compile-toplevel :load-toplevel :execute) 

Я преобразовал insns.dat в общий синтаксис Lisp NASM-(как выше), используя тривиальный сценарий Perl (далее, но нет ничего интересного в самом скрипте) и в принципе он работает. Таким образом, это работает, но компиляция этих 7193 объектов действительно очень медленная и обычно вызывает истощение кучи. На моем Linux Ядро i7-2760QM ноутбук с 16G из памяти составление блока (eval-when (:compile-toplevel :load-toplevel :execute) кода с 7193 объектов, как те выше занимает более 7 минут, а иногда и вызывает кучу вытяжку, как этот:

 
;; Swank started at port: 4005. 
* Heap exhausted during garbage collection: 0 bytes available, 32 requested. 
Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB LUB !move Alloc Waste Trig WP GCs Mem-age 
    0:  0  0  0  0  0  0  0  0  0  0  0 41943040 0 0 0.0000 
    1:  0  0  0  0  0  0  0  0  0  0  0 41943040 0 0 0.0000 
    2:  0  0  0  0  0  0  0  0  0  0  0 41943040 0 0 0.0000 
    3: 38805 38652  0  0 49474 15433 389 416  0 2144219760 9031056 1442579856 0 1 1.5255 
    4: 127998 127996  0  0 45870 14828 106 143 199 1971682720 25428576 2000000 0 0 0.0000 
    5:  0  0  0  0  0  0  0  0  0  0  0 2000000 0 0 0.0000 
    6:  0  0  0  0 1178 163  0  0  0 43941888  0 2000000 985 0 0.0000 
    Total bytes allocated = 4159844368 
    Dynamic-space-size bytes = 4194304000 
GC control variables: 
    *GC-INHIBIT* = true 
    *GC-PENDING* = in progress 
    *STOP-FOR-GC-PENDING* = false 
fatal error encountered in SBCL pid 9994(tid 46912556431104): 
Heap exhausted, game over. 

Welcome to LDB, a low-level debugger for the Lisp runtime environment. 
ldb> 

я имел добавить параметр --dynamic-space-size 4000 для SBCL, чтобы он вообще скомпилировался, но все же после выделения 4 гигабайт динамической кучи пространства иногда истощается. Даже если бы исчерпание кучи было решено, более 7 минут для компиляции 7193 экземпляров после добавления только слота в классе (класс 'x86-asm-instruction, используемый для этих экземпляров), слишком много для интерактивной разработки в REPL (я использую slimv, если это имеет значение).

Вот (time (compile-file выхода:

 
; caught 18636 WARNING conditions 


; insns.fasl written 
; compilation finished in 0:07:11.329 
Evaluation took: 
    431.329 seconds of real time 
    238.317000 seconds of total run time (234.972000 user, 3.345000 system) 
    [ Run times consist of 6.073 seconds GC time, and 232.244 seconds non-GC time. ] 
    55.25% CPU 
    50,367 forms interpreted 
    784,044 lambdas converted 
    1,031,842,900,608 processor cycles 
    19,402,921,376 bytes consed 

Используя ООП (CLOS) даст возможность включения мнемоника команд (такие, как jc или stosb выше, :name), позволил операнды инструкции (:operands), двоичной кодировка назначенной команды (такие как #xaa для stosb, :code-string) и возможные ограничения архитектуры (:arch-flags) инструкции в одном объекте. Но кажется, что, по крайней мере, мой 3-летний компьютер недостаточно эффективен, чтобы быстро скомпилировать около 7000 объектов CLOS.

Мой вопрос: есть ли способ сделать SBCL's make-instance быстрее, или я должен продолжать генерировать код сборки в обычных функциях, таких как примеры выше? Я был бы очень рад узнать о любых других возможных решениях.

Вот сценарий Perl, на всякий случай:

#!/usr/bin/env perl 
use strict; 
use warnings; 

# this program converts NASM's `insns.dat` to Common Lisp Object System (CLOS) syntax. 

my $firstchar; 
my $line_length; 
my $are_there_square_brackets; 
my $mnemonic_and_operands; 
my $mnemonic; 
my $operands; 
my $code_string; 
my $flags; 
my $mnemonic_of_current_mnemonic_array; 

my $clos_object_name; 
my $clos_mnemonic; 
my $clos_operands; 
my $clos_code_string; 
my $clos_flags; 

my @object_name_array =(); 
my @mnemonic_array =(); 
my @operands_array =(); 
my @code_string_array =(); 
my @flags_array =(); 

my @each_mnemonic_only_once_array =(); 

my @instruction_variants_array =(); 
my @instruction_variants_for_current_instruction_array =(); 

open(FILE, 'insns.dat'); 

$mnemonic_of_current_mnemonic_array = ""; 

# read one line at once. 
while (<FILE>) 
{ 
    $firstchar = substr($_, 0, 1); 
    $line_length = length($_); 
    $are_there_square_brackets = ($_ =~ /\[.*\]/); 
    chomp; 
    if (($line_length > 1) && ($firstchar =~ /[^\t ;]/)) 
    { 
     if ($are_there_square_brackets) 
     { 
      ($mnemonic_and_operands, $code_string, $flags) = split /[\[\]]+/, $_; 
      $code_string = "[" . $code_string . "]"; 
      ($mnemonic, $operands) = split /[\t ]+/, $mnemonic_and_operands; 
     } 
     else 
     { 
      ($mnemonic, $operands, $code_string, $flags) = split /[\t ]+/, $_; 
     } 
     $mnemonic =~ s/[\t ]+/ /g; 
     $operands =~ s/[\t ]+/ /g; 
     $code_string =~ s/[\t ]+/ /g; 
     $flags =~ s/[\t ]+//g; 

     # we don't want non-x86-64 instructions here. 
     unless ($flags =~ "NOLONG") 
     { 
      # ok, the content of each field is now filtered, 
      # let's convert them to a suitable Common Lisp format. 
      $clos_object_name = $mnemonic . "-" . $operands; 

      # in Common Lisp object names `|`, `,`, and `:` must be escaped with a backslash `\`, 
      # but that would get too complicated. 
      # so we'll simply replace them: 
      # `|` -> `-`. 
      # `,` -> `.`. 
      # `:` -> `.`. 
      $clos_object_name =~ s/\|/-/g;    
      $clos_object_name =~ s/,/./g;    
      $clos_object_name =~ s/:/./g;    

      $clos_mnemonic = "\"" . $mnemonic . "\""; 
      $clos_operands = "\"" . $operands . "\""; 
      $clos_code_string = "\"" . $code_string . "\""; 

      $clos_flags = "\"" . $flags . "\"";  # add first and last double quotes. 
      $clos_flags =~ s/,/" "/g;     # make each flag its own Common Lisp string. 
      $clos_flags = "(list " . $clos_flags. ")"; # convert to `list` syntax. 

      push @object_name_array, $clos_object_name; 
      push @mnemonic_array, $clos_mnemonic; 
      push @operands_array, $clos_operands; 
      push @code_string_array, $clos_code_string; 
      push @flags_array, $clos_flags; 

      if ($mnemonic eq $mnemonic_of_current_mnemonic_array) 
      { 
       # ok, same mnemonic as the previous one, 
       # so the current object name goes to the list. 
       push @instruction_variants_for_current_instruction_array, $clos_object_name; 
      } 
      else 
      { 
       # ok, this is a new mnemonic. 
       # so we'll mark this as current mnemonic. 
       $mnemonic_of_current_mnemonic_array = $mnemonic; 
       push @each_mnemonic_only_once_array, $mnemonic; 

       # we first push the old array (unless it's empty), then clear it, 
       # and then push the current object name to the cleared array. 

       if (@instruction_variants_for_current_instruction_array) 
       { 
        # push the variants array, unless it's empty. 
        push @instruction_variants_array, [ @instruction_variants_for_current_instruction_array ]; 
       } 
       @instruction_variants_for_current_instruction_array =(); 
       push @instruction_variants_for_current_instruction_array, $clos_object_name; 
      } 
     } 
    } 
} 

# the last instruction's instruction variants must be pushed too. 
if (@instruction_variants_for_current_instruction_array) 
{ 
    # push the variants array, unless it's empty. 
    push @instruction_variants_array, [ @instruction_variants_for_current_instruction_array ]; 
} 

close(FILE); 

# these objects need be created already during compilation. 
printf("(eval-when (:compile-toplevel :load-toplevel :execute)\n"); 

# print the code to create each instruction + operands combination object. 

for (my $i=0; $i <= $#mnemonic_array; $i++) 
{ 
    $clos_object_name = $object_name_array[$i]; 
    $mnemonic   = $mnemonic_array[$i]; 
    $operands   = $operands_array[$i]; 
    $code_string  = $code_string_array[$i]; 
    $flags   = $flags_array[$i]; 

    # print the code to create a variant object. 
    # each object here is a variant of a single instruction (or a single mnemonic). 
    # actually printed as 6 lines to make it easier to read (for us humans, I mean), with an empty line in the end. 
    printf("(setf %s (make-instance 'x86-asm-instruction\n:name %s\n:operands %s\n:code-string %s\n:arch-flags %s\n:is-variant t))", 
     $clos_object_name, 
     $mnemonic, 
     $operands, 
     $code_string, 
     $flags); 
    printf("\n\n"); 
} 

# print the code to create each instruction + operands combination object. 

# for (my $i=0; $i <= $#each_mnemonic_only_once_array; $i++) 
for my $i (0 .. $#instruction_variants_array) 
{ 
    $mnemonic = $each_mnemonic_only_once_array[$i]; 

    # print the code to create a container object. 
    printf("(setf %s (make-instance 'x86-asm-instruction :name \"%s\" :is-container t :variants (list \n", $mnemonic, $mnemonic); 
    @instruction_variants_for_current_instruction_array = $instruction_variants_array[$i]; 

    # for (my $j=0; $j <= $#instruction_variants_for_current_instruction_array; $j++) 
    for my $j (0 .. $#{$instruction_variants_array[$i]}) 
    { 
     printf("%s", $instruction_variants_array[$i][$j]); 

     # print 3 closing brackets if this is the last variant. 
     if ($j == $#{$instruction_variants_array[$i]}) 
     { 
      printf(")))"); 
     } 
     else 
     { 
      printf(" "); 
     } 
    } 

    # if this is not the last instruction, print two newlines. 
    if ($i < $#instruction_variants_array) 
    { 
     printf("\n\n"); 
    } 
} 

# print the closing bracket to close `eval-when`. 
print(")"); 

exit; 

ответ

10

18636 предупреждения выглядит очень плохо, Start, избавившись от всех предупреждений.

Я бы начал с избавления от EVAL-WHEN вокруг всего этого. Не имеет для меня никакого смысла. Либо загрузите файл напрямую, либо скомпилируйте и загрузите файл.

Также обратите внимание, что SBCL не любит (setf STOSB-void ...), когда переменная не определена. Новые переменные верхнего уровня вводятся с DEFVAR или DEFPARAMETER. SETF просто устанавливает их, но не определяет их. Это должно помочь избавиться от предупреждений.

Также :is-container t и :is-variant t Запах, подобный этим свойствам, должен быть преобразован в классы для наследования (например, как mixin). Контейнер имеет варианты. Вариант не имеет вариантов.

+2

исправление 'deffamameter'' setf' -> сократило время компиляции с 7 минут до 2 минут (мне пришлось префикс имен переменных, потому что '(defparameter DO foo)' вызывает ошибку 'Блокировка на пакете COMMON-LISP нарушена, когда глобально объявляя DO специальным, находясь в пакете COMMON-LISP-USER' (в моем собственном пакете это происходит из-за '(: use: cl)'). Затем я создал класс контейнера для каждой команды (мнемоника), отбросил 'is- контейнером и 'is-variant', и создал экземпляр класса контейнера для каждого варианта, он отбросил время компиляции с 2 минут до 2 секунд. Большое спасибо, он отлично работает. – nrz

+5

@nrz: это довольно улучшилось. от 2 до 2 секунд. Faktor 210. Btw. вы также можете создать собственный пакет для переменных. Пакет, который не будет: использовать Common Lisp ... –

 Смежные вопросы

  • Нет связанных вопросов^_^