В прошлых темах мы посмотрели, как помещать в регистры какие-нибудь числа, копировать их из одного регистра в другой и так далее. Но при этом не учитывалась разрядность регистров. В качестве примера будем считать, что наши регистры - 32-разрядные. Соответственно больше 32 бит в них поместить нельзя. А поскольку в качестве языка программирования выбран язык Python, то числа, которыми мы манипулируем, потенциально могут иметь и гораздо большее количество разрядов. И нам надо что-то с этим сделать.
В Python нет, отдельных числовых типов для чисел разной разрядности, как в C или Java, и нам надо самостоятельно убирать старшие разряды, кроме
первых 32. С одной стороны, это сделать легко, так как мы можем с помощью операции логического умножения применить маску 0xFFFFFFFF
, которая отсеить все избыточные разряды:
num1 = 15 num1 = num1 & 0xFFFFFFFF print(f"{num1}") # 15
С другой стороны, нам надо учитывать интерпретацию чисел. Например:
num2 = -3 num2 = num2 & 0xFFFFFFFF print(f"{num2}") # 4294967293
После применения маски вместо отрицательного числа -3 мы получаем положительное число 4294967293. В то же время если мы посмотрим на 16-ричное представление числа после применения маски:
num2 = -3 num2 = num2 & 0xFFFFFFFF print(f"{num2}") # 4294967293 print(f"{num2:0x}") # fffffffd
То мы увидим, что мы получаем корректное значение - 0xfffffffd при интерпретации как 32 разрядного числа со знаком как раз и представляет число -3. То есть мы имеем дело в реальности с число 0xfffffffd, а как его интерпретировать - как беззнаковое число 4294967293 или как число со знаком -3, это другое дело, которое зависит от контекста.
Возьмем программу из прошлой темы и добавим в нее нормализацию по разрядности:
instructions = [] # список инструкций r = [0]*4 # значения 4 регистров # карта сопоставления регистров и их индексов в списке r regs32 = {"r0":0, "r1":1, "r2":2, "r3":3} pc = 0 # указатель на следующую инструкцию # поддерживаемые инструкции и количество их операндов mnemonics = {"mov":2, "add": 3, "sub":3, "mul": 3, "and": 3, "orr": 3} # считываем файл hello.s в список инструкций with open("hello.s", "r", encoding="utf8") as source: lines = source.readlines() # обрабатываем строки из файла for i in range(0,len(lines)): lines[i] = lines[i].split("//")[0] \ .replace(",", " ") \ .strip().rstrip("\n") \ .lower() while " " in lines[i]: # заменяем несколько пробелов одним lines[i] = lines[i].replace(" ", " ") if(lines[i]) == "": continue # если получилась пустая строка, переходим к следующей строке tokens = lines[i].split(" ") # разбиваем инструкцию на токены instructions.append(tokens) # добавляем инструкцию в список instructions # функций вывода состояния программы на консоль def print_state(instruction): print(f"pc:{pc}", end=" ") # выводим значение PC print(f"{instruction:<16}", end=" ") # выводим текущую инструкцию for reg in regs32: # выводим регистры rInd = regs32[reg] print(f"{reg}:0x{r[rInd]:04x}", end=" ") print() # получаем индекс регистра def get_register_index(token, show_error): if (token in regs32): return regs32[token] if show_error: print("Некорректный регистр", token) return None # получаем значение регистра def get_register(token, show_error): rInd = get_register_index(token, show_error) if (rInd != None): return r[rInd] return None # получаем литерал def get_literal(token): try: result = 0 if (token[0:2]=="0x"): result = int(token[2:],16) # если 16-ричное число elif (token[0:2]=="0b"): result = int(token[2:],2) # если двоичное число else: result= int(token) return result & 0xffffffff # нормализуем литерал до 32 разрядов except ValueError: print("Некорректный токен", token) return None # получаем операнд, который может быть регистром или литералом def get_register_or_literal(token): reg = get_register(token, False) if (reg != None): return reg return get_literal(token) # получаем тип инструкции def get_opCount(tokens): if tokens[0] not in mnemonics: # проверяем корректность инструкции print("Некорректная инструкция ", " ".join(tokens)) return None # получаем количество операндов для данной инструкции count = mnemonics[tokens[0]] if count!= len(tokens[1:]): # проверяем количество операндов print("Некорректное количество операндов для инструкции: ", " ".join(tokens)) return None return count # цикл обработки инструкций while True: if pc >= len(instructions): break # если инструкции закончились, то выход из цикла tokens = instructions[pc] # получаем текущую инструкцию для выполнения pc = pc + 1 # увеличиваем указатель инструкций opCount = get_opCount(tokens) # получаем количество операндов if(opCount == None): break # получаем операнды op2, op3 = 0, 0 # первый операнд всегда регистр op1=get_register_index(tokens[1], True) # если инструкция с 2-мя операндами, то второй операнд может быть регистром или литералом if(opCount==2): op2 = get_register_or_literal(tokens[2]) # если инструкция с 3-мя операндами, то второй операнд может быть регистром # а третий операнд может быть регистром или литералом if(opCount==3): op2 = get_register(tokens[2], True) op3 = get_register_or_literal(tokens[3]) # если какой-то параметр не установлен, завершаем цикл if(None in [op1, op2, op3]): break result = 0 match tokens[0]: case "mov": result = op2 case "add": result = op2 + op3 case "sub": result = op2 - op3 case "mul": result = op2 * op3 case "and": result = op2 & op3 case "orr": result= op2 | op3 r[op1]= result & 0xffffffff # нормализуем число до 32 разрядов print_state(" ".join(tokens)) # логгируем состояние программы на консоль
Прежде всего в функции get_literal усекаем переданный литерал до 32 разрядов:
def get_literal(token): try: result = 0 if (token[0:2]=="0x"): result = int(token[2:],16) # если 16-ричное число elif (token[0:2]=="0b"): result = int(token[2:],2) # если двоичное число else: result= int(token) return result & 0xffffffff # нормализуем литерал до 32 разрядов except ValueError: print("Некорректный токен", token) return None
В реальности в различных ассемблерах часто также действует некоторые ограничения на размер литерала. Некоторые ассемблеры генерируют ошибку, если значение литерала, скажем, больше 32-ти или какого-то другого количества разрядов. Здесь же мы просто отбрасываем старшие разряды.
Кроме того, нам надо следить, чтобы после выполнения инструкции помещаемое в целевой регистр число также представляло не больше 32 разрядов. Для этого также нормализуем значение с помощью маски 0xFFFFFFFF
:
r[op1]= result & 0xffffffff # нормализуем число до 32 разрядов
И поскольку теперь десятичное представление числа зависит от интерпретации, при выводе значения регистра в функции print_state заменяем его на шестнадцатеричное представление:
for reg in regs32: rInd = regs32[reg] print(f"{reg}:0x{r[rInd]:04x}", end=" ")
Для тестирования в файле hello.s определим следующую программу:
// тестовая программа на ассемблере mov r1, -3 // r1 = 0xFFFFFFFD mov r0, 8 // r0 = 0x08 mov r1, -1 // r1 = 0xFFFFFFFF mov r0, 255 // r0 = 0xFF mov r1, 0x1234 // r1 = 0x00001234 mov r0, 128 // r0 = 0x80
Консольный вывод программы:
pc:1 mov r1 -3 r0:0x0000 r1:0xfffffffd r2:0x0000 r3:0x0000 pc:2 mov r0 8 r0:0x0008 r1:0xfffffffd r2:0x0000 r3:0x0000 pc:3 mov r1 -1 r0:0x0008 r1:0xffffffff r2:0x0000 r3:0x0000 pc:4 mov r0 255 r0:0x00ff r1:0xffffffff r2:0x0000 r3:0x0000 pc:5 mov r1 0x1234 r0:0x00ff r1:0x1234 r2:0x0000 r3:0x0000 pc:6 mov r0 128 r0:0x0080 r1:0x1234 r2:0x0000 r3:0x0000