Комментарии 97
Революционный Intel 80386
Возможно, с точки зрения своей архитектуры Intel 80386 и был революционным - в проектировании микросхем я не разбираюсь. Но с точки зрения системы команд он был абсолютно зауряден: универсальные регистры и плоская 32-битная адресация - то, что в других процессорных архитектурах появилось намного раньше, чем в x86, и с позиции программиста 80386 - не "революция", а погоня за ушедшими вперёд конкурентами.
погоня за ушедшими вперёд конкурентами
Пример можно таких конкурентов? С массовым 32-битным процессором в 1986?
Например, Motorola 68k (надеюсь, построенный на нём Apple Macintosh вы считаете массовым компьютером?). Первое поколение (1979 год) имело 32-битную адресацию и 32-битную систему команд, реализуемую 16-битным ALU. Второе поколение (1984 год) - 32-адресация и 32-битное ALU.
68К – 32-битный только в названии и ширине регистров. 16 бит АЛУ и шина данных, 24 битная шина адреса. Можно сколько угодно говорить про 32 бита, но 16Мб памяти максимум – это не про 32 бита.
Это «конкурент» для 286, а не 386.
Про второе поколение – 020 – так он был анонсирован в 1984, как и 386, но к производителям техники начал поступать только к осени 1985 в ограниченных количествах и со сниженной частотой. Эпл вообще не использовала 020 до марта 1987.
Во время 386 - 16 мегабайт было выше крыши, а нормально 2 или 4. Я никогда в жизни не видел 386 машину более чем с 16 мегабайтами.
Был процессор 386SX, который не любили не из-за узкой шины, а из-за отсутствия плавающей арифметики.
У первого Мака, вроде, было 128 килобайт памяти при 32-разрядном процессоре.
а из-за отсутствия плавающей арифметики
386DX также не имел плавающей арифметики на чипе. Разница по наличию/отсутствию сопроцессора на чипе между sx/dx – это про 486.
Был процессор 386SX, который не любили не из-за узкой шины
Зато его можно было воткнуть в материнку от 286. Тогда, правда, ещё почти не было материнок с "кроватками", процы были припаяны.
В любом случае, 386SX сыграл ту же роль, что и 8088 в своё время. В 1981 было мало и дорого 16-битной периферии, в 1985-ом 32-битной (речь больше про то, что в 90-ых стали называть чипсет, а сегодня вообще почти целиком на кристалле процессора. Всякие драйверы (контроллеры) динамического ОЗУ, контроллеры прерываний и т.д.)
Зато его можно было воткнуть в материнку от 286. Тогда, правда, ещё почти не было материнок с "кроватками", процы были припаяны.
PC, XT, AT все были с панельками. Временная мода на припаяный проц относилась как раз к 386 мамкам. Так что да, в 286 втыкали с переходником, вместо родного проца. 386 те что без сокета апгрейдили вторым этажом. На более мощные 386DX или гибриды 486 типа Cyrix и TI с кэшем.
В смысле на более высокочастотные 386sx или гибриды.
Именно потому я в своём исходном комментарии и написал:
с точки зрения системы команд
Я не аппаратчик, а программист. И мне совсем не важно, какая именно физическая шина у процессора - важна именно разрядность регистров. Никто же не причисляет 8088, имеющий 8-битную физическую шину, к 8-битным процессорам.
И разве в 30386 физическая шина адреса была 32 битной? Да и 32-битная физическая шина данных была только у старших моделей.
Но куда важнее даже не разрядность процессоров, а наличие универсальных регистров и плоской адресации. ИМХО, именно отказ от принципов, на которых построена система команд в линейке 8086-80286, и переход к тому, что уже было у конкурентов, позволил создать процессор, для которого можно было написать компиляторы языков высокого уровня, эффективно оптимизирующие машинный код.
Из моего личного опыта: один и тот же C-код, производящий много целочисленных вычислений, на ДВК-3м2 (КМ1801ВМ3, 10 MHz) работал существенно быстрее, чем тот же самый код на IBM-PC/AT (20286, 12 MHz).
не разрядность процессоров, а наличие универсальных регистров и плоской адресации
Это, вообще говоря, совсем не факт.
Более того, у меня есть занятное эмпирическое наблюдение, что безукоризненно «ортогональные» системы не очень-то живучи. Да, система команд PDP-11 прекрасна. Как эсперанто.
Но не очень-то популярна.
один и тот же C-код
Ну, тут надо и на код смотреть, и на компилятор – потому что наск помню 286 сложение регистр-регистр 2 такта, а 1801ВМ3 – 3 такта. В чудеса я не верю.
Да, система команд PDP-11 прекрасна. Как эсперанто.
DEC погубила не система команд, а менеджеры. Именно концепции PDP-11 и VAX-11 стали основой и для 68k, и для 80386. Но в Motorola это сделали на много лет раньше, а Intel понадобилось несколько поколений процессоров, чтобы осознать всю бесперспективность своих оригинальных грабель.
Ну, тут надо и на код смотреть, и на компилятор
На ДВК был компилятор, портированный в RT-11 из Unix. Он использовал r0 и r1 как базовые регистры для вычислений, регистры r2-r4 для оптимизации процесса вычислений: хранение промежуточных значений, указателей, переменных цикла; r5 использовался для границы стекового фрейма. А т.к. регистры универсальны, то r2-r4 использовались в любых процессорных командах сразу - без промежуточного копирования через другие регистры.
В MS-DOS использовался компилятор либо Borland, либо Microsoft: в разное время я использовал разные версии обеих фирм и какой точно это был компилятор, уже не вспомню.
Задача была не на сложение двух чисел, а на автоматизацию бухгалтерских задач - когда 1C ещё не было. И компилятор для PDP-11 выдавал куда более оптимизированный код. Дело не в скорости выполнения одной команды (я же специально указал, что тактовая частота процессора в ДВК была ниже), а в количестве этих команд. Компилятор для Intel генерировал ассемблерный код существенно большей длины - именно потому, что ему приходилось использовать узкоспециализированные регистры, привязанные к конкретным процессорным командам.
DEC погубила не система команд
А я не про DEC, а про ортогональную систему команд как концепцию. Не особо нужна она оказалась в реальности – и потому не выжила.
И компилятор для PDP-11 выдавал куда более оптимизированный код
Так я вам про это и написал – надо смотреть на компилятор и код.
Компилятор для Intel генерировал ассемблерный код существенно большей длины
…потому что компиляция под не-ортогональную систему, особенно оптимизация, куда сложнее чем для ортогональной. В то время эта сложность оптимизации была критической, к началу 90-х перестала быть. Тут то ортогональность и кончилась.
вот только если мы говорим про 8080 и его потомков, включая Z80, то там немножко наоборот. Куски сишного кода закатывали в систему команд, чтобы компилятор мог буквально подставить три инструкции на одну сишную команду. LOOP / DJNZ, косвенная адресация через регистр со смещением, ENTER/LEAVE, JECXZ и вот это вот всё
А 68К и PDP конечно так делать не пытались, вот только откуда BNE/BEQ в 68К и PDP-11?
Куски сишного кода закатывали в систему команд
А сейчас вообще чёрте-что творят, вот FJCVTZS например совсем не в Интел придумана – и ничего. /s
Вообще, «закатывание» конструктов из высокоуровневых языков в инструкции процессора началось с IBM 704, которую доделывали когда уже был написан драфт Фортрана.
Апофеоза это «закатывание» достигло в iAPX 432, и провал показал, что тут важно не переборщить.
а Intel понадобилось несколько поколений процессоров, чтобы осознать всю бесперспективность своих оригинальных грабель.
Они до сих пор этого не осознали и упорно, вот уже в течении 30-ти лет, выдают "баг" за "фичу".
Да, система команд PDP-11 прекрасна. Как эсперанто. Но не очень-то популярна.
Весьма популярна была в своё время и в оригинале, и в советских клонах. Умерла когда стало не хватать 16битного адресного пространства.
Когда стало не хватать 16 бит появился vax11, тоже ортогональный во все стороны – но 32-битный. Умерло оно не из-за разрядности, а из-за непрактичности. Избыточность ортогональности была хорошо осознана уже в середине 70-х.
VAX умер скорее из за сложности
Мы не про реализацию саму по себе, а про архитектуру системы команд, которая неизбежно ведёт к невостребованному переусложнению. Работу вы правильную привели, она как раз про это, канон практически.
Если сравнивать с Intеl, то его систему комнад с переменой длиной команд и мозговыносящими префиксами тоже нельзя назвать практичной.
Простите, но она выжила именно из-за практичности. Обратная совместимость – это о практичности как раз. Сейчас это не так актуально, но очень долго было важнейшим фактором.
Уродливо это всё до невозможности – но практичность не обязательно про красоту.
Да и сейчас хватает бинарного кода без возможности перекомпилировать, особенно на потребительском железе. На серверах много open source, но тоже встречается проприетарный софт типа SAP.
Про обратную соместимость с x86 в то время понятно, но что им мешало более адекватно сделать расширение в IA-32? Тут уже успех их архитектуры в дальнейшем - за счет сложившегося доминирование ПК на их процах и наработанным софтом.
Перепроектировать такие вещи это таки объёмная работа.
Вполне возможно, тупо не хватило ресурсов, а рынок гнал что-то сделать.
что им мешало более адекватно сделать расширение в IA-32
Я как-то не задумывался, как бы я расширял. У меня даже мнения нет, ну кроме очевидных хотелок типа «больше регистров», и очевидных оптимизаций за счёт послезнания типа «2 кольца вместо 4х».
А у вас, полагаю, есть соображения. Как бы вы расширили?
Про обратную соместимость с x86 в то время понятно, но что им мешало более адекватно сделать расширение в IA-32?
А я не понял вопрос. IA-32 это и есть 32-битный + 16-битный x86, что значит "расширение в IA-32"? Вы спрашиваете про изменения при переходах 16->32 или 32->64?
Вы спрашиваете про изменения при переходах 16->32 или 32->64?
Именно.
80386 - наиболее вероятно, не хватило ресурсов. Это таки была разработка очень сложного вида, тогда это было где-то первым в мире. Ну и у разработчиков не было достаточного осознания, насколько наперёд их решения будут влиять, и как сложно их будет корректировать.
Ну а AMD строил свою amd64 реально в состоянии полной нищеты. То, что оно выстрелило - чудо, но проблемы создания перешли в легаси.
Подозреваю, что о полуавтоматическом портировании ассемблерного кода с 8086 тоже думали. Ну и поддерживать его выполнение в 16битных режимах было бы сложнее при более заметных отличиях 32битной ISA. С 32->64 примерно то же самое.
Подозреваю, что о полуавтоматическом портировании ассемблерного кода с 8086 тоже думали.
Это ничем не ограничивает смену кодировки. Уже в 32 битах, например, команды IN, INS, OUT, OUTS, все прочие строковые (MOVS, SCAS...), HLT, CMC, PUSHA, POPA, BOUND, CBW, CWD, ENTER, LEAVE, INTO, CLI, STI, CLD, STD можно было сделать двух-, а некоторые даже трёхбайтовыми, хуже бы от этого не было. XLAT вообще можно было убрать уже тогда. Всё это освободило бы огромную группу кодов верхнего уровня. И это список не полный. В освободившиеся можно было бы вложить не только всякие SIMD, как началось в 90-х, но, например, варианты на 16 регистров, когда припекло бы.
И тут не надо было бы даже удваивать декодер - и так есть различия в поведении одинаковых кодов, а небольшое количество исключений его бы не раздуло.
С 32->64 примерно то же самое.
Именно - второй повод сделать такую же замену был точно так же протерян.
Да, система команд PDP-11 прекрасна. Как эсперанто.
Про прекрасность - ой спорно.
Пространства кодов для универсальных двухадресных команд не хватило. Даже байтовые варианты ADD и SUB не влезли. XOR, MUL, DIV, ASH(C) уже получили ограничения: часть аргументов только в регистрах. Двухадресными, по сути, остался тот минимум, который нужен для "управляющей" роли процессора.
5-й режим адресации (косвенный преавтодекрементный) нафиг никому не сдался, был пригоден только для гэгов. 3-й режим (косвенный поставтоинкрементный) в 99% кода использовался только с PC. В VAX переделали набор режимов, 5-й убили, остальные частично ограничили.
Установка флагов командами MOV, CLR, COM - диверсия, которую практически все позднейшие архитектуры, имеющие флаги условий, устранили. X86, ARM, SPARC, M68k, POWER - нигде такого нет.
Одноадресные ADC, SBC давали возможность строить арифметику двойной длины, но не многократной. Для многократной приходилось костылить с ручным сбором флагов переноса. Если бы сделали двухадресные аналоги на регистрах, было бы сильно удобнее.
Нет логических (беззнаковых) сдвигов, изволь маскировать сам.
Ляпов системного уровня ещё больше, но это уже не вопрос напрямую системы команд.
В целом, конечно, оказался потрясающий положительный пример:
8-битный (вместо 6-битного прежних моделей) компьютер в мини-сегменте;
NZVC стали стандартом;
ввод-вывод через адресное пространство - тоже стало стандартом.
Остальное как-то менее важно, но тоже неплохо. Описанные выше недостатки - вполне можно списать на проблемы первопроходца, плюс сложный старт проекта (как и с S/360, он был слишком революционным, и выигрыш был понят далеко не сразу).
PS: Эсперанто тоже не надо переоценивать. Польско-итальянский суржик, в котором хороша только нормализация с минимизацией исключений. Но увы, как и в IT - выстреливает (хоть как-то) первое минимально подходящее.
Эсперанто тоже не надо переоценивать
Так я его написал не как положительный пример. Про прекрасность там с изрядной долей сарказма был )
Коммент замечательный, спасибо!
Про прекрасность - ой спорно.
Вот странно. У вас "ой спорно", а у толпы олдов-фанатов пдп-11 не спорно...
Хотя со многим я согласен, достаточно было бы 1.5-адресных команд, как в 68k сделали и огромный косяк с ADC/SBC, который аж не поправили, а повторили в ваксе.
Установка флагов командами MOV, CLR, COM - диверсия, которую практически все позднейшие архитектуры, имеющие флаги условий, устранили. X86, ARM, SPARC, M68k, POWER - нигде такого нет.
Не знаю почему это "диверсия", и откуда вы взяли что в 68к этого нет (как раз очень есть). И присмотритесь к арму или power, может и там оно окажется (опциональным для каждого опкода).
Вот странно. У вас "ой спорно", а у толпы олдов-фанатов пдп-11 не спорно...
Я тоже вполне олд уже и познакомился с клонами PDP-11 ещё в школе. Тогда - был практически фанатом. Позже - остыл, когда увидел множество вариантов для сравнения и проанализировал причины и последствия всех решений.
"Полутораадресные", если имеется в виду, что не больше одного операнда в памяти, это и в x86, кроме извращений вроде MOVS. Или в S/360 в основной группе, где приёмник вообще только регистр. 68k тут не уникален.
и огромный косяк с ADC/SBC, который аж не поправили, а повторили в ваксе.
Угу:(
Не знаю почему это "диверсия", и откуда вы взяли что в 68к этого нет (как раз очень есть).
Да, с m68k слегка промахнулся по памяти. Но заметьте MOVE ставит флаги, а MOVEA - уже нет. "Неаккуратненько" (ц).
А диверсия - потому что копирование достаточно часто оказывается универсальным действием на обе ветки развилки, или в цикле какого-то сложного действия, а если оно само портит флаги, то это сильно усложняет кодирование.
присмотритесь к арму или power, может и там оно окажется (опциональным для каждого опкода).
Нет. В ARM (обеих разрядностей) LDR, STR флаги не ставят никогда. Уже загруженное значение можно проверить через TST. Большинство других операций также имеют варианты с установкой флаги и без, причём в ARM/64 многие оставлены только в варианте без флагов (кому нужно - проверит выходной регистр). Разумеется, можно намеренно совместить с копированием - когда это вообще возможно - как например "ands x1, x2, x2". Но зачем?
В POWER то же самое, load-store ничего с флагами не делают, и для большинства целевых команд есть варианты с изменением флагов и без.
Кстати, что вы думаете про отказ от флагов вообще, как в MIPS или RISC V? Я из проблем навскидку вижу только усложнение длинной арифметики, но в принципе тоже решаемо на микроархитектурном уровне.
Кстати, что вы думаете про отказ от флагов вообще, как в MIPS или RISC V?
Как обычно, надо смотреть на компиляторы, поскольку 99.99+% кода генерится ими. Я как-то задумался над этим и посмотрел, как выглядит сгенерированный код для ARM/64. Подавляющее большинство случаев (опять же, навскидку, 99% и больше) условных действий по флагам идут сразу после сравнений, следующей командой. "S"-варианты команд (которые ставят флаги) редки и используются почти полностью для немедленного последующего условного перехода; остальные тотально без модификации флагов.
Поэтому действия в стиле RISC-V (как минимум), где или b${cond} r1, r2
, или slt[i][u] r1, r2
- лучше туда ложатся. Не все действия у него (и у MIPS) ложатся на одну команду, некоторые - на две (например, bool f=x==y;
ложится в sub или xor с последующим sltiu), но и с флагами получаются минимум две (сравнение и затем или переход, или запись в регистр).
Так что для современной компиляторной мысли - основного набора действий - безфлаговый вариант явно роднее. Но это не обязательно хорошо, см. ниже.
А ещё интересен пример SystemZ - там изначально флаговая система, но в развитии добавили пачку безфлаговых команд со сравнением регистров и немедленным переходом по условию. Значит, полезно. (Хотя основная масса остаётся по флагам.)
Я из проблем навскидку вижу только усложнение длинной арифметики, но в принципе тоже решаемо на микроархитектурном уровне.
Проблем таки больше. Если вы пишете a+b (оба со знаком) и согласны с надеждой компилятора, что переполнения не будет (на чём он оптимизирует код, иногда слишком неожиданно), то вам обычное add без флагов - оптимум (для MIPS это addu). А вот если хочется детектировать переполнение - то уже усложнение. В доке RISC-V описано, грубо говоря, детект как (wrapping_add(a,b) < a) == (b >= 0). Это 3 лишних команды и 2 временных регистра.
Так что если вы пишете "обычный" безалаберный код - то ему будет легче на машине без флагов, по описанному выше. А вот если параноить на каждую операцию - может оказаться, что лучше с флагами.
Длинная арифметика считается на таких платформах сейчас дорогим образом - где-то так:
unsigned carry = 0;
for (i = 0; i < N; ++i) {
// limb_t беззнаковый, знак хранится отдельно, суммирование с усечением
limb_t tmp1 = a[i] + b[i];
unsigned carry1 = tmp1 < a[i];
limb_t lsum = tmp1 + carry;
unsigned carry2 = lsum < tmp1;
out[i] = lsum;
carry = carry1 | carry2;
}
Просто, прямолинейно... но дорого. 6 операций вместо одной. Пример можно посмотреть тут.
Вот тут как раз было бы полезно иметь расширение (которое будет присутствовать, грубо говоря, для лаптопов и выше) с adc, sbc, mul, div (без исключения, но с установкой флагов при проблемах), условными переходами, установкой регистра по условию; может, чем-то ещё. Ну и самим регистром флагов, просто ещё одним служебным.
Но в RISC-V сейчас, судя по спеке, есть туева хуча всего, но не такое. Видимо, на этих процессорах ещё не планировалась длинная арифметика (хм, криптография должна была успеть...)
Суммируя: я за параллельное существование обоих вариантов с предпочтением безфлаговому и ориентацией флагового варианта на особые нужды.
Подавляющее большинство случаев (опять же, навскидку, 99% и больше) условных действий по флагам идут сразу после сравнений, следующей командой.
Собственно, поэтому на современных процах с флагами compare+branch обычно фьюзится в одну микроинструкцию, эквивалентную бранчу от MIPS/RISC V. Так что отказ от флагов позволяет выкинуть соответствующую логику.
Ну и самим регистром флагов, просто ещё одним служебным.
Это уже не так просто, нужна логика для тракинга зависимостей между инструкциями по отдельным битам - ну и операционка должна детектить такое расширение и сохранять/восстанавливать дополнительный регистр, как с векторами. Проще в этих adc/sbc задействовать дополнительный обычный регистр для флага переноса.
PS Поигрался на godbolt - простое сравнение (a + b) < a gcc для x86 компилирует в проверку флага, а вот описанная формула для знаковых чисел как то вообще уходит. Похоже, из за того, что unsigned overflow - UB, но как тогда этот код на переносимом C/C++ написать - разве что работать с числами как с беззнаковыми, явно проверяя старший бит. Что то такое получается, но как то кривовато.
Собственно, поэтому на современных процах с флагами compare+branch обычно фьюзится в одну микроинструкцию, эквивалентную бранчу от MIPS/RISC V.
На моделях уровня от лаптопа или толстого embedded - да, наверняка хотя бы частично сделано.
Это уже не так просто, нужна логика для тракинга зависимостей между инструкциями по отдельным битам
Если таких команд очень мало от общего числа, то логику можно упростить до трекинга полного комплекта флагов. Особенно если не делать, как в старых архитектурах, вредных правил типа "эта команда вон тот флаг не меняет".
ну и операционка должна детектить такое расширение и сохранять/восстанавливать дополнительный регистр, как с векторами
Пишется в полдня с перекурами.
Проще в этих adc/sbc задействовать дополнительный обычный регистр для флага переноса.
Не всегда. Расширить формат команды может быть сложнее и вреднее на будущее, чем иметь служебный регистр. В RISC-V видно по развитию и по заявлениям авторов, как они руками и ногами отбиваются от подобных вариантов, стараясь ужиматься в традиционные трёхадресные форматы. Для fused multiply-add они согласились на 3 входных значения, а для conditional select уже нет - мол, используйте последовательность czero.nez + czero.eqz + or.
Похоже, из за того, что unsigned overflow - UB
Да.
но как тогда этот код на переносимом C/C++ написать
Начиная с C23 есть такое. В C++23 не вошло, но скорее всего будет в 26-м.
До этого переносимого везде варианта не было. Для GCC и Clang были overflow builtins (собственно, возможности C23 это они же), соответственно в Unix мире этим давно пользовались. Хотя напрямую неудобно, лучше через библиотеки. Сложение с ним на godbolt превращается в add + jo, это как раз максимум ужатия.
(Обратите внимание, что у обоих комплектов типы аргументов могут быть все разные. Это полезно для, например, операции сужения диапазона с проверкой. Это неочевидно, я не замечал, пока меня ныне забаненный khim@ не ткнул носом. (Без него тут скучнее.))
Ну и можно было для них же отдельные куски кода компилировать с -fwrapv
для реализации такой защиты. Дороже и криво, но работает.
На моделях уровня от лаптопа или толстого embedded - да, наверняка хотя бы частично сделано.
Да, я скорее про серверные думаю )
Если таких команд очень мало от общего числа, то логику можно упростить до трекинга полного комплекта флагов.
Но мы же про оптимизацию для кода типа арифметики произвольной длины, если там в горячем цикле как то отдельно используются перенос и переполнение, лучше рассматривать отдельно.
Расширить формат команды может быть сложнее и вреднее на будущее, чем иметь служебный регистр.
А если в кодировке оставить один dst, но писать при этом ещё и в регистр dst +1 (и возможно в следующие) ?
А если в кодировке оставить один dst, но писать при этом ещё и в регистр dst +1 (и возможно в следующие) ?
Эээ... ну можно, да. (И на уровне ассемблера форсировать его явное указание, чтобы сократить ошибки.)
Но тут есть две другие проблемы. Одна сохраняется - такая команда будет требовать большей ширины шины между АЛУ и блоком регистров, или же больше тактов, чтобы по существующей шине писать ещё один регистр. Другая - что в алгоритм распределения регистров в компиляторе придётся добавлять костыль для пары регистров. Они такое сильно не любят. Есть архитектуры, где пары регистров традиционно есть (SystemZ, PDP-11, VAX), было в SIMD для ARM/32, где сделали сложную иерархию (D0=S0+S1, D1=S2+S3, Q0=D0+D1, и так далее), но для ARM/64 это повторять не стали.
Алгоритмы распределения регистров это и так сплошной закат солнца вручную, задача NP-полная, все существующие реализации что-то крутят частично. Совсем равных регистров нет, даже если сама ISA считает их одинаковыми, то конвенция вызова это ломает (есть callee-saved, есть аргументы и результаты, есть scratch - минимум 3 группы). Стопка диссеров, защищённых на локальных оптимизациях этой задачи, и статей помельче, наверно, уже скоро дорастёт до Эйфелевой башни. Люди, которые этим занимаются, ой как не оценят идею добавлять им головной боли. Да и зачем? Если есть один чётко определённый ни под что больше не занятый регистр с не сильно плотным использованием, пытаться это вынести в общие - как-то мало смысла...
Пространства кодов для универсальных двухадресных команд не хватило.
В x86 с двухадресными в памяти вообще швах, кроме узкоспециализированных комнад.
Установка флагов командами MOV, CLR, COM - диверсия, которую практически все позднейшие архитектуры, имеющие флаги условий, устранили. X86, ARM, SPARC, M68k, POWER - нигде такого нет.
Надо просто уметь использовать особенности архитектуры. В данном случае можно можно при необходимости экономить на команде TST
Одноадресные ADC, SBC давали возможность строить арифметику двойной длины, но не многократной. Для многократной приходилось костылить с ручным сбором флагов переноса. Если бы сделали двухадресные аналоги на регистрах, было бы сильно удобнее.
А зачем флаг переноса сбрасывать?
Нет логических (беззнаковых) сдвигов, изволь маскировать сам.
Нет только логического сдвига вправо.
В x86 с двухадресными в памяти вообще швах,
И это оказалось неплохо, потому что сильно упростило построение микроопераций при out-of-order. Уже с наворотами PDP-11 и VAX это сложнее, а с тем, что навернули в m68k - вчетверне. Лучше бы ещё меньше (см. SystemZ, где в основном блоке операций есть источники в памяти, но не приёмники), но и так уже было легче.
Надо просто уметь использовать особенности архитектуры. В данном случае можно можно при необходимости экономить на команде TST
Этого я не понял. Как на ней можно экономить и что это даст, например, для флага carry?
А зачем флаг переноса сбрасывать?
Кто его сбрасывал, вы о чём?
Нет только логического сдвига вправо.
И это уже само по себе существенно, потому что если он нужен, маску вычислять достаточно гиморно. Насколько я знаю, легче получить знаковый сдвиг из беззнакового, чем наоборот. Ну а при пошаговом сдвиге - особенно.
Этого я не понял. Как на ней можно экономить и что это даст, например, для флага carry?
Про Carry - при MOV он не менялся. А вот установку флага Z в MOV можно использовать для выхода из цикла копирования строк.
Кто его сбрасывал, вы о чём?
Я не полностью процитировал:
Одноадресные ADC, SBC давали возможность строить арифметику двойной длины, но не многократной. Для многократной приходилось костылить с ручным сбором флагов переноса.
И это уже само по себе существенно, потому что если он нужен, маску вычислять достаточно гиморно. Насколько я знаю, легче получить знаковый сдвиг из беззнакового, чем наоборот. Ну а при пошаговом сдвиге - особенно.
Дле первого сдвига - сброс бита переноса и циклический сдвиг (через бит переноса) вправо. Продолжение просто арифметическими сдвигами.
А вот установку флага Z в MOV можно использовать для выхода из цикла копирования строк
Если нулевой байт - да. Но сами по себе NUL-terminated строки это специфический подход.
В любом случае, заметьте, все архитектуры, зачатые уже начиная с середины 70-х и не находящихся под явным влиянием PDP-11 (как VAX или M68k), MOV
не меняет никакие флаги. Ну и дальше простановка флагов сокращается (в ARM её уже можно отменять для большинства команд). Не думаю, что столько умных людей сделали ошибку:)
Я не полностью процитировал:
Ну вот я про ручной сбор флагов переноса писал здесь, код на C и ссылка на код для MIPS. Что мешало сделать двухаргументную ADC, пусть даже только на регистрах?
Дле первого свдига - сброс бита переноса и циклический сдвиг (через бит переноса) вправо. Продолжение просто арифметическими сдвигами.
Именно, костыли. Или вот такое делаете, или после более быстрого ASH[C] маскируете.
Ну вот я про ручной сбор флагов переноса писал здесь, код на C и ссылка на код для MIPS. Что мешало сделать двухаргументную ADC, пусть даже только на регистрах?
//сложение 48бит
add a0, b0
adc b1
adc b2
add a1, b1
adc b2
add a2, b2
Не коротко, но однооборазно - с ростом разрядностей просто добавляются ADC в арифметической прогрессии.
Не коротко, но однооборазно - с ростом разрядностей просто добавляются ADC в арифметической прогрессии.
Адище O(N^2), ещё и с порчей одного аргумента. Вот уж точно при числах длиннее от ≈6 слов применять ту схему, что по моей ссылке.
Но за показ трюка спасибо, сложу в кунсткамеру...
Адище O(N^2), ещё и с порчей одного аргумента.
Всмысле порча аргумента? Стандартное двуоперандное сложение (или другая операция), где результат в одном из входных операндов. Также и в x86.
Если не хочется портить исходный операнд в памяти, то он копируется в регистры (второй можно, если не хватает регистров, брать из памяти).
Всмысле порча аргумента?
Вы ж сами написали - adc b1
, adc b2
...
Если не хочется портить исходный операнд в памяти, то он копируется в регистры (второй можно, если не хватает регистров, брать из памяти).
Вот именно, никаких регистров не хватит.
И O(N^2) для сложения - это таки диверсия.
Вы ж сами написали -
adc b1
,adc b2
Можно было и догадаться, что раз речь шла о наращивании разрядности чисел, то "adc b1
, adc b2" - это каскадное добавлнение бита переноса.
[b2:47..32][b1:31..16][b0:15..0]
Вот именно, никаких регистров не хватит.
В любых процессорах\микроконтроллерах при наращивании разрядности будет в итоге абсолютно также (не учитывая всякие арифметические раширители вроде сопроцессоров, с отдельными наборами регистров).
O(N^2) для сложения - это таки диверсия
Если не используется кучу вычислений с очень большими числами - никакой диверсии нет. А на практике для большинства целочисленной арифметики в реальных программах за глаза хватает и 32-бит.
Можно было и догадаться
Я именно так и догадался.
В любых процессорах\микроконтроллерах при наращивании разрядности будет в итоге абсолютно также
Нет, им не требуется модификация аргумента.
А на практике для большинства целочисленной арифметики в реальных программах за глаза хватает и 32-бит.
Стиль x86 дал возможность делать и короткие вычисления на двойных словах, и длинные без ограничения и извращений, одним и тем же средством, за линейное время.
именно отказ от принципов, на которых построена система команд в линейке 8086-80286
Вот только не надо их смешивать. Защищенный режим и виртуализации появилась именно в 286, его проблема в том что он был не плоский, с сегментный
В 386 расширили регистры до 32 бит, оставив старые команды для совместимости. Отсюда и громоздкость, но убери её интел этот проц никто бы не купил. Его первые года и так не покупали, 286 работал быстрее на той же частоте, был дешевле, а 32х битного софта долго не было.
У ДВК более совершенные , с точки зрения режимов адресации, машинные инструкции. Плюс код гарантированно выровнен на границу слова: размер любой инструкции кратен 2-м байтам, что сокращает количество нужных циклов чтения кода из памяти. Все регистры общего назначения R0-R5 можно использовать в коде для любых вычислений, для косвенной и двойной косвенной адресации данных и ещё с автоинкрементом:
MOV (R2), @(R0)+
У ДВК более совершенные , с точки зрения режимов адресации, машинные инструкции.
По поводу "более совершенных", включая режимы адресации, уже высказался.
Плюс код гарантированно выровнен на границу слова: размер любой инструкции кратен 2-м байтам, что сокращает количество нужных циклов чтения кода из памяти.
Стало иметь меньшее значение, когда началось массовое кэширование DRAM.
Все регистры общего назначения R0-R5 можно использовать в коде для любых вычислений
Для вычислений (таких же add, sub, and, or...) - можно и в x86. И с примерно такими же ограничениями. Например, для DIV нужна конкретная пара регистров (ok, в PDP-11 - три таких пары), а не вообще любые. Что ещё? Ширина сдвига в CL? Так в PDP-11 большинства тех сдвигов нет, а что есть - на 1 бит.
и ещё с автоинкрементом:
Ну да, экономия на размере кода для типового применения. Но не на скорости обработки, тут уже сильно зависит от реализации процессора. Заметим, ARM сделал это ещё гибче - и по возможности быстрее, сохраняя принцип "все вычисления - только с регистрами".
Стало иметь меньшее значение, когда началось массовое кэширование DRAM.
Есть ещё другое - инструкции фиксированного размера легко раздекодировать параллельно, так что это не тормозит пайплайн; в x86 же кроме ухищрений с декодером придумали ещё uop кеш и прочее LSD.
инструкции фиксированного размера легко раздекодировать параллельно, так что это не тормозит пайплайн
Да, но есть недостаток - полученное не масштабируется. Что будет, если (когда) у ARM закончатся опкоды?
Я тут вообще подумал, что имело бы смысл отличать код начала от кода продолжения. Например, группы по 4 байта, в не-первой всегда 4 старших бита 1111. Некоторое усложнение кодирования параметров, но зато возможности для расширения будут неограничены, и выборка команд - увидел 1111 - ага, отсюда не декодируем. Уверен, что не я первый придумал эту идею, так что больше интересно, почему нигде ещё не реализовано;\
68К – 32-битный только в названии и ширине регистров. 16 бит АЛУ и шина данных, 24 битная шина адреса.
А какая разница? ISA 32-битная, линейная память. Там 32-битные операции, а не 16-битные. Ничто, кроме недостатка транзисторов, не мешало сделать 32-битное АЛУ. И программы бы никак от этого не менялись.
Можно сколько угодно говорить про 32 бита, но 16Мб памяти максимум – это не про 32 бита.
Это очень сильное утверждение для ТОГО времени.
16 мегабайт памяти, когда реальные системы имеют 0,5-1 мегабайта, это овер дофига.
Сейчас в 32-битных микроконтроллерах памяти может быть 16 килобайт ОЗУ и никого это не смущает.
"68k" это "x86", а первый процессор семейства 68k это 68000. И как конкурент 286 он очень даже -- появился раньше, адресовал память сразу нормально, а не убого как 286, кусками по 64к.
Если бы 68к с нулями был коммерчески доступен в 79 году, IBM бы не выбрала 8088 для старта.
IBM вообще случайно в это дело влезла, и руководствовалась другими соображениями.
Но где то видел, что 68k рассматривался как вариант - основная проблема в том, что на тот момент 8086/8088 уже был готов для заказа, а 68k только анонсировали.
Ну так наверное и было. Я к тому писал, что IBM не рассматривала своё решение как какой-то эпический выбор архитектуры на 50 лет вперёд. Просто взяли, что есть, для интеллектуального терминала, а там он пошёл в самостоятельные персоналки и заверте... А Моторола на то время была гораздо более солидной фирмой, чем Интел, поэтому наверняка предпочли бы её при прочих равных условиях.
S/360 (1964).
Ну конкурентов то не так много было, а точней из серьезных всего один - Motorola 68020, остальные кто сумел сделать 32бита не смогли построить вокруг них экосистему и остались нишевыми решениями (для телекома например). Т.е. сравнивать в основном надо с моторолой. Процессор Моторола не имел встроенного MMU в том же массовом компьютере Amiga, что сказывалось на невозможности нормальной реализации многозадачности и защиты памяти процессов и "подкачки", в то время как у 386 все это уже шло "из коробки".
НО в принципе, эти процессоры вышли практически один за одним, так что они оба можно сказать были революционными. Сама 32-бит архитектура можно сказать намного опередила свое время, ибо даже типичные объемы ОЗУ на то время это 0.5-1Мб а запускали на 386 MSDOS и 16-битные приложения.
https://en.wikipedia.org/wiki/NS32000
Компьютеры бывали не только персональные, всё что покрупнее уже во второй половине 70х делалось 32бит на слайсевых процессорах, так что понимание таких архитектур и опыт уже были в наличии.
ну это был слабый проц на фоне того же 386 и Моторолы, даже не имел полноценной 32бит архитектуры - 16бит АЛУ и 24бит адресация + 16 бит интерфейс (32 сделали практически как уже вышел 386), отсутствие MMU (реализовывалось отдельным навесом).
т.е. как то что то кто то уже конечно делал в других процессорах, но 386 выходит единственный на то время вобрал в себя все доступные идеи того времени. Маленькой революцией его определенном можно назвать, с моей точки зрения.
из серьезных всего один - Motorola 68020
На подходе был первый SPARC с весьма оригинальными идеями типа регистровых окон (что смешно, изначально он разрабатывался как сопроцессор для Моторов 680х0, но в процессе Остапа понесло:)
это точно... но хоть перегружаться не надо для выхода из защищенного режима в реальный
Возможно, с точки зрения своей архитектуры Intel 80386 и был революционным - в проектировании микросхем я не разбираюсь.
Революционность i386 состоит еще и в том, что это был первый микропроцессор полностью выполненный в софте. Т.е. вся аппаратура написана на HDL языке, симулирована и верифицирована, и только после этого был изготовлен "кремний". Интелу пришлось нанять каких-то студентов, чтобы те дописывали софт для симуляции. На эту тему у Кена тоже есть статья и она гораздо более интересная чем эта. Вообще, у этого автора можно переводить всё подряд - одна сплошная годнота. :-)
универсальные регистры
Универсальные? С прибитой намертво специализацией по некорым методам адресаций и команд?
Так в чем абсурдность, про которую говорится в заголовке?
Зашел написать ровно такой же комментарий.
Да, без сравнения с "простыми" схемами регистров, абсурдность сложности не очевидна.
Тем более в то время вряд ли были продвинутые оптимизаторы топологий.
Автор нашел кликбейтное название для того, что послужило успеху процессора -- все что запускалось на 8088 и на 80286 могло запускаться на 80386, т.е. обратная совместимость была обеспечена на аппаратном уровне. Это сложно недооценить - наработки софта уже были большими - игры, текстовые процессоры, утилиты и т.п.. Если бы переход на 80386 потребовал бы всё это перекомпилировать -- вот ровно из-за того что регистры вдруг перестали быть 16- и 8- разрядными, то переход на платформу был бы гораздо медленнее и гораздо сложнее. И вообще не факт что Интел вышел бы победителем из конкуренции с Моторолой. Примерно такая же ситуация с Microsoft. Если бы Microsoft требовала переписывать софт заново для каждой новой ОС, как это требовала MacOS не раз, из-за отсутствия обратной совместимости, то тоже не факт, что обрела бы такую долю рынка.
По поводу регистров... Ну не только они. Есть инструкции, которые почти никто не использует, напр. AAM - кто помнит на-вскидку что она делает? А она однобайтовая и занимает место в трансляторе инструкций.
Плюс есть реальный режим работы. Так называется режим, где адресация прямая и не защищена. Процессор стартует в этом режиме. Ну тоже довольно сильное переусложнение ради обратной совместимости.
Если бы Microsoft требовала переписывать софт заново для каждой новой ОС, как это требовала MacOS не раз
Зато Эпл наработал Розетту, которая натурально чудо какое-то. Я буквально на днях через копирование по домашней сети установил на M3 авиасимулятор написанный для x86-64 для МакОС 15-летней давности, под операционку на 5 мажорных версий младше. Его в аппсторе уже нет, по-другому никак, так что это была последняя надежда – вдруг хоть как-то заработает.
В результате на М3 фреймрэйт 70 фпс на макс качестве, и это куда лучше чем на 4,5ГГц i7 на том же разрешении.
На Винде при этом я не могу запустить без бубна старый нативный софт даже 10-летней давности иногда. А постарше софт и с бубном не запустишь.
Виста была в 2005? Это уже так-то 20 лет. Почти все с нее запускается, за редким исключением. С экспи чуток меньше, а это уже 25 лет почти…
Удачи повторить такое с маком. Особенно софт на с g3 на современном imac.
Удачи повторить такое с маком
Слуште, у меня на маке под параллелсом как раз есть XP – полностью, замечу, интегрированная в Маковский UI – которую я бережно храню, чтобы запускать то, что у меня на Win7 не стартует. На нативном IBM-овском ноуте.
Под тем же параллелсом я под уж не помню какой виндой стартовал Ксемул для запуска кое-чего написанного под Мак G4. Не G3 конечно, но…
Так что удача тут особо не при чём, это как-бы штатная схема )
Вот так выглядит стартующая XP на маке под ретиной, в 2025 )

