Самый маленький интерпретатор байт-кода / VM

17

Таблица лидеров - JIT скомпилировано (чем ниже, тем лучше)

  1. es1024 - 81,2 балла (включая работающий компилятор!)
  2. Кит Рэндалл - 116 очков
  3. Элл - 121 очко

Таблица лидеров - Интерпретируется (чем ниже, тем лучше)

  1. Мартин Бюттнер - 706654 балла (где-то около 2 часов).
  2. криптих - 30379 баллов (97 секунд)

Ваша миссия, если вы решите принять ее, - написать наименьший возможный интерпретатор байт-кода / VM. ВМ / интерпретатор использует небольшую архитектуру CISC (операции могут различаться по размеру) с языком, указанным ниже. После завершения вы должны напечатать значение 3 регистров ЦП, чтобы доказать, что выводится правильный вывод (3 126 900 366).

составитель

Если вы хотите сделать свои собственные тесты, компилятор размещен ниже. Не стесняйтесь размещать свои тесты с вашим ответом.

"ВМ" Технические характеристики

Виртуальная машина имеет 3 32-битных целочисленных регистра без знака: R0, R1, R2. Они представлены в шестнадцатеричном виде как 0x00, 0x01 и 0x02.

Следующие операции должны поддерживаться:

Формат: [имя] [... операнды ...], [шестнадцатеричный код операции] [... операнды повторяются ...]

  • LOAD [регистр] [4-байтовое значение], 0x00 [регистр] [4-байтовое значение]
  • PUSH [регистр], 0x02 [регистр]
  • POP [регистрация], 0x03 [регистрация]
  • ADD [регистр, 1 байт] [регистр, 1 байт], 0x04 [регистр] [регистр]
  • SUB [регистр, 1 байт] [регистр, 1 байт], 0x05 [регистр] [регистр]
  • MUL [регистр, 1 байт] [регистр, 1 байт], 0x06 [регистр] [регистр]
  • DIV [регистр, 1 байт] [регистр, 1 байт], 0x07 [регистр] [регистр]
  • JMP [строка кода, 4 байта], 0x08 [номер строки кода 4 байта]
  • CMP [регистр, 1 байт] [регистр, 1 байт], 0x09 [регистр] [регистр]
  • BRANCHLT [строка кода, 4 байта], 0x0a [номер строки кода 4 байта]

Некоторые заметки:

  • Вышеприведенные математические операции складывают значения двух регистров вместе, помещая вывод в первый регистр.
  • CMP, оператор сравнения, должен сравнивать значения двух регистров и сохранять выходные данные в некотором внутреннем флаге (это может зависеть от реализации) для будущего использования в инструкциях ветвления.
  • Если BRANCH вызывается до CMP, если не вызывается BRANCHEQ, «VM» не должна переходить.
  • PUSH / POP неудивительно толкать или выталкивать числа из стека.
  • Операторы Jump и Branch переходят к определенной операции (строке кода), а не к двоичному адресу.
  • Операции ветвления не делают сравнения. Скорее, они берут вывод из последнего сравнения для выполнения.
  • Операторы Branch и Jump используют систему индексации номеров строк, начинающуюся с нуля. (Например, JMP 0 переходит на первую строку)
  • Все операции должны выполняться над числами без знака, которые переполняются до нуля и не генерируют исключение при переполнении целых чисел.
  • Деление на ноль не допускается и, как таковое, поведение программы не определяется. Вы можете (например) ...
    • Сбой программы.
    • Завершите выполнение виртуальной машины и верните ее текущее состояние.
    • Показать сообщение «ERR: Деление на 0».
  • Завершение программы определяется как то, когда указатель инструкции достигает конца программы (можно предположить, что программа не пустая).

Выход Выходные данные должны быть именно такими (включая новые строки)

R0 3126900366
R1 0
R2 10000    

Очки Очки рассчитываются по следующей формуле:Number Of Characters * (Seconds Needed To Run / 2)

Чтобы избежать различий в оборудовании, приводящих к разному времени, каждый тест будет выполняться на моем компьютере (i5-4210u, 8 ГБ ОЗУ) на сервере Ubuntu или в Windows 8, поэтому старайтесь не использовать какое-то безумно-экзотическое время выполнения, которое компилируется только на Dual G5 Mac Pro с ровно 762,66 МБ свободной оперативной памяти.

