Как получить текущее время в секундах на языке ассемблера MIPS?

Я пытаюсь получить текущее время в MIPS в формате час:минута:секунда. Я знаю, что использование системного вызова 30 поместит общее количество миллисекунд с 1 января 1970 года в регистры $a1 и $a0, но я не знаю, как сложить регистры вместе и разделить на 1000, чтобы получить общее количество секунд. Я считаю, что остальное должно быть легко после этого.

Благодарю вас!


person Brandon Kowalecki    schedule 05.10.2017    source источник
comment
связанные: stackoverflow.com/questions/16050338/. Вы можете либо использовать аппаратную инструкцию деления 32b / 32b => 32b, чтобы построить деление с повышенной точностью на 1000, либо использовать инверсию мультипликативного метода с фиксированной точкой. Вам придется построить умножение 64b x 1000 => 128b из 32-битных умножений, но на оборудовании с умножением намного быстрее, чем деление, это может стоить того. (gcc просто вызывает вспомогательную функцию libgcc.a для int div_by_1000(long long millis) { return millis / 1000; }: godbolt.org/g/MR6h6d; мог бы посмотреть на его src.)   -  person Peter Cordes    schedule 06.10.2017


Ответы (1)


Деление 64-битного числа

Поскольку MIPS, эмулированный MARS, не поддерживает 64 ÷ 32 ⇒ 64 деления1, нам нужно реализовать собственное разделение на несколько слов.
Книга < em>Хакерское восхищение как глава, основанная в основном на Искусстве компьютерного программирования Кнута.

Идея в принципе очень проста: представьте 64-битное число как двузначное число, каждая цифра 32-битная (так что основание этого числа равно 232) и выполните оценку. школьное цифровое деление.

Давайте рассмотрим эту идею на примере с основанием 10: рассмотрим 53 / 2.
Мы можем вычислить результат, разделив 5 на 2, что даст результат m1 от 2 и остаток r1 от 1.
m1 — первая цифра результата. (наиболее значимое).
Затем мы "опускаем" 3 из 53, чтобы получить число 13, это r1 * 10 + 3.
Снова выполняем 13/2, чтобы получить m0 = 6 и r0 = 1.
Таким образом, результат равен 26 (с остатком 1).

Мы используем тот же подход, с той лишь разницей, что мы имеем дело с цифрами в диапазоне от 0 до 232 - 1.
Таким образом, 64-битное число, подобное тому, которое возвращает системный вызов time , рассматривается как двузначное число: наименее значащая (крайняя правая) находится в $a0, другая — в $a1.
Например, число 0x15EF18933B1 рассматривается как

 Digit 1  Digit 0
 0x15E    0xF18933B1

Алгоритм, который мы ищем, представляет собой просто цикл, в котором мы «прибавляем» последний остаток к текущей цифре, делим на делитель, чтобы получить текущую цифру результата, а остаток используем на следующем шаге.
Обратите внимание, что под «добавить» мы подразумеваем «добавление с весом», остаток ri не добавляется к текущей цифре ni, он масштабируется по базе, а затем прибавляется (именно так мы делали ранее, мы делали 1*10+3, а не 1+3). Я не буду подробно объяснять алгоритм, так как он широко доступен в Интернете.

Важно отметить, что на самом деле мы никуда не двигаемся.
Из-за масштабирования текущего остатка нам все равно нужно выполнить деление 64 ÷ 32 ⇒ 64 (как и в десятичном случае, 13/2 равно не легче, чем 53/2!).
Разница в том, что мы знаем, что число состоит не более чем из двух цифр.

Чтобы распутать этот циклический аргумент, нам нужно уменьшить масштаб до 32 ÷ 16 ⇒ 32.
MIPS поддерживает 32 ÷ 32 ⇒ 32, ограничивая наш делитель не более чем 65 535, мы получаем то, что хотели.
Итак, Алгоритм работает с полусловом из 16 бит, тогда 64-битное число воспринимается как 4-значное число.

Код

#Input
#   a0:a1 = N = DCBA
#   a2    = K (16-bit)

#Output
#   a0:a1 = quotient
#   hi    = reminder
div64x16:
 subu $sp, $sp, 16

 sw $a0, ($sp)
 sw $a1, 4($sp)

 add $t0, $sp, 8     # Pointer to digits (N)
 add $t3, $sp, 16    # Pointer to result (M)
 xor $t1, $t1, $t1   # Remainder

loop: 
  subu $t3, $t3, 2
  subu $t0, $t0, 2

  sll $t1, $t1, 16   # t1 = R * 65536
  lhu $t2, ($t0)     # t2 = N[i]
  addu $t2, $t2, $t1 # t2 = N[i] + R * 65536

  div $t2, $a2

  mflo $t1           # t1 = (N[i] + R * 65536) / K
  sh $t1, ($t3)      # M[i] = (N[i] + R * 65536) / K

  mfhi $t1           # t1 =  (N[i] + R * 65536) % K

bne $t0, $sp, loop

 mthi $t1

 lw $a0, 8($sp) 
 lw $a1, 12($sp)

 addu $sp, $sp, 16
 jr $ra 

Входные аргументы представлены в виде a0:a1 (64-битное делимое, младшее слово в a0) и a2 (делитель).
Результатом является a0:a1 (частное) и hi (остаток).

Обратите внимание, что существует ограничение на делитель: он должен иметь размер 16 бит.
Это значительно упрощает алгоритм деления, но требует некоторого обходного пути при вычислении времени суток.

Вычисление времени суток

Учитывая мс эпохи Unix (1 января 1970 г.), чтобы получить время суток, нужно разделить на 1000 * 3600 * 24 и взять остаток.
Однако 1000 * 3600 * 24 не соответствует 16 битам. Мы можем сделать это с тремя делениями, но тогда нам нужно будет объединить остатки.

Есть немного более простой и интуитивно понятный подход.
Во-первых, мы избавляемся от ms. Нам не нужна точность мс по времени суток, поэтому мы можем вообще отбросить остаток.

li $v0, 30
syscall

#a0:a1 = ms since epoch

li $a2, 1000 
jal div64x16  

#a0:a1 = seconds since epoch

Теперь нам нужно разделить на 3600 * 24 = 86400, но мы не можем.
Хороший трюк — разделить на 3600 * 12 = 43200 (что соответствует 16 битам), это дает нам количество полдня (назовем его чч), а остаток дает нам количество секунд далеко за полдня (назовем его чч).
Поскольку в половине дня 43200 секунд, время может быть не более 11:59:59.
Мы не знаем, является ли 2:0:0 14:00 или 2:00, нам нужно проверить, hh нечетное или четное, чтобы узнать, если hh четное, то мы находимся в первой половине дня и время правильное, в противном случае мы находимся во второй половине дня и мы прибавляем 43200 (секунды за полдня) к hs.
Это переводит секунды за полдня в секунды за дни.

#a0:a1 = seconds since epoch

li $a2, 43200
jal div64x16

#a0:a1 = half-days since epoch
#hi = seconds in half-day

mfhi $s0              #Seconds in the half-day

andi $a0, $a0, 1      #a1 = 1 if odd half-day number (otherwise 0)
ror $a0, $a0, 1       #a1 < 0 if odd half-day number (otherwise 0)
sra $a0, $a0, 31      #a1 = 0xffffffff if odd half-day number (otherwise 0)
andi $a0, $a0, 43200  #a1 = 43200  if odd half-day number (otherwise 0)

add $s0, $s0, $a0     #s0 = seconds in the day

Как только у вас есть секунды в дне (32-битное число), остальное легко.

li $t0, 3600
div $s0, $t0         
mflo $s0              #s0 = Hour

mfhi $t1 
li $t0, 60 
div $t1, $t0 
mflo $s1              #s1 = Minute
mfhi $s2              #s2 = Second

#Print the time
li $v0, 1
move $a0, $s0
syscall 

li $v0, 4
la $a0, sep
syscall 

li $v0, 1
move $a0, $s1
syscall 

li $v0, 4
la $a0, sep
syscall 

li $v0, 1
move $a0, $s2
syscall  


#Exit
li $v0, 10
syscall 

Примечание. Системный вызов времени использует значение Java new Date().getTime(), это текущее время в часовом поясе по Гринвичу. Если вы не живете в этом часовом поясе, часы будут другими.


1 Это обозначение обозначает 64-битное делимое, 32-битный делитель и 64-битный результат.

person Margaret Bloom    schedule 06.10.2017