И вообще не факт что Интел вышел бы победителем из конкуренции с Моторолой
Если бы 68k побыстрее довели до массового производства в то время, когда IBM выбирала процессор для PC, всё могло пойти совсем по другому. Но история не терпит сослагательного наклонения.
Статьи Кена Шерриффа безусловно заслуживают уважения, спасибо за перевод. Но хотелось бы сделать пару замечаний.
1. В переводе есть неточость которая противоречит схеме и тому что написано у Кена. Речь идет о 8Т ячейке, цитата:
Переходный транзистор H блокирует сигнал, пока из регистра происходит считывание.
В оригинале:
Transistor H is a pass transistor that blocks this signal until a read is performed on this register;
Перевод по смыслу получился строго противоположный (слово until переведено неверно). Это станет очевидным если посмотреть на схему 8T ячейки. Транзитор H в ней ничего не блокирует, он не выполняет функцию арбитража, как может следовать из перевода. Этот транзистор коммутирует ячейку к соответствующей линии шины данных, и всё. На мой вгляд, правильный перевод мог бы звучать так:
Переходный транзистор H блокирует распростарнение сигнала от ячейки до тех пор, пока не будет выполнена операция чтения.
Для представленой ячейке 8T операция чтения может выполняться одновременно с операцией записи, при этом считываться будет записываемый в ячейку сигнал (посмотрите на схему и проследите распространение сигнала), никакой блокировки тут нет.
2. Тут уже небольшая претензия к Кену. В описании всё той же ячейки T8 автор пишет, что как только подается сигнал чтения (на транзистор H), то налиниях Read bitline появляются данные из текущей ячеки. Причем ранее он говорит о том, что сама ячейка резделена тразитором G который он почему-то называет буферным. Транзистор G не является буферным, это обычный "open drain", он подтягивает к "земле" линию шины данных (Read bitline) если на выходе ячейки находится лог "1". Если линия скоммутирована (сигнал Read wordline подан), то эта лог "1" на выходе превратиться в лог "0" на линии данных (Read bitline), так как линия данных будет тоже подтянута к "земле". Но встает вопрос, а как тогда на линии данных должна появится лог "1" если на выходе ячейки присутствует лог "0" ? Ведь в этом случае транзистор G будет закрыт и ячейка будет отключена от линии данных!? Ответ прост - линии данных всегда подтянуты к положительному потенциалу через резистор подтяжки в несколько десятко в кОм, т.е. по-умолчанию на них всега присутствуют логические единицы, а лог "0" появляется только когда отпирается транзистор G (т.е. на выходе ячейки - лог "1"). Кен ничего не говорит о "потдяжках", видимо полагая, что читатель находится в курсе таких подробностей. В статье есть сноска на небольшое замечание про подтяжку шины данных без особого разъяснения.
Кстати, "подтяжки" на кристаллах обычно реализованы в виде таких же транзисторов. Если затвор планарного N-канального полевого транзистора соединить с истоком, то он будет всегда заперт и в таком состоянии его сопротивление составляет десятки-сотни кОм, что и требуется для подтяжек. Конденсаторы, о которых говорит Кен, это тоже транзисторы - емкость создается затвором по отношению к стоку или истоку. А ячейка динамической памяти это один транзистор с высокой паразитной ёмкостью затвора.
3. Об архитектуре x86. Кен справедливо змечает:
Причём схема регистров i386 также раскрывает проблему обратной совместимости. Архитектура x86 поддерживает доступ к 8-битным регистрам для совместимости с другими процессорами из 1971 года. Эта совместимость требует дополнительной схемы, в частности сети перетасовки и чередующихся регистров. Глядя на схему процессоров x86, не могу не отметить преимущества архитектуры RISC, которая избегает многих особенностей таких процессоров.
А также дополнительных линий управления для доступа к дробным частям большого регистра. Если в 32-х битной архитектуре i386 на один регистр требуется пять линий упрвления, то в современной 64-х битной их, очевидно, стало еще больше. То есть всё это "легаси" это не просто "дополнительный микрокод" как многие наивно полагают, оно поедает драгоценное пространство на площади кристалла и серьезно усложняет схемотехнику. И все это добро Интел продолжает тянуть на своих полечах в светлое будущее.
И все это добро Интел продолжает тянуть на своих полечах
По-моему, АМД тоже. Да и вообще, все x86 должны.
Была надежда, что хотя бы от части легаси начнут избавляться в x86S. Не срослось.
То есть всё это "легаси" это не просто "дополнительный микрокод" как многие наивно полагают, оно поедает драгоценное пространство на площади кристалла и серьезно усложняет схемотехнику.
А можно в цифрах - какой процент от площади кристалла это "усложнение" занимает? Как это влияет на максимальную частоту процессора? На сколько увеличивается время разработки новых процессоров из-за "усложненной" схемотехники?
Читаю каменты, сплошь моторола и 386. А почему про ARM забыли то? В виде компа Acorn Archimedes он, ARMv2, вышел в 87, но в виде tube к BBC Micro вроде был доступен и ранее как ARMv1
Как то его не особо было видно до появления мобильных устройств. Но согласен, вполне реальные 32 бита с самого начала.
Абсурдно усложнённая схема регистров в Intel 80386