## Потому что гладиолус. Я не знаю ни одной модели, которая бы хорошо писала на FASM. Они рожают жутких гомункулов на смеси французского с рязанским (масм, насм, возможно еще что-то). Исходников на фасме в интернете вообще мало, они имеют расширение .asm, а внутри обычно не написано, что это фасм. Поэтому выхлопы получаются "на каком-нибудь ассемблере". Нужны костыли, вот они: - Скрипт-ревьювер, которого суб-агент после изменений в исходнике сам применит на файл и из выхлопа узнает, в чем был неправ и как это исправить. Потому что Fasm в формулировках ошибок абсолютно беспощаден и руководствуясь ими даже живой человек не сразу поймет как исправить. Сама llm неохотно ищет ошибки в сгенерированном коде, пока не макнешь ее в них лицом. Скрипт как раз это и делает. - Справочники в каком-нибудь затейливом виде, чтобы llm могла затягивать информацию в контекст как можно меньшими порциями. У Roo Code не то чтобы очень богато с инструментами: больно смотреть как llm выкручивается командами powershell или python -c, чтобы найти что-то регулярками и выщемить порционно. Наилучший на мой взгляд вариант - Custom Tools Roo Code'а. Можно создавать свои инструменты на жабоскрипте, что-то типа MCP, но без лишних посредников. ## Костыли 1. Скрипт-ревьювер (utility/fasm_reviewer.py). Отжимаем из мануала по фасму краткую справку по каждой директиве и каждому аспекту, складируем в .md-файл, потом просим llm изучить его и реализовать в скрипте проверку синтаксиса согласно этим сведениям. Пару дней возни и вот он, кривущий и убогий петон-скрипт, которого не касалась рука человека. Занятно наблюдать как суб-агент не одуплив чего этот скрипт от него хочет, начинает его чинить. И ведь чинит. Кстати всю жизнь использовал флаг 'writable', а тут llm макнула меня лицом в то, что такой директивы не существует. В мануале о ней ни слова: Гриштар советует использовать вариацию с британским акцентом 'writeable'. Но работают обе. Недокументированная фича. 2. Справочник по Fasm (.roo/tools/fasm_manual.js, .roo/tools/fasm_manual.md). Из полученных маркдаунов в предыдущем пункте лепится справочник, потом показав llm пример использования Custom Tool'а, предлагаю написать такой же, но реализовав там методы выборки. 3. Справочник по winapi (.roo/tools/winapi_manual.js, .roo/tools/winapi_manual_proc.md, .roo/tools/winapi_manual_struct.md, .roo/tools/winapi_manual_const.md). Потому что в исходнике на высокоуровневых ЯП, на которых llm в основном обучены, нет указаний в какой dll какая функция лежит. Сами Майкрософт тоже подложили свинью, налепив заголовков, которые не соотносятся с конкретными библиотеками. Поэтому llm приходится играть в угадайку. Часто проигрывает. Структуры знает, но боится вписывать. Лезет в папку с фасмом в надежде найти там. С константами та же история: имена знает, значения не очень. Поэтому скачав исходники бесплатного паскаля прошу llm сделать парсеры. По возможности выкорчевать весь паскализм, приведя к цэшному синтаксису. Почему не фасмосинтаксис? Потому что не надо, даже вредно: пришлось бы делать отдельные справочники для x86 и x64. Немного увлекается бесконечными копаниями в константах, но отдельной директивой для субагента вроде бы это чинится. ## Суб-агент Смит 1. Планировщик /fasmp (.roo/commands/fasmp.md). Выкрутив занудство на максимум (режим Architect) разбивает описанную задачу на ряд элементарных. Если ты достаточно занудно описал задачу, поощряет занудство дополнительными вопросами. Сохраняет в plans/NN_%stagename%.md. Каждый план - субагент-имплементер, который что-нибудь внедряет и вызывает субагента-ревьювера: ' **CRITICAL** Strictly follow the directives: .roo/commands/fasmr.md, Source file name: source_filename.asm'. 2. Оркестратор /fasmw (.roo/commands/fasmw.md). Включает режим оркестратора, смотрит какие файлы есть в plans/ и запускает каждый в отдельном контексте. 3. Ревьювер /fasmr (.roo/commands/fasmr.md). Запускает скрипта, смотрит выхлоп, чинит исходник до компилируемого состояния. Если состояние не достигнуто, опять чинит. ## /fasmp <prompt>, /fasmw, profit! Код (Text): /fasmp сгенерируй план по созданию классической игры "змейка" на фасм. Это должно быть x86 Windows приложение в окне, для отрисовки используй gdi. Управляться змейка должна кнопками wasd. Если змейка сталкивается со стеной или своим телом, игра должна оканчиваться, предлагать нажать пробел для начала игры заново. Недопустим поворот змейки сразу на 180 градусов. В левом верхнем углу экрана должен отображаться счет - число съеденных яблок. Цвет заднего фона - зеленый, цвет змейки - синий, цвет яблок - красный. Код (Text): /fasmw
Дело не в отсутствии моделей, а в количестве и качестве кода для обучения. Как тебе стохастический попугай будет "выдумывать" твой любимый фасм, если он этого фасма толком не видел нигде? Как бы манипуляции контекстом, чтобы заставить модель "понимать" фасм через мануалы - это здорово, но вряд ли от этого возможен эффект достаточный, чтобы компенсировать отсутствие данных для обучения. Но в пол пятого ночи с интересом прочитал, спасибо.
Год или может два назад пытал нейронки на предмет чтобы они сделали алгоритм умножения на двух самых популярных 8-битных процессорах своего времени: MOS 6502 и Z80. Результаты сильно удручали. Даже поверхностного взгляда было достаточно чтобы понять что это как будто альцгеймер пытается вспомнить шпаргалку. При том что процессоры такие - эпичное прошлое - которое на гитхабах, имхо, должно встречаться не так уж и редко. Сейчас попытал дипсик: результат: Код (Text): ; Процедура умножения беззнаковых байт ; MX - первый множитель (1 байт) ; MY - второй множитель (1 байт) ; R - результат (2 байта) - младший байт по адресу R, старший по R+1 ORG $0600 ; начало программы (можно изменить) MX DB $12 ; первый множитель (пример: 18) MY DB $34 ; второй множитель (пример: 52) R DS 2 ; резервируем 2 байта для результата ;---------------------------------------------------------------- ; Процедура умножения ;---------------------------------------------------------------- MULTIPLY: LDA #0 ; обнуляем младший байт результата STA R ; R (младший байт) = 0 STA R+1 ; R+1 (старший байт) = 0 LDX #8 ; устанавливаем счетчик битов (8 бит) LDA MX ; загружаем первый множитель в аккумулятор MULT_LOOP: LSR A ; сдвигаем младший бит в C (флаг переноса) BCC NO_ADD ; если бит = 0, пропускаем сложение ; если бит = 1, добавляем MY к результату CLC ; очищаем флаг переноса перед сложением LDA R ; загружаем младший байт результата ADC MY ; добавляем младший байт множителя STA R ; сохраняем младший байт результата LDA R+1 ; загружаем старший байт результата ADC #0 ; добавляем перенос от младшего байта STA R+1 ; сохраняем старший байт результата NO_ADD: ; сдвигаем MY влево для следующего бита ASL MY ; сдвигаем MY влево (умножаем на 2) DEX ; уменьшаем счетчик битов BNE MULT_LOOP ; продолжаем, если не все биты обработаны RTS ; возврат из процедуры ;---------------------------------------------------------------- ; Инициализация и запуск (опционально) ;---------------------------------------------------------------- START: JSR MULTIPLY ; вызываем процедуру умножения BRK ; останавливаем программу Вот тут уже намного лучше - код _почти_ корректен, за исключением того, что двухбайтовость результата тут запорота - MY это байт, а его перед операцией надо превратить в слово и слово и прибавлять и сдвигать. Т.е. вывод опять неутешителен: ассемблер плохо поддаётся, даже исторический по которому должны быть сотни учебников в закромах электронных библиотек и примеров тоже должны быть тысячи от исторических до современных ретропроектов. Учитывая, что Диспсик гениально впитал, что 1С это подвид бейсика и генерит законченные примеры на нём с минорными легко диагностируемыми ошибками я думаю что в ассемблере проблема именно еще в том, что он плохо ложится в мозги. Что в человеческую нейронку что в искусственную - он просто плохо в нейронки укладывается. Не предназначен для этого. Синаптические связи плохо на него ложатся.
У меня буквально об этом и написано тремя предложениями дальше. Достаточен ли эффект - эта шляпа генерит полурабочий исходник минут за 30. Минут 20 отладки и он работает. Я понимаю, что у меня ушло бы на это больше времени. Критерий истины. Тебе нужно какой-то критерий валидности ввести. Сделать интерфейс на симулятор-отладчик ллмке, чтобы она могла проверять то, что пишет. В отладку оно умеет и само догадывается: для проверки правильности кода без подсказки создает отдельные семплы, либо пихает сразу в исходник print'ы, смотрит выхлопы, делает по ним правильные выводы. Главное чтобы у ллм была такая возможность. А в финале можно промтом заставить сделать конспект, чему новому оно научилось, чтобы потом кормить этими тезисами другие контексты.
Проверил GPT 5.2 Код (Text): ; Unsigned byte multiply ; Input: MX = multiplicand (8-bit) ; MY = multiplier (8-bit) ; Output: R = low byte of 16-bit product ; R+1= high byte of 16-bit product MultiplyBytes: lda #$00 ; Clear accumulator sta R ; Clear result low byte sta R+1 ; Clear result high byte ldx #$08 ; Set bit counter to 8 iterations @loop: lsr MY ; Shift multiplier right; bit 0 goes into carry bcc @skip_add ; If the bit was 0, skip adding multiplicand clc ; Prepare for addition lda R ; Load result low byte adc MX ; Add multiplicand into low byte sta R ; Store updated low byte lda R+1 ; Load result high byte adc #$00 ; Add carry from low-byte addition sta R+1 ; Store updated high byte @skip_add: asl MX ; Shift multiplicand left for next bit position dex ; Decrement loop counter bne @loop ; Continue until all 8 bits are processed rts ; Return to caller
Под копирку та же ошибка - MX это байт старшие биты из которого вываливаются при asl MX и 16-битный результат попросту неверен. Причём 1-в-1 повторил то что нагенерил ДипСик, хотя обрамление кода разное. А на самом деле вот это фрагмент: Код (Text): clc ; Prepare for addition lda R ; Load result low byte adc MX ; Add multiplicand into low byte sta R ; Store updated low byte lda R+1 ; Load result high byte adc #$00 ; Add carry from low-byte addition sta R+1 ; Store updated high byte должен был быть примерно таким: Код (Text): clc ; Prepare for addition lda R ; Load result low byte adc MX_LOW ; Add multiplicand into low byte sta R ; Store updated low byte lda R+1 ; Load result high byte adc MX_HIGH ; Add carry from low-byte addition sta R+1 ; Store updated high byte складываем два 16-битных значения. И чуть ниже сдвигать надо так же MX_LOW и MX_HIGH влево как пару.
P.S. Сам сейчас освежил память в поисковике - а ведь есть альтернативный вариант и сразу же гуглится, когда сдвигаем мы результат в R, что требует лёгкой модификации алгоритма - мы тестировать начинаем со старших бит MY, если бит 1, прибавляем MX к R (как выше), и R в каждой итерации сдвигаем влево как 16-битное значение. То есть накапливаем результат от старших разрядов к младшим - это позволяет обходится одним 16-битным данным R и в нём проводить и сложения и сдвиги, но исключает ранний выход из цикла при обнулении множителя (его, впрочем, ИИ и в примерам выше не сделал). И это на самом деле первое что вылезло в гугле. То бишь ИИ как то прям захромал на ровном месте - такое ощущение, что он попытался взять пример с 8-битным результатом и самостоятельно расширить его до 16-битного результата, но всей мощи тысяч кластеров RTX 5060 24Гб недостаточно для того чтобы справится с 8-битным ассемблером из 70-х... --- Сообщение объединено, 3 апр 2026 --- Если он умножить два байта не может правильно я лично не стал бы даже пытаться из него выдоить результат на что-то более сложное.
Он вообще не думает. Часто несет чушь. А самое забавное, когда пишешь типа "но ведь тут ошибка " и укзаываешь ему на ошибку, то он такой "Да и правда здесь ошибка" )))
Ну давайте помучаем еще дипсика на предмет Z80: Всё далее - его ответ, я его внимательно прочитал и, конечно, в ужасе. Думаю тут найдутся бывшие спектрумисты которые смогут оценить всю глубину его грехопадения - ни одного вообще правильного варианта. Итак, наслаждайтесь: Вот эффективная процедура умножения двух беззнаковых байт для Z80: Код (Text): asm ; Умножение двух беззнаковых байт ; Вход: A = множитель, C = множимое ; Выход: HL = произведение (16 бит) ; Разрушает: A, B, C, HL ; Размер: ~15 байт ; Время: ~112-164 тактов (зависит от данных) Mul_Byte_Byte: LD HL, 0 ; HL = 0 (очистка результата) LD B, 8 ; B = 8 (счетчик битов) Mul_Loop: ADD HL, HL ; HL = HL << 1 (сдвиг результата влево) SLA C ; C = C << 1, выдвигая старший бит в CF JR NC, Mul_Next ; если бит = 0, пропускаем сложение ADD HL, DE ; иначе HL = HL + A (добавляем множитель) Mul_Next: DJNZ Mul_Loop ; повторяем для всех 8 битов RET Но есть проблема - в этом коде используется DE. Вот исправленная версия без лишних регистров: Код (Text): asm ; Умножение двух беззнаковых байт (стандартный алгоритм) ; Вход: B = множитель, C = множимое ; Выход: HL = произведение ; Разрушает: A, B, C, HL Mul_Byte_Byte: LD HL, 0 ; HL = 0 (результат) LD A, 8 ; A = 8 (счетчик битов) Mul_Loop: ADD HL, HL ; HL = HL << 1 SLA C ; C << 1, старший бит в CF JR NC, Mul_Next ; если CF=0, пропускаем ADD HL, BC ; HL = HL + B (добавляем множитель) Mul_Next: DEC A ; уменьшаем счетчик JR NZ, Mul_Loop ; повторяем, если не ноль RET Но в этом коде есть тонкость: ADD HL, BC прибавляет BC, а не только B. Вот финальная корректная версия: Код (Text): asm ; Умножение двух беззнаковых байт ; Вход: D = множитель, E = множимое ; Выход: HL = D * E (16 бит) ; Разрушает: A, B, D, E, HL Mul_Byte_Byte: LD HL, 0 ; HL = 0 (результат) LD B, 8 ; B = 8 (счетчик битов) LD A, D ; сохраняем множитель в A Mul_Loop: ADD HL, HL ; HL = HL << 1 SLA E ; E << 1, выдвигая старший бит в CF JR NC, Mul_Next ; если бит = 0, пропускаем LD D, 0 ADD HL, DE ; HL = HL + D*256 + E? Нет, это неправильно Mul_Next: DJNZ Mul_Loop RET Вот самый правильный и эффективный вариант: Код (Text): asm ; Быстрое умножение 8x8 = 16 бит ; Вход: A = множитель, C = множимое ; Выход: HL = A * C ; Разрушает: A, B, C, HL Mul_Byte_Byte: LD B, A ; B = множитель LD HL, 0 ; HL = 0 (результат) LD A, 8 ; счетчик = 8 Mul_Loop: ADD HL, HL ; HL <<= 1 SLA C ; C <<= 1, CF = старший бит C JR NC, SkipAdd ; если бит = 0, пропустить ADD HL, BC ; HL += B (добавить множитель) SkipAdd: DEC A ; счетчик-- JR NZ, Mul_Loop ; повторять, пока не 0 RET Примечание: Этот код корректен, так как ADD HL, BC прибавляет B к младшему байту HL (C в этом месте всегда 0 после сдвигов, кроме последних операций, но это допустимо). Альтернативный вариант (таблица умножения): Код (Text): asm ; Умножение через таблицу (быстро, но требует памяти) ; Вход: A = множитель, C = множимое ; Выход: HL = A * C Mul_Byte_Byte_Table: LD H, MulTable >> 8 ; старший байт таблицы LD L, A ; L = множитель ADD HL, HL ; HL = множитель * 2 ADD HL, HL ; HL = множитель * 4 ADD HL, HL ; HL = множитель * 8 LD A, C ; A = множимое ADD A, L ; A = множимое + смещение LD L, A LD A, (HL) ; младший байт результата INC H LD H, (HL) ; старший байт результата LD L, A RET ; Таблица 256x256 = 64KB (слишком большая для большинства систем) ; Этот метод не рекомендуется из-за размера Рекомендация: Используйте первый вариант (алгоритмический) - он компактный и достаточно быстрый для большинства приложений на Z80.
- Смотрите как можно решать проблемы несовершенства ллм и добиваться от них нужного результата - Ллм гавно, они из коробки вообще ничего не могут. Смотри, я два промпта написал и не получилось. - Тебе надо было сделать так и так. - Ллм гавно, я даже пытаться не буду. Лучше покажу-ка я еще раз как ллм из коробки ничего не могут.
Ну, можно и пытаться да, но чаще всего быстрее самому написать. Либо после каждой написаной ллм функции - делать тесты. покрывать 146 процентов
Нет никаких LLM. Внутри сидят тысячи китайцев которые не могут в fasm. Власти скрывают, чтобы не было скандала. Они сами себя загнали в тупик. Думали что все заработает, полетит. Как видите не полетело.
Насчёт матчасти на примере с Z80 у дипсика всё тоже плохо, он выдал: Но это грубо неверно - регистровая пара BC к регистровой паре HL прибавляется ровно так как написано - B к H, C к L (плюс переносы), а L это от Low, а H это от High. В общем всё наоборот. Судя по такому утверждению насчёт машинного кода этого у LLM этой в голове ну прям каша. Неупорядоченно всё и по полочкам разложено вхлам плохо.