Если вы используете специализированную среду выполнения / язык, пожалуйста, оставьте ссылку на нее.

  • Для заинтересованных сторон я разместил тестовый код (написанный на C #) здесь: http://pastebin.com/WYCG5Uqu

Тестовая программа

Идея пришла отсюда , поэтому мы будем использовать несколько измененную версию их программы.

Правильный вывод для программы: 3 126 900 366

В С:

int s, i, j;
for (s = 0, i = 0; i < 10000; i++) {
    for (j = 0; j < 10000; j++)
        s += (i * j) / 3;
}

В коде: [R0 является представителем s, R1 из j, R2 из i]

LOAD R0 0
LOAD R2 0 <--outer loop value
LOAD R1 0 <--inner loop value
     --Begin inner loop--
PUSH R1 <--push inner loop value to the stack
MUL R1 R2 <--(i*j)
PUSH R2
LOAD R2 3
DIV R1 R2 <-- / 3
POP R2
ADD R0 R1 <-- s+=
POP R1
PUSH R2 
LOAD R2 1
ADD R1 R2 <--j++
POP R2
PUSH R2
LOAD R2 10000
CMP R1 R2 <-- j < 10000
POP R2
BRANCHLT 3 <--Go back to beginning inner loop
--Drop To outer loop--
LOAD R1 1
ADD R2 R1 <--i++
LOAD R1 10000
CMP R2 R1 <-- i < 10000
LOAD R1 0 <--Reset inner loop
BRANCHLT 2

В двоичном / шестнадцатеричном виде:

0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x02 0x00 0x00 0x00 0x00
0x00 0x01 0x00 0x00 0x00 0x00
0x02 0x01
0x06 0x01 0x02
0x02 0x02
0x00 0x02 0x00 0x00 0x00 0x03
0x07 0x01 0x02
0x03 0x02
0x04 0x00 0x01
0x03 0x01
0x02 0x02
0x00 0x02 0x00 0x00 0x00 0x01
0x04 0x01 0x02
0x03 0x02
0x02 0x02
0x00 0x02 0x00 0x00 0x27 0x10
0x09 0x01 0x02
0x03 0x02
0x0a 0x00 0x00 0x00 0x03
0x00 0x01 0x00 0x00 0x00 0x01
0x04 0x02 0x01
0x00 0x01 0x00 0x00 0x27 0x10
0x09 0x02 0x01
0x00 0x01 0x00 0x00 0x00 0x00
0x0a 0x00 0x00 0x00 0x02

Бонусные баллы (эффекты применяются мультипликативно) Например, если вы подходите для всех трех, это будет ((символы * 0.50) * 0.75) * 0.90

  • Уменьшение на 50%, если интерпретатор на самом деле является JIT-компилятором
  • Уменьшение на 25%, если применяется какой-либо цикл развертывания / значимой оптимизации.
  • Уменьшение на 10% при расширении виртуальной машины
    • BRANCHEQ [строка кода, 4 байта] (ветвь, если равно - код операции 0x0b)
    • BRANCHGT [строка кода, 4 байта] (ветвь, если больше - код операции 0x0c)
    • BRANCHNE [строка кода, 4 байта] (ветвь, если не равно - код операции 0x0d)
    • ЗАГРУЗИТЬ [регистр 1] [регистр 2] (переместить значение регистра 2 в регистр 1 - код операции 0x01).

Disallowed

  • Прекомпиляция тестового примера в программу запрещена. Вы должны либо принять байт-код из STDIN или из файла (неважно, какой).
  • Возврат вывода без запуска программы.
  • Любой другой способ обмануть требование виртуальной машины.
Красочно монохромный
источник
Почему бы не включить еще несколько тестовых программ, чтобы препятствовать тому, что вы сказали, запрещено? Если это виртуальная машина, она должна запускать все, что написано в спецификации байт-кода, верно?
Касран
Я постараюсь сделать это сегодня вечером. Я пишу компилятор прямо сейчас.
Красочно монохромный
Компилятор находится здесь jsfiddle.net/aL9y19bd/23
Красочно монохромный
1
CMPПроверяет ли меньше или равенство? И что происходит с его результатом?
es1024
1
MULи DIVтакже не указаны. Должны ли они быть подписаны или не подписаны? Что происходит при переполнении умножения?
февраля

Ответы:

8

C, 752 (589 + 163 для определения флагов) * 0,5 (JIT) * 0,9 (расширения) * (оптимизация 0,75) * (0,64 секунды / 2) = 81,216

C[S],J[S],i,j,k,y,c,X,Y,Z;char*R,a,b[9];main(x){R=mmap(0,S,6,34,-1,0);N=85;while(scanf("%c%s%*[ R]%d%*[ R]%d",&a,b,&x,&y)&&~getchar())a-=65,a-1?a-15?a-9?a?a-2?a-3?a-11?a-12?a-17?(N=41,v):(N=137,v):(N=137,u,N=247,g(H,4),N=139,u):(y?N=189+x,s(y):(N=51,g(G,G))):(N=137,u,N=247,g(H,6),N=139,u):(N=57,v,s(0xFC8A9F),--j):(N=1,v):(N=233,J[k++]=i,s(x)):b[1]-80?N=85+x:(N=93+x):(c=b[5],s(0x0F9EE78A),N=(c-69?c-71?c-76?1:8:11:0)+132,J[k++]=i,s(x)),C[++i]=j;U(E8,X)U(F0,Y)U(F8,Z)s(50013);i=j;while(k--)j=C[J[k]]+1,R[j-1]-233&&(j+=4),s(C[*(int*)(R+j)]-j-4);((int(*)())R)();printf("%u %u %u\n",X,Y,Z);}

Принимает код ( LOAD R0и т. Д.), Без завершающих символов, одного пробела, без пустых строк в середине, без комментариев и т. Д. Требуется завершающий перевод новой строки.

Затем он преобразуется в байт-код 80386 и выполняется.

Загрузка 0в регистр заменяется xorвводом самого регистра вместо movввода 0в регистр, который на три байта короче в сгенерированном байт-коде и может быть очень незначительно быстрее.

Компилировать с:

gcc -m32 -D"g(a,b)=(N=192|b<<3|a)"-D"s(b)=(*(int*)(R+j)=b,j+=4)"-DN=R[j++]-D"G=((x+1)|4)"
-D"H=((y+1)|4)"-DS=9999-D"u=g(0,G)"-D"v=g(G,H)"-D"U(b,c)=s(0xA3##b##89),--j,s(&c);"
bytecode.c -o bytecode

Требуется POSIX-совместимая ОС.

Входные данные читаются из STDIN (использовать ./bytecode < fileдля передачи из файла).

Результирующий байт-код для тестовой программы:

; start
 0:   55                      push   %ebp
; LOAD R0 0
 1:   33 ed                   xor    %ebp,%ebp
; LOAD R2 0
 3:   33 ff                   xor    %edi,%edi
; LOAD R1 0
 5:   33 f6                   xor    %esi,%esi
; PUSH $1
 7:   56                      push   %esi
; MUL R1 R2
 8:   89 f0                   mov    %esi,%eax
 a:   f7 e7                   mul    %edi
 c:   8b f0                   mov    %eax,%esi
; PUSH R2
 e:   57                      push   %edi
; LOAD R2 3
 f:   bf 03 00 00 00          mov    $0x3,%edi
; DIV R1 R2
14:   89 f0                   mov    %esi,%eax
16:   f7 f7                   div    %edi
18:   8b f0                   mov    %eax,%esi
; POP R2
1a:   5f                      pop    %edi
; ADD R0 R1
1b:   01 f5                   add    %esi,%ebp
; POP R1
1d:   5e                      pop    %esi
; PUSH R2
1e:   57                      push   %edi
; LOAD R2 1
1f:   bf 01 00 00 00          mov    $0x1,%edi
; ADD R1 R2
24:   01 fe                   add    %edi,%esi
; POP R2
26:   5f                      pop    %edi
; PUSH R2
27:   57                      push   %edi
; LOAD R2 10000
28:   bf 10 27 00 00          mov    $0x2710,%ed
; CMP R1 R2
2d:   39 fe                   cmp    %edi,%esi
2f:   9f                      lahf
30:   8a fc                   mov    %ah,%bh
; POP R2
32:   5f                      pop    %edi
; BRANCHLT 3
33:   8a e7                   mov    %bh,%ah
35:   9e                      sahf
36:   0f 8c cb ff ff ff       jl     0x7
; LOAD R1 1
3c:   be 01 00 00 00          mov    $0x1,%esi
; ADD R2 R1
41:   01 f7                   add    %esi,%edi
; LOAD R1 10000
43:   be 10 27 00 00          mov    $0x2710,%es
; CMP R2 R1
48:   39 f7                   cmp    %esi,%edi
4a:   9f                      lahf
4b:   8a fc                   mov    %ah,%bh
; LOAD R1 0
4d:   33 f6                   xor    %esi,%esi
; BRANCHLT 2
4f:   8a e7                   mov    %bh,%ah
51:   9e                      sahf
52:   0f 8c ad ff ff ff       jl     0x5
; copy R0 to X
58:   89 e8                   mov    %ebp,%eax
5a:   a3 28 5b 42 00          mov    %eax,0x425b
; copy R1 to Y
5f:   89 f0                   mov    %esi,%eax
61:   a3 38 55 44 00          mov    %eax,0x4455
; copy R2 to Z
66:   89 f8                   mov    %edi,%eax
68:   a3 40 55 44 00          mov    %eax,0x4455
; exit
6d:   5d                      pop    %ebp
6e:   c3                      ret

Ungolfed:

C[9999],J[9999],i,j,k,y,c,X,Y,Z;
char *R,a,b[9];
main(x){
    // 6 is PROC_WRITE|PROC_EXEC
    // 34 is MAP_ANON|MAP_PRIVATE
    R=mmap(0,'~~',6,34,-1,0);

    N=0x55;
    while(scanf("%c%s%*[ R]%d%*[ R]%d",&a,b,&x,&y)&&~getchar())
        a-=65,
        a-1? // B[RANCH**]
            a-15? // P[USH/OP]
                a-9? // J[MP]
                    a? // A[DD]
                        a-2? // C[MP]
                            a-3? // D[IV]
                                a-11? // L[OAD]
                                    a-12? // M[UL]
                                        a-17? // R[LOAD]
                                            // SUB
                                            (N=0x29,g(G,H))
                                        :(N=0x89,g(G,H))
                                    :(N=0x89,g(0,G),N=0xF7,g(H,4),N=0x8B,g(0,G))
                                :(y?N=0xBD+x,s(y):(N=0x33,g(G,G)))
                            :(N=0x89,g(0,G),N=0xF7,g(H,6),N=0x8B,g(0,G))
                        :(N=0x39,g(G,H),s(0xfc8a9f),--j)
                    :(N=0x1,g(G,H))
                :(N=0xE9,J[k++]=i,s(x))
            :b[1]-80? 
                N=0x55+x // PUSH
            :(N=0x5D+x) // POP
        :(c=b[5],s(0x0f9ee78a),N=(
        c-69? // EQ
            c-71? // GT
                c-76? // LT
                    1 // NE
                :8
            :11
        :0
        )+0x84,J[k++]=i,s(x)),
        C[++i]=j
        ;
    // transfer registers to X,Y,Z
    s(0xA3E889),--j,s(&X);
    s(0xA3F089),--j,s(&Y);
    s(0xA3F889),--j,s(&Z);

    // pop and ret
    s(0xC35D);

    i=j;
    // fix distances for jmp/branch**
    while(k--)
        j=C[J[k]]+1,R[j-1]-0xE9&&(j+=4),
        s(C[*(int*)(R+j)]-j-4);

    // call
    ((int(*)())R)();

    // output
    printf("%u %u %u\n",X,Y,Z);
}
es1024
источник
Вау. Хотелось бы добавить бонус за включение компилятора в виртуальную машину.
Красочно монохромный
В среднем 0,67 секунды на пробег за 15 пробежек.
Красочно монохромный,
Я должен не согласиться с тем, что ксоринг - это оптимизация. Несмотря на разумный размер кода, ксоринг не меняет характеристики производительности ВМ (поправьте меня, если я ошибаюсь). Под оптимизацией я подразумевал изменение или удаление инструкций из входного кода (например, удаление избыточного POP ... PUSH) или реализацию двух инструкций подряд, загружая регистр, чтобы можно было удалить одну из них, и т. Д.
Красочно монохромный
РЕДАКТИРОВАТЬ: На самом деле, это оптимизация: он упал до 0,64 секунды за цикл за 15 прогонов. Я предполагаю, что это предотвращает кэш-кэш или что-то, сокращая код (или удаляет избыточные обращения к памяти)?
Красочно монохромный
@ColorfullyMonochrome Некоторые архитектуры, когда они представлены с сохранением регистра для себя, на самом деле не выполняют инструкцию, а просто обнуляют сам регистр.
es1024
7

C, оценка = 854 байта × (~ 0,8 с / 2) × 0,5 [JIT] × 0,9 [расширения] = ~ 154 байта сек

#define G getchar()
#define L for(i=0;i<3;++i)
#define N*(int*)
#define M(x)"P\x8a\xe7\x9e\xf"#x"    KL"
*T[1<<20],**t=T,*F[1<<20],**f=F,R[3],r[]={1,6,7};char*I[]={"L\xb8    GGJH","I\x8b\xc0HHGH","H\x50GG","H\x58GG","I\3\xc0HHGH","I\53\xc0HHGH","M\x8b\xc0\xf7\xe0\x8b\xc0IHLGJ","O\63\xd2\x8b\xc0\xf7\xf0\x8b\xc0IJNGL","L\xe9    KH","L\73\xc0\x9f\x8a\xfcHHGH",M(\x82),M(\x84),M(\x87),M(\x85)},C[1<<24],*c=C;main(i,o,l,g){N c=0xb7ec8b60;c[4]=70;c+=5;while((o=G)>=0){char*s=I[o];l=*s-'G';memcpy(c,s+1,l);for(s+=l+1;o=*s++;){o-='G';if(o<3){g=r[G];c[*s++-'G']|=g<<3*(o&1);if(o>1)c[*s++-'G']|=g<<3;}else{if(o>3)*f++=c+*s-'G';for(i=4;i;--i)c[*s-'G'+i-1]=G;++s;}}*t++=c;c+=l;}*t=c;while(f>F)--f,**f=(int)T[**f]-(int)*f-4;L N&c[7*i]=0x5893e|r[i]<<19,N&c[3+7*i]=R+i;N&c[21]=0xc361e58b;mprotect((int)C>>12<<12,1<<24,7);((void(*)())C)();L printf("R%d %u\n",i,R[i]);}

Компилировать gcc vm.c -ovm -m32 -wна POSIX-совместимой ОС x86.
Запустите с ./vm < program, где programнаходится двоичный файл программы.


Идем на скорости. Программа выполняет довольно простой перевод входной программы в машинный код x86 и позволяет процессору делать все остальное.

Например, вот перевод тестовой программы. ecx, esiИ ediсоответствуют R0, R1и R2, соответственно; bhсодержит флаги состояния; eaxи edxскретч-регистры; стек вызовов соответствует стеку виртуальной машины:

# Prologue
     0:   60                      pusha
     1:   8b ec                   mov    ebp,esp
     3:   b7 46                   mov    bh,0x46
# LOAD R0 0
     5:   b9 00 00 00 00          mov    ecx,0x0
# LOAD R2 0 <--outer loop value
     a:   bf 00 00 00 00          mov    edi,0x0
# LOAD R1 0 <--inner loop value
     f:   be 00 00 00 00          mov    esi,0x0
#      --Begin inner loop--
# PUSH R1 <--push inner loop value to the stack
    14:   56                      push   esi
# MUL R1 R2 <--(i*j)
    15:   8b c6                   mov    eax,esi
    15:   f7 e7                   mul    edi
    19:   8b f0                   mov    esi,eax
# PUSH R2
    1b:   57                      push   edi
# LOAD R2 3
    1c:   bf 03 00 00 00          mov    edi,0x3
# DIV R1 R2 <-- / 3
    21:   33 d2                   xor    edx,edx
    23:   8b c6                   mov    eax,esi
    25:   f7 f7                   div    edi
    27:   8b f0                   mov    esi,eax
# POP R2
    29:   5f                      pop    edi
# ADD R0 R1 <-- s+=
    2a:   03 ce                   add    ecx,esi
# POP R1
    2c:   5e                      pop    esi
# PUSH R2
    2d:   57                      push   edi
# LOAD R2 1
    2e:   bf 01 00 00 00          mov    edi,0x1
# ADD R1 R2 <--j++
    33:   03 f7                   add    esi,edi
# POP R2
    35:   5f                      pop    edi
# PUSH R2
    36:   57                      push   edi
# LOAD R2 10000
    37:   bf 10 27 00 00          mov    edi,0x2710
# CMP R1 R2 <-- j < 10000
    3c:   3b f7                   cmp    esi,edi
    3e:   9f                      lahf
    3f:   8a fc                   mov    bh,ah
# POP R2
    41:   5f                      pop    edi
# BRANCHLT 4 <--Go back to beginning inner loop
    42:   8a e7                   mov    ah,bh
    44:   9e                      sahf
    45:   0f 82 c9 ff ff ff       jb     0x14
# --Drop To outer loop--
# LOAD R1 1
    4b:   be 01 00 00 00          mov    esi,0x1
# ADD R2 R1 <--i++
    50:   03 fe                   add    edi,esi
# LOAD R1 10000
    52:   be 10 27 00 00          mov    esi,0x2710
# CMP R2 R1 <-- i < 10000
    57:   3b fe                   cmp    edi,esi
    59:   9f                      lahf
    5a:   8a fc                   mov    bh,ah
# LOAD R1 0 <--Reset inner loop
    5c:   be 00 00 00 00          mov    esi,0x0
# BRANCHLT 3
    61:   8a e7                   mov    ah,bh
    63:   9e                      sahf
    64:   0f 82 a5 ff ff ff       jb     0xf
# Epilogue
    6a:   3e 89 0d 60 ac 04 09    mov    DWORD PTR ds:0x904ac60,ecx
    71:   3e 89 35 64 ac 04 09    mov    DWORD PTR ds:0x904ac64,esi
    78:   3e 89 3d 68 ac 04 09    mov    DWORD PTR ds:0x904ac68,edi
    7f:   8b e5                   mov    esp,ebp
    81:   61                      popa
    82:   c3                      ret

Ungolfed

флигель
источник
Ух ты ... мой JIT был ~ 900 строк кода (написано на C ++) ...
Красочно монохромный
В среднем 0,63 секунды на пробег за 15 запусков.
Красочно монохромный,
2

CJam, 222 187 185 байт * (слишком медленно / 2)

Я просто хотел посмотреть, насколько коротким я могу получить виртуальную машину с байт-кодом, написав ее на CJam. Меньше чем 200 байтов кажется довольно приличным. Хотя это чертовски медленно, потому что сам CJam интерпретируется. Чтобы запустить тестовую программу, нужны годы.

304402480 6b:P;q:iD-);{(_P=@/(\L*@@+\}h;]:P;TTT]:R;{_Rf=~}:Q;{4G#%R@0=@t:R;}:O;{TP=("R\(\GG*bt:R;  ~R= R\~@t:R; Q+O Q4G#+-O Q*O Q/O ~(:T; Rf=~-:U; GG*bU0<{(:T}*;"S/=~T):TP,<}g3,{'R\_S\R=N}/

Чтобы запустить его, загрузите интерпретатор Java по этой ссылке на sourceforge , сохраните код vm.cjamи запустите его с

java -jar cjam-0.6.2.jar vm.cjam

Программа ожидает байт-код на STDIN. Я еще не нашел способа передать двоичные данные в программу без добавления в PowerShell завершающего переноса строки и преобразования 0x0aв него 0x0d 0x0a, что действительно раздражает. Код включает 4 байта, чтобы исправить это (D-); ), который я не включил в общее количество, потому что это не то, что программа должна делать, если она фактически получала сам байт-код на STDIN, а не какую-то странно закодированную версию , Если кто-то знает решение для этого, пожалуйста, дайте мне знать.

Слегка разгульный

304402480 6b:P; "Create lookup table for instruction sizes. Store in P.";
q:i             "Read program and convert bytes to integers.";
D-);            "Remove spurious carriage returns. This shouldn't be necessary.";
{(_P=@/(\L*@@+\}h;]:P; "Split into instructions. Store in P.";
"We'll use T for the instruction pointer as it's initialised to 0.";
"Likewise, we'll use U for the CMP flag.";
TTT]:R; "Store [0 0 0] in R for the registers.";
{_Rf=~}:Q; "Register lookup block.";
{4G#%R@0=@t:R;}:O; "Save in register block.";
{TP=("R\(\GG*bt:R;

~R=
R\~@t:R;
Q+O
Q4G#+-O
Q*O
Q/O
~(:T;
Rf=~-:U;
GG*bU0<{(:T}*;"N/=~T):TP,<}g "Run program.";
3,{'R\_S\R=N}/

Завтра я добавлю правильное объяснение.

Короче говоря, я храню все регистры, указатель инструкций и флаг сравнения в переменных, чтобы я мог оставить стек CJam свободным для использования в качестве стека виртуальной машины.

Мартин Эндер
источник
Давайте продолжим эту дискуссию в чате .
Мартин Эндер
1
15,279 секунды в среднем за 20 итераций. - 15 тестов. Это означает 2,12208333 часа на тест.
Красочно монохромный
1

python / c ++, оценка = 56,66

1435 символов * .234 / 2 секунды * .5 [JIT] * .75 [Оптимизация] * .90 [Дополнительные инструкции]

Компилирует входную программу в c ++, запускает на ней gcc, затем запускает результат. Большую часть времени проводят внутри GCC.

Единственная оптимизация, которую я делаю, - сводить операции стека к явным переменным, если это разрешено семантически. Это очень помогает, примерно в 10 раз лучше время выполнения скомпилированного кода (около 0,056 сек для фактического запуска полученного двоичного файла). Я не уверен, что делает gcc, что дает вам такое улучшение, но это хорошо.

import sys,os
x=map(ord,sys.stdin.read())
w=lambda x:(x[0]<<24)+(x[1]<<16)+(x[2]<<8)+x[3]
I=[]
while x:
 if x[0]==0:f='r%d=%d'%(x[1],w(x[2:]));n=6
 if x[0]==1:f='r%d=r%d'%(x[1],x[2]);n=3
 if x[0]==2:f='P%d'%x[1];n=2
 if x[0]==3:f='O%d'%x[1];n=2
 if x[0]==4:f='r%d=r%d+r%d'%(x[1],x[1],x[2]);n=3
 if x[0]==5:f='r%d=r%d-r%d'%(x[1],x[1],x[2]);n=3
 if x[0]==6:f='r%d=r%d*r%d'%(x[1],x[1],x[2]);n=3
 if x[0]==7:f='r%d=r%d/r%d'%(x[1],x[1],x[2]);n=3
 if x[0]==8:f='goto L%d'%w(x[1:]);n=5
 if x[0]==9:f='a=r%d;b=r%d'%(x[1],x[2]);n=3
 if x[0]==10:f='if(a<b)goto L%d'%w(x[1:]);n=5
 if x[0]==11:f='if(a==b)goto L%d'%w(x[1:]);n=5
 if x[0]==12:f='if(a>b)goto L%d'%w(x[1:]);n=5
 if x[0]==13:f='if(a!=b)goto L%d'%w(x[1:]);n=5
 I+=[f];x=x[n:]
D=[]
d=0
for f in I:D+=[d];d+='P'==f[0];d-='O'==f[0]
J=[]
if all(d==D[int(f[f.find('L')+1:])]for f,d in zip(I,D)if f[0]in'gi'):
 H='uint32_t '+','.join('s%d'%i for i in range(max(D)))+';'
 for f,d in zip(I,D):
  if f[0]=='P':f='s%d=r'%d+f[1:]
  if f[0]=='O':f='r'+f[1:]+'=s%d'%(d-1)
  J+=[f]
else:
 H='std::vector<uint32_t>s;'
 for f,d in zip(I,D):
  if f[0]=='P':f='s.push_back(r'+f[1:]+')'
  if f[0]=='O':f='r'+f[1:]+'=s.back();s.pop_back()'
  J+=[f]
P='#include<vector>\n#include<cstdint>\nuint32_t r0,r1,r2,a,b;'+H+'int main(){'
for i,f in enumerate(J):P+='L%d:'%i+f+';'
P+=r'printf("R0 %u\nR1 %u\nR2 %u\n",r0,r1,r2);}'
c=open("t.cc", "w")
c.write(P)
c.close()
os.system("g++ -O1 t.cc")
os.system("./a.out")

Конечно, можно играть в гольф еще.

Кит Рэндалл
источник
Среднее число .477 секунд за пробег за 15 пробежек.
Красочно монохромный
1

Lua 5,2 (или LuaJIT), 740 байт

Первая попытка, только минимально игра в гольф. Эта версия работает (по крайней мере, в тестовой программе) и реализует дополнительные коды операций, но не поддерживает математические требования без знака и не особенно быстра. В качестве бонуса, тем не менее, это виртуальная машина, работающая на виртуальной машине, и написана так, что она может быть интерпретирована (запущена с PUC-Lua) или подобна JIT (запущена с LuaJIT; все еще интерпретируется, но интерпретатор теперь JITted).

РЕДАКТИРОВАТЬ: Гольф лучше, все еще большой.

РЕДАКТИРОВАТЬ: Исправлена ​​серьезная ошибка, и теперь ограничивает арифметику в unsigned longдиапазоне. Как-то удалось удержать размер от выхода из-под контроля, хотя, но он все равно дает неправильный ответ.

РЕДАКТИРОВАТЬ: Оказывается, результат был правильным, но результат не был. Перешел на печать с %uвместо, %dи все хорошо. Также отключены табличные регистры для переменных, чтобы несколько улучшить размер и скорость.

РЕДАКТИРОВАТЬ: Используя gotoзаявление Lua 5.2 (также доступно в LuaJIT), я заменил интерпретатор на «JIT-to-Lua», генерируя код, который запускается непосредственно самой Lua VM. Не уверен, действительно ли это считается JIT, но это улучшает скорость.

U,S,P,F=table.unpack,table.insert,table.remove,math.floor X,r0,r1,r2,p,m,s=2^32,0,0,0,1,0,{}C={{'r%u=%u',1,4},{'r%u=r%u',1,1},{'S(s,r%u)',1},{'r%u=P(s)',1},{'r%u=(r%u+r%u)%%X',1,0,1},{'r%u=(r%u-r%u)%%X',1,0,1},{'r%u=(r%u*r%u)%%X',1,0,1},{'r%u=F(r%u/r%u)%%X',1,0,1},{'goto L%u',4},{'m=r%u-r%u',1,1},{'if m<0 then goto L%u end',4},{'if m==0 then goto L%u end',4},{'if m>0 then goto L%u end',4},{'if m~=0 then goto L%u end',4}}t={io.open(arg[1],'rb'):read('*a'):byte(1,-1)}i,n,r=1,0,{}while i<=#t do c,i,x,a=C[t[i]+1],i+1,0,{}for j=2,#c do y=c[j]if y>0 then x=0 for k=1,y do i,x=i+1,x*256+t[i]end end S(a,x)end S(r,('::L%d::'):format(n))n=n+1 S(r,c[1]:format(U(a)))end load(table.concat(r,' '))()print(('R0 %u\nR1 %u\nR2 %u'):format(r0,r1,r2))

Вот оригинальная, читаемая версия.

U,S,P,F=table.unpack,table.insert,table.remove,math.floor

X,r0,r1,r2,p,m,s=2^32,0,0,0,1,0,{}

C={
    {'r%u=%u',1,4},
    {'r%u=r%u',1,1},
    {'S(s,r%u)',1},
    {'r%u=P(s)',1},
    {'r%u=(r%u+r%u)%%X',1,0,1},
    {'r%u=(r%u-r%u)%%X',1,0,1},
    {'r%u=(r%u*r%u)%%X',1,0,1},
    {'r%u=F(r%u/r%u)%%X',1,0,1},
    {'goto L%u',4},
    {'m=r%u-r%u',1,1},
    {'if m<0 then goto L%u end',4},
    {'if m==0 then goto L%u end',4},
    {'if m>0 then goto L%u end',4},
    {'if m~=0 then goto L%u end',4},
}

t={io.open(arg[1],'rb'):read('*a'):byte(1,-1)}
i,n,r=1,0,{}
while i<=#t do
    c,i,x,a=C[t[i]+1],i+1,0,{}
    for j=2,#c do
        y=c[j]
        if y>0 then
            x=0 
            for k=1,y do 
                i,x=i+1,x*256+t[i]
            end 
        end
        S(a,x)
    end
    S(r,('::L%d::'):format(n)) 
    n=n+1
    S(r,c[1]:format(U(a)))
end
load(table.concat(r,' '))()
print(('R0 %u\nR1 %u\nR2 %u'):format(r0,r1,r2))
Криптих стоит с Моникой
источник
Когда я запустил вашу программу, я получил следующую ошибку: pastebin.com/qQBD7Rs8 . Ожидаете ли вы байт-код поверх стандартного ввода или в виде файла?
Красочно монохромный
Сожалею. Мой бинарный файл для Windows был поврежден. Таким образом, все версии gcc / linux работали, но все тесты окон давали сбой. Тем не менее, он по-прежнему сообщает, что R0 и R1 равны 0, а R2 - 1.
Красочно монохромный
Я подозреваю, что это на самом деле не выполняется: для выполнения в среднем потребовалось 33,8 мс (GCC занимает ~ 0,25 секунды).
Красочно монохромный
Скрипт ожидает байт-код в виде файла с именем файла, передаваемым в командной строке. Вы правы, хотя, я проследил это, и похоже, что он делает только первый внешний цикл. Вернуться к чертежной доске ...
Криптих стоит с Моникой
Это то, что я получаю, думая на C и пишя на Lua: я использовал <в своих циклах вместо <=, поэтому окончательная инструкция перехода была исключена. Он все еще получает неправильный ответ, но теперь на это уходит несколько минут. :)
criptych выступает с Моникой
1

C #

1505 1475 байт

Это моя версия интерпретатора, написанная на C #, я думаю, что она может быть оптимизирована / улучшена, но я не знаю где;)

версия для гольфа:

using System;using System.Collections.Generic;using System.IO;using System.Linq;class M{static void Main(string[]a){if(a.Length==1&&File.Exists(a[0])){B.E(B.P(File.ReadAllLines(a[0])));Console.WriteLine(B.O);}}}class B{public enum I{L=0x00,P=0x02,Q=0x03,A=0x04,S=0x05,M=0x06,D=0x07,J=0x08,C=0x09,BL=0x0a,BE=0x0b,BG=0x0c,BN=0x0d}public enum R{A,B,C}enum C{N,L,E,G}public static Dictionary<R,uint>r=new Dictionary<R,uint>{{R.A,0},{R.B,0},{R.C,0}};public static Stack<uint>s=new Stack<uint>();static C c=C.N;public static string O{get{return string.Format("R0 {0}\nR1 {1}\nR2 {2}",r[R.A],r[R.B],r[R.C]);}}public static void E(byte[][]l){for(uint i=0;i<l.Length;i++){var q=l[i];switch((I)q[0]){case I.L:r[(R)q[1]]=U(q,2);break;case I.P:r[(R)q[1]]=s.Pop();break;case I.Q:s.Push(r[(R)q[1]]);r[(R)q[1]]=0;break;case I.A:s.Push(r[(R)q[1]]+r[(R)q[2]]);break;case I.S:s.Push(r[(R)q[1]]-r[(R)q[2]]);break;case I.M:s.Push(r[(R)q[1]]*r[(R)q[2]]);break;case I.D:s.Push(r[(R)q[1]]/r[(R)q[2]]);break;case I.J:i=U(q,1)-1;break;case I.C:{uint x=r[(R)q[1]],y=r[(R)q[2]];c=x<y?C.L:x>y?C.G:C.E;}break;case I.BL:if(c==C.L)i=U(q,1)-1;break;case I.BG:if(c==C.G)i=U(q,1)-1;break;case I.BE:if(c==C.E)i=U(q,1)-1;break;case I.BN:if(c!=C.E)i=U(q,1)-1;break;}}}public static byte[][]P(string[]c){return c.Where(l=>!l.StartsWith("#")).Select(r=>r.Split(' ').Where(b=>b.Length>0).Select(b=>Convert.ToByte(b,16)).ToArray()).Where(l=>l.Length>0).ToArray();}static uint U(byte[]b,int i){return(uint)(b[i]<<24|b[i+1]<<16|b[i+2]<<8|b[i+3]);}}

редактировать

убраны некоторые ненужные publicи privateмодификаторы:

using System;using System.Collections.Generic;using System.IO;using System.Linq;class M{static void Main(string[]a){if(a.Length==1&&File.Exists(a[0])){B.E(B.P(File.ReadAllLines(a[0])));Console.Write(B.O);}}}class B{enum I{L=0x00,P=0x02,Q=0x03,A=0x04,S=0x05,M=0x06,D=0x07,J=0x08,C=0x09,BL=0x0a,BE=0x0b,BG=0x0c,BN=0x0d}enum R{A,B,C}enum C{N,L,E,G}static Dictionary<R,uint>r=new Dictionary<R,uint>{{R.A,0},{R.B,0},{R.C,0}};static Stack<uint>s=new Stack<uint>();static C c=C.N;public static string O{get{return string.Format("R0 {0}\nR1 {1}\nR2 {2}\n",r[R.A],r[R.B],r[R.C]);}}public static void E(byte[][]l){for(uint i=0;i<l.Length;i++){var q=l[i];switch((I)q[0]){case I.L:r[(R)q[1]]=U(q,2);break;case I.P:r[(R)q[1]]=s.Pop();break;case I.Q:s.Push(r[(R)q[1]]);r[(R)q[1]]=0;break;case I.A:s.Push(r[(R)q[1]]+r[(R)q[2]]);break;case I.S:s.Push(r[(R)q[1]]-r[(R)q[2]]);break;case I.M:s.Push(r[(R)q[1]]*r[(R)q[2]]);break;case I.D:s.Push(r[(R)q[1]]/r[(R)q[2]]);break;case I.J:i=U(q,1)-1;break;case I.C:{uint x=r[(R)q[1]],y=r[(R)q[2]];c=x<y?C.L:x>y?C.G:C.E;}break;case I.BL:if(c==C.L)i=U(q,1)-1;break;case I.BG:if(c==C.G)i=U(q,1)-1;break;case I.BE:if(c==C.E)i=U(q,1)-1;break;case I.BN:if(c!=C.E)i=U(q,1)-1;break;}}}public static byte[][]P(string[]c){return c.Where(l=>!l.StartsWith("#")).Select(r=>r.Split(' ').Where(b=>b.Length>0).Select(b=>Convert.ToByte(b,16)).ToArray()).Where(l=>l.Length>0).ToArray();}static uint U(byte[]b,int i){return(uint)(b[i]<<24|b[i+1]<<16|b[i+2]<<8|b[i+3]);}}

назовите его executable.exe filenameгде filenameфайл, содержащий код для интерпретации

Моя "тестовая программа":

# LOAD R0 5
# CMP R0 R1
# BRANCHEQ 13
# LOAD R1 1
# LOAD R2 1
# CMP R0 R2
# MUL R1 R2
# LOAD R1 1
# ADD R2 R1
# PUSH R2
# PUSH R1 
# BRANCHEQ 13
# JMP 5
# POP R2
# POP R0
# POP R1
# PUSH R0

0x0 0x0 0x0 0x0 0x0 0x5
0x9 0x0 0x1 
0xb 0x0 0x0 0x0 0xd 
0x0 0x1 0x0 0x0 0x0 0x1 
0x0 0x2 0x0 0x0 0x0 0x1 
0x9 0x0 0x2 
0x6 0x1 0x2 
0x0 0x1 0x0 0x0 0x0 0x1 
0x4 0x2 0x1 
0x2 0x2 
0x2 0x1 
0xb 0x0 0x0 0x0 0xd 
0x8 0x0 0x0 0x0 0x5 
0x3 0x2 
0x3 0x0 
0x3 0x1 
0x2 0x0 

Интерпретатор развлекается с более длинными именами переменных, классов, ...

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        if (args.Length == 1 && File.Exists(args[0]))
        {
            var code = ByteCodeInterpreter.ParseCode(File.ReadAllLines(args[0]));
            ByteCodeInterpreter.Execute(code);
            Console.WriteLine(ByteCodeInterpreter.Output);
        }
    }
}

public static class ByteCodeInterpreter
{
    public enum Instruction : byte
    {
        LOAD = 0x00,
        PUSH = 0x02,
        POP = 0x03,
        ADD = 0x04,
        SUB = 0x05,
        MUL = 0x06,
        DIV = 0x07,
        JMP = 0x08,
        CMP = 0x09,
        BRANCHLT = 0x0a,
        BRANCHEQ = 0x0b,
        BRANCHGT = 0x0c,
        BRANCHNE = 0x0d
    }

    public enum Register : byte
    {
        R0 = 0x00,
        R1 = 0x01,
        R2 = 0x02
    }

    private enum CompareFlag : byte
    {
        NONE = 0x00,
        LT = 0x01,
        EQ = 0x02,
        GT = 0x03,
    }

    public static readonly Dictionary<Register, uint> register = new Dictionary<Register, uint>
    {
        {Register.R0, 0},
        {Register.R1, 0},
        {Register.R2, 0}
    };

    public static readonly Stack<uint> stack = new Stack<uint>();
    private static CompareFlag compareFlag = CompareFlag.NONE;

    public static string Output
    {
        get
        {
            return string.Format("R0 {0}\nR1 {1}\nR2 {2}", register[Register.R0], register[Register.R1],
                register[Register.R2]);
        }
    }

    public static void Execute(byte[][] lines)
    {
        for (uint i = 0; i < lines.Length; i++)
        {
            var line = lines[i];
            switch ((Instruction)line[0])
            {
                case Instruction.LOAD:
                    register[(Register)line[1]] = GetUint(line, 2);
                    break;
                case Instruction.PUSH:
                    register[(Register)line[1]] = stack.Pop();
                    break;
                case Instruction.POP:
                    stack.Push(register[(Register)line[1]]);
                    register[(Register)line[1]] = 0;
                    break;
                case Instruction.ADD:
                    stack.Push(register[(Register)line[1]] + register[(Register)line[2]]);
                    break;
                case Instruction.SUB:
                    stack.Push(register[(Register)line[1]] - register[(Register)line[2]]);
                    break;
                case Instruction.MUL:
                    stack.Push(register[(Register)line[1]] * register[(Register)line[2]]);
                    break;
                case Instruction.DIV:
                    stack.Push(register[(Register)line[1]] / register[(Register)line[2]]);
                    break;
                case Instruction.JMP:
                    i = GetUint(line, 1) - 1;
                    break;
                case Instruction.CMP:
                    {
                        uint v0 = register[(Register)line[1]], v1 = register[(Register)line[2]];
                        if (v0 < v1)
                            compareFlag = CompareFlag.LT;
                        else if (v0 > v1)
                            compareFlag = CompareFlag.GT;
                        else
                            compareFlag = CompareFlag.EQ;
                    }
                    break;
                case Instruction.BRANCHLT:
                    if (compareFlag == CompareFlag.LT)
                        i = GetUint(line, 1) - 1;
                    break;
                case Instruction.BRANCHGT:
                    if (compareFlag == CompareFlag.GT)
                        i = GetUint(line, 1) - 1;
                    break;
                case Instruction.BRANCHEQ:
                    if (compareFlag == CompareFlag.EQ)
                        i = GetUint(line, 1) - 1;
                    break;
                case Instruction.BRANCHNE:
                    if (compareFlag != CompareFlag.EQ)
                        i = GetUint(line, 1) - 1;
                    break;
            }
        }
    }

    public static byte[][] ParseCode(string[] code)
    {
        return
            code.Where(line => !line.StartsWith("#"))
                .Select(line => line.Split(' ').Where(b => b.Length > 0).Select(b => Convert.ToByte(b, 16)).ToArray())
                .Where(line => line.Length > 0)
                .ToArray();
    }

    private static uint GetUint(byte[] bytes, int index)
    {
        return (uint)(bytes[index] << 24 | bytes[index + 1] << 16 | bytes[index + 2] << 8 | bytes[index + 3]);
    }
}
Стефан
источник