Как выгрузить вычисление смещения памяти из среды выполнения в C/C++?
Я реализую простую виртуальную машину, и в настоящее время я использую арифметику времени выполнения для вычисления адресов отдельных программных объектов в виде смещений от базовых указателей.
Я задал пару вопросов по этому вопросу сегодня, но, похоже, я медленно иду в никуда.
Из первого вопроса я узнал несколько вещей - доступ к элементам и структурам и вычисление смещения адресов - я узнал, что современные процессоры имеют возможности виртуальной адресации, позволяющие вычислять смещения памяти без каких-либо дополнительных циклов, посвященных арифметике.
И из второго вопроса : разрешены ли смещения адресов во время компиляции в C / C++? - Я узнал, что нет никакой гарантии, что это произойдет при ручной коррекции.
К настоящему времени должно быть ясно, что я хочу добиться того, чтобы использовать возможности аппаратной адресации виртуальной памяти и выгружать их из среды выполнения.
Я использую GCC, как и для платформы - я разрабатываю для x86 в Windows, но, поскольку это виртуальная машина, я хотел бы, чтобы она эффективно работала на всех платформах, поддерживаемых GCC.
Так что любая информация по этому вопросу приветствуется и будет высоко оценена.
Заранее спасибо!
РЕДАКТИРОВАТЬ: Некоторый обзор генерации моего программного кода - на этапе проектирования программа строится в виде древовидной иерархии, которая затем рекурсивно сериализуется в один непрерывный блок памяти вместе с индексированием объектов и вычислением их смещения от начала блока памяти программы,
РЕДАКТИРОВАТЬ 2: Вот некоторый псевдокод виртуальной машины:
switch *instruction
case 1: call_fn1(*(instruction+1)); instruction += (1+sizeof(parameter1)); break;
case 2: call_fn2(*(instruction+1), *(instruction+1+sizeof(parameter1));
instruction += (1+sizeof(parameter1)+sizeof(parameter2); break;
case 3: instruction += *(instruction+1); break;
Случай 1 - это функция, которая принимает один параметр, который находится сразу после инструкции, поэтому он передается как смещение на 1 байт от инструкции. Указатель инструкции увеличивается на 1 + размер первого параметра, чтобы найти следующую инструкцию.
Случай 2 - это функция, которая принимает два параметра, как и раньше, первый параметр передается как смещение в 1 байт, второй параметр передается как смещение в 1 байт плюс размер первого параметра. Указатель инструкции затем увеличивается на размер инструкции плюс размеры обоих параметров.
Случай 3 является оператором goto, указатель команды увеличивается на смещение, которое следует непосредственно за инструкцией goto.
РЕДАКТИРОВАТЬ 3: Насколько я понимаю, ОС предоставит каждому процессу свое собственное выделенное пространство адресации виртуальной памяти. Если это так, значит ли это, что первый адрес всегда... ну, ну, так что смещение от первого байта блока памяти фактически является адресом этого элемента? Если адрес памяти выделен для каждого процесса, и я знаю смещение моего блока программной памяти И смещение каждого программного объекта от первого байта блока памяти, то разрешаются ли адреса объектов во время компиляции?
Проблема в том, что эти смещения недоступны во время компиляции кода C, они становятся известны во время фазы "компиляции" и перевода в байт-код. Означает ли это, что не существует способа для вычисления адреса памяти объекта для "свободного"?
Как это делается, например, в Java, где в машинный код компилируется только виртуальная машина, означает ли это, что вычисление адресов объектов требует снижения производительности из-за арифметики времени выполнения?
4 ответа
Вот попытка пролить свет на то, как связанные вопросы и ответы относятся к этой ситуации.
Ответ на первый вопрос смешивает две разные вещи: первая - это режимы адресации в инструкции X86, а вторая - отображение виртуальных адресов в физические. Первый - это то, что делается компиляторами, а второй - то, что (как правило) настраивается операционной системой. В вашем случае вы должны беспокоиться только о первом.
Инструкции в сборке X86 обладают большой гибкостью при обращении к адресу памяти. Инструкции, которые читают или записывают память, имеют адрес, рассчитанный по следующей формуле:
segment + base + index * size + offset
Сегментная часть адреса почти всегда используется по умолчанию DS
сегмент и обычно может быть проигнорировано. base
часть задается одним из регистров общего назначения или указателем стека. index
часть задается одним из регистров общего назначения и имеет размер 1, 2, 4 или 8. Наконец, смещение является постоянным значением, встроенным в инструкцию. Каждый из этих компонентов является необязательным, но, очевидно, должен быть указан хотя бы один.
Эта возможность адресации, как правило, подразумевается, когда речь идет о вычислительных адресах без явных арифметических инструкций. Существует специальная инструкция, которую упомянул один из комментаторов: LEA
который выполняет вычисление адреса, но вместо чтения или записи памяти сохраняет вычисленный адрес в регистре.
Для кода, который вы включили в вопрос, вполне вероятно, что компилятор будет использовать эти режимы адресации, чтобы избежать явных арифметических инструкций.
В качестве примера, текущее значение instruction
переменная может быть проведена в ESI
регистр. Кроме того, каждый из sizeof(parameter1)
а также sizeof(parameter2)
являются константами времени компиляции. В стандартных соглашениях о вызовах в X86 аргументы функций передаются в обратном порядке (поэтому первый аргумент находится в верхней части стека), поэтому коды сборки могут выглядеть примерно так:
case1:
PUSH [ESI+1]
CALL fn1
ADD ESP,4 ; drop arguments from stack
ADD ESI,5
JMP end_switch
case2:
PUSH [ESI+5]
PUSH [ESI+1]
CALL fn2
ADD ESP,8 ; drop arguments from stack
ADD ESI,9
JMP end_swtich
case3:
MOV ESI,[ESI+1]
JMP end_switch
end_switch:
это предполагает, что размер обоих параметров составляет 4 байта. Конечно, реальный код зависит от компилятора, и разумно ожидать, что компилятор будет выводить довольно эффективный код, если вы попросите некоторую оптимизацию уровня.
У вас есть элемент данных X
в ВМ, по относительному адресу A
и инструкция, которая говорит (например) push X
, это правильно? И вы хотите быть в состоянии выполнить эту инструкцию без необходимости добавлять A
на базовый адрес области данных виртуальной машины.
Я написал виртуальную машину, которая решает эту проблему путем сопоставления области данных виртуальной машины с фиксированным виртуальным адресом. Компилятор знает этот виртуальный адрес, и поэтому может настроить A
во время компиляции. Будет ли это решение работать на вас? Можете ли вы изменить компилятор самостоятельно?
Моя виртуальная машина работает на смарт-карте, и у меня есть полный контроль над операционной системой, так что эта среда сильно отличается от вашей. Но в Windows есть некоторые возможности для выделения памяти по фиксированному адресу - например, функция VirtualAlloc. Вы можете попробовать это. Если вы попробуете это, вы можете обнаружить, что Windows выделяет области, конфликтующие с вашей областью данных с фиксированным адресом, поэтому вам, вероятно, придется вручную загружать любые библиотеки DLL, которые вы используете, после того, как вы выделите область данных виртуальной машины.
Но, вероятно, будут преодолены непредвиденные проблемы, и это может не стоить того.
Чтобы ответить на мой собственный вопрос, основываясь на многочисленных ответах, которые я получил.
Оказывается, то, чего я хочу достичь, в моей ситуации не представляется возможным, так как бесплатные вычисления адресов памяти достижимы только тогда, когда удовлетворены конкретные требования и требуется компиляция для конкретных машинных инструкций.
Я разрабатываю визуальный элемент, среду программирования с перетаскиванием в стиле Lego для образовательных целей, которая использует простую виртуальную машину для выполнения программного кода. Я надеялся максимизировать производительность, но это просто невозможно в моем сценарии. Это не так уж важно, потому что программные элементы могут также генерировать эквивалентный код на С, который затем может быть скомпилирован условно для максимизации производительности.
Спасибо всем, кто откликнулся и прояснил вопрос, который мне не совсем понятен!
Игра с трансляцией виртуальных адресов, таблицами страниц или TLB - это то, что может быть сделано только на уровне ядра ОС, и она не переносима между платформами и семействами процессоров. Кроме того, аппаратная трансляция адресов на большинстве процессоров ISA обычно поддерживается только на уровне определенных размеров страниц.