@SergeySerge11

Как передаются регистры в трансляторах? Как происходит переход, от виртуальных регистров к машинным?

Вопрос не относится к разделу алгоритмов выделения регистров
Опишу проблему.
Допустим есть структура виртуальных регистров.
struct Env{
      int* regs;
 }


Как перестать поддерживать ее, при выполнении клиентского кода в ходе динамической трансляции.

Например клиентский код встречает такую инструкцию.
addi r1, 1
его можно транслировать к примеру так,
<br>
e->regs[1]+=1;  <br>
add     DWORD PTR [rdi+4], 1<br>
// а хотелось бы в <br>
add   ecx,  1 ; но тогда regs уже не знает чему равен ее 1 регистер. <br>

То есть вместо работы с реальными регистрами происходит работа с памятью.

Я не понимаю(хотя есть варианты ниже, как уйти от поддержки виртуальных регистров

То есть допустим я присвоил множеству регистров {r} ->{A} - набор регистров на уровень ниже . И теперь я работаю с регистрами {A}, регистры {r} в этот момент не обновляются. А при переключении режима транслятора, мне всегда нужно знать актуальные значения регистров. А их обновление(если всегда через виртуальный работать) уменьшает скорость работы где-то в 2 раза. (Чтения из верхнего кеша, чтение регистров)
Какие есть варианты?
К примеру есть вариант, загрузка и выгрузка используемых регистров при входе и выходе из метода. Может так и надо, на этапе компиляции, вычисляется набор используемых регистров в б блоках, и по своей конвенции вызова, принимается решение. По факту если метод не Inline-подобный, то это ни как не сказывается.

Еще вариант, похожий, Компиляция в " в супер большие функции" всего кода(или несколько). (Большие функции языка на котором транслятор работает). С передачей регистров как аргументов функции, и восстановлении значений при выходе из нее. И уже своей реализации логики вызовов методов. Заменить call return, на jmp-ы. Ведь процессор можно рассмотреть как одну большую функцию с jmp туда сюда.

Код в таких вариантах будет полностью аналогичен машинному(или коду уровнем ниже), но будет Штраф за передачу регистров. который близок к 0%(что тестил).

А как такая проблема решается в трансляторах, jit компиляторов, трассирующих компиляторов ... . Знаю там допустим jvm просто легко восстанавливает пару значений, так как нету регистров. А для регистровых трансляторов(Кстати такие есть? v8 вроде есть регистре).
Возможно поэтому большинство трансляторов стековые? Кроме того что проще.

Гуглится по теме вообще что-то типа https://en.wikipedia.org/wiki/Register_transfer_la... даже понять не могу, относится ли это к теме.
  • Вопрос задан
  • 115 просмотров
Пригласить эксперта
Ответы на вопрос 1
AshBlade
@AshBlade
Просто хочу быть счастливым
Как перестать поддерживать ее, при выполнении клиентского кода в ходе динамической трансляции.

Просто иметь оптимизации, которые знают, какой регистр какое название имеет.

Раз уж ты поставил тег .NET, то у платформы есть свой JIT компилятор, который пишется под каждую платформу (CPU) и имеет знание о ее регистрах.
Вот пример того, как это делается в исходном коде:
1. Каждая платформа регистрирует свои регистры и информацию о них.
#if defined(TARGET_XARCH)

#if defined(TARGET_X86)
/*
REGDEF(name, rnum,   mask, sname) */
REGDEF(EAX,     0,   0x01, "eax"   )
REGDEF(ECX,     1,   0x02, "ecx"   )
REGDEF(EDX,     2,   0x04, "edx"   )
REGDEF(EBX,     3,   0x08, "ebx"   )
REGDEF(ESP,     4,   0x10, "esp"   )
REGDEF(EBP,     5,   0x20, "ebp"   )
REGDEF(ESI,     6,   0x40, "esi"   )
REGDEF(EDI,     7,   0x80, "edi"   )
REGALIAS(RAX, EAX)
REGALIAS(RCX, ECX)
REGALIAS(RDX, EDX)
REGALIAS(RBX, EBX)
REGALIAS(RSP, ESP)
REGALIAS(RBP, EBP)
REGALIAS(RSI, ESI)
REGALIAS(RDI, EDI)

#else // !defined(TARGET_X86)

/*
REGDEF(name, rnum,   mask, sname) */
REGDEF(RAX,     0, 0x0001, "rax"   )
REGDEF(RCX,     1, 0x0002, "rcx"   )
REGDEF(RDX,     2, 0x0004, "rdx"   )
REGDEF(RBX,     3, 0x0008, "rbx"   )
REGDEF(RSP,     4, 0x0010, "rsp"   )
REGDEF(RBP,     5, 0x0020, "rbp"   )
REGDEF(RSI,     6, 0x0040, "rsi"   )
REGDEF(RDI,     7, 0x0080, "rdi"   )
REGDEF(R8,      8, 0x0100, "r8"    )
REGDEF(R9,      9, 0x0200, "r9"    )
REGDEF(R10,    10, 0x0400, "r10"   )
REGDEF(R11,    11, 0x0800, "r11"   )
REGDEF(R12,    12, 0x1000, "r12"   )
REGDEF(R13,    13, 0x2000, "r13"   )
REGDEF(R14,    14, 0x4000, "r14"   )
REGDEF(R15,    15, 0x8000, "r15"   )

REGALIAS(EAX, RAX)
REGALIAS(ECX, RCX)
REGALIAS(EDX, RDX)
REGALIAS(EBX, RBX)
REGALIAS(ESP, RSP)
REGALIAS(EBP, RBP)
REGALIAS(ESI, RSI)
REGALIAS(EDI, RDI)

#endif // !defined(TARGET_X86)

2. Для каждой платформы реализуются своя пара кодогенератор/эмиттер

// Кодогенератор
void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
{
    GenTree* op1 = treeNode->gtGetOp1();
    GenTree* op2 = treeNode->gtGetOp2();

    instruction ins = genGetInsForOper(treeNode->OperGet(), targetType);

    regNumber op1reg = op1->isUsedFromReg() ? op1->GetRegNum() : REG_NA;
    regNumber op2reg = op2->isUsedFromReg() ? op2->GetRegNum() : REG_NA;

    GenTree* dst;
    GenTree* src;

    // This is the case of reg1 = reg1 op reg2
    // We're ready to emit the instruction without any moves
    if (op1reg == targetReg)
    {
        dst = op1;
        src = op2;
    }
    // We have reg1 = reg2 op reg1
    // In order for this operation to be correct
    // we need that op is a commutative operation so
    // we can convert it into reg1 = reg1 op reg2 and emit
    // the same code as above
    else if (op2reg == targetReg)
    {
        dst = op2;
        src = op1;
    }
    // dest, op1 and op2 registers are different:
    // reg3 = reg1 op reg2
    // We can implement this by issuing a mov:
    // reg3 = reg1
    // reg3 = reg3 op reg2
    else
    {
        var_types op1Type = op1->TypeGet();
        inst_Mov(op1Type, targetReg, op1reg, /* canSkip */ false);
        regSet.verifyRegUsed(targetReg);
        gcInfo.gcMarkRegPtrVal(targetReg, op1Type);
        dst = treeNode;
        src = op2;
    }
    // try to use an inc or dec
    if (oper == GT_ADD && !varTypeIsFloating(treeNode) && src->isContainedIntOrIImmed() && !treeNode->gtOverflowEx())
    {
        if (src->IsIntegralConst(1))
        {
            emit->emitIns_R(INS_inc, emitTypeSize(treeNode), targetReg);
            genProduceReg(treeNode);
            return;
        }
        else if (src->IsIntegralConst(-1))
        {
            emit->emitIns_R(INS_dec, emitTypeSize(treeNode), targetReg);
            genProduceReg(treeNode);
            return;
        }
    }
    regNumber r = emit->emitInsBinary(ins, emitTypeSize(treeNode), dst, src);
}

// Эммитер
/*****************************************************************************
 *
 *  Add an instruction with two register operands.
 */

void emitter::emitIns_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insOpts instOptions)
{
    if (IsMovInstruction(ins))
    {
        assert(!"Please use emitIns_Mov() to correctly handle move elision");
        emitIns_Mov(ins, attr, reg1, reg2, /* canSkip */ false);
    }

    emitAttr size = EA_SIZE(attr);

    assert(size <= EA_64BYTE);
    noway_assert(emitVerifyEncodable(ins, size, reg1, reg2));

    /* Special case: "XCHG" uses a different format */
    insFormat fmt = (ins == INS_xchg) ? IF_RRW_RRW : emitInsModeFormat(ins, IF_RRD_RRD);

    instrDesc* id = emitNewInstrSmall(attr);
    id->idIns(ins);
    id->idInsFmt(fmt);
    id->idReg1(reg1);
    id->idReg2(reg2);

    if ((instOptions & INS_OPTS_EVEX_b_MASK) != INS_OPTS_NONE)
    {
        // if EVEX.b needs to be set in this path, then it should be embedded rounding.
        assert(UseEvexEncoding());
        id->idSetEvexbContext(instOptions);
    }

    UNATIVE_OFFSET sz = emitInsSizeRR(id);
    id->idCodeSize(sz);

    dispIns(id);
    emitCurIGsize += sz;
}


Дальше нам остается просто получить название регистра по его числу - это мы сделали на 1 шаге при их регистрации.

Здесь применяется условная компиляция. Но в рантайме (динамически) это тоже можно реализовать - просто кидаешь везде простые массивы и индексы.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы