Подозрительное поведение shl

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Подозрительное поведение shl

Сообщение Дож »

Код:

Код: Выделить всё

  function One: Int32;
  begin
    One := 1;
  end;
var
  U: UInt64;
begin
  U := One shl 63;
  Writeln(BinStr(U, 64));
end.

Компилируется без варнингов, результат на i386:

Код: Выделить всё

1111111111111111111111111111111110000000000000000000000000000000

Результат на ARMv6:

Код: Выделить всё

1111111111111111111111111111111110000000000000000000000000000000


Если включить RANGECHECKS, то код выше падает в ран-тайме, но вот если заменить 63 на 62, то почему-то выполняется и тоже выдаёт удивительный результат:

Код: Выделить всё

0000000000000000000000000000000001000000000000000000000000000000


Я вот как-то слышал такое, что паскаль -- это строгий типизированный язык для людей, с понятными и естественными операторами, без мутных неявных преобразований. Наверное, что я что-то не понимаю в том, как происходит сдвиг битов? Может ли мне кто-нибудь рассказать как в fpc в точности работает shl?
Аватара пользователя
Снег Север
долгожитель
Сообщения: 3067
Зарегистрирован: 27.11.2007 15:14:47
Контактная информация:

Сообщение Снег Север »

Я только одного понять не могу, какое отношение к собственно Паскалю имеет ассемблерная битовая операция, которая, конечно же, машиннозависима?
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

Так я же не на ассемблере писал, а на паскале. В нём, внезапно, есть оператор shl.

Если написать на ассемблере

Код: Выделить всё

{$ASMMODE Intel}
  function One: Int32;
  begin
    One := 1;
  end;
var
  U: UInt64;
begin
  asm
    call One
    shl eax, 63
    mov DWORD [U], eax
    mov DWORD [U+4], 0
  end;
  Writeln(BinStr(U, 64));
end.

То машиннозависимая инструкция shl на i386 выдаёт

Код: Выделить всё

0000000000000000000000000000000010000000000000000000000000000000

Этот результат всё равно отличается от того, что выдают программы в первом посте.

Так как работает оператор shl в паскале?
SSerge
энтузиаст
Сообщения: 971
Зарегистрирован: 12.01.2012 05:34:14
Откуда: Барнаул

Сообщение SSerge »

Дож писал(а):Так как работает оператор shl в паскале?


Судя по примерчикам, вами приведенным, работает он корректно только над аргументами до 16 разрядов.
А что бы не посмотреть ассемблерный код, в который компилятор это сгенерировал? Не окажутся ли в нем операции с 16-битовыми регистрами?

Добавлено спустя 13 минут 31 секунду:
Кстати, минуточку...
вы двигаете временную переменную in32 на 63 разряда, доводя ее до внутреннего изображения отрицательного числа и затем преобразуете результат в unsigned int64. Во всяком случае, это ничуть не эквивалентно вашему аасемблерному коду.
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

А что бы не посмотреть ассемблерный код, в который компилятор это сгенерировал?

Пожалуйста:

Код: Выделить всё

# Var U located in register edi:esi
...
# [8] U := One shl 63;
   call   P$PROGRAM_$$_ONE$$LONGINT
   movl   %eax,%esi
   shll   $31,%esi
   movl   %esi,%edi
   sarl   $31,%edi

Код: Выделить всё

# [8] U := One shl 63;
  blx P$PROGRAM_$$_ONE$$LONGINT
  lsl r0,r0,#31
  asr r2,r0,#31
  ldr r1,.Lj9
  str r0,[r1]
  ldr r0,.Lj10
  str r2,[r0]
...
.Lj9:
  .long U_$P$PROGRAM_$$_U
.Lj10:
  .long U_$P$PROGRAM_$$_U+4
iskander
энтузиаст
Сообщения: 627
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

X86_64, немного изменил пример(результат не изменился):

Код: Выделить всё

function One: Integer; inline;
begin
  Result := 1;
end;

var
  U: UInt64;
begin
  U := One shl 63;
  Writeln(BinStr(U, 64));
end. 

asm:

Код: Выделить всё

main:
@@c3:
; Temps allocated between rsp+32 and rsp+288
; [12] begin
      push   rbx
      push   rsi
      lea   rsp,qword ptr [rsp-296]
@@c5:
; Var U located in register rax
      call   fpc_initializeunits
; Var U located in register rsi
; [13] U := One shl 63;
      mov   rsi,-2147483648 //<--------------
; [16] Writeln(BinStr(U, 64));
      call   fpc_get_output
      mov   rbx,rax
      mov   rdx,rsi
; Var U located in register rdx
      mov   r8d,64
      lea   rcx,qword ptr [rsp+32]
      call   SYSTEM_$$_BINSTR$QWORD$BYTE$$SHORTSTRING
      lea   r8,qword ptr [rsp+32]
      mov   rdx,rbx
      xor   ecx,ecx
      call   fpc_write_text_shortstr
      call   fpc_iocheck
      mov   rcx,rbx
      call   fpc_writeln_end
      call   fpc_iocheck
; [17] ReadLn;
      call   fpc_get_input
      mov   rcx,rax
      call   fpc_readln_end
      call   fpc_iocheck
; [18] end.
      call   fpc_do_exit
      nop
      lea   rsp,qword ptr [rsp+296]
      pop   rsi
      pop   rbx
      ret

Кажется надо стучаться в багтрекер.
Kemet
постоялец
Сообщения: 241
Зарегистрирован: 10.02.2010 18:28:32
Откуда: Временно оккупированная территория
Контактная информация:

Сообщение Kemet »

Так ты сдвигаешь 32битное число, а в интеле количество сдвигов в таком случае обрезается до 5 бит - вот что там в результате получится из 63? 31 - максимально возможное количество сдвигов для 32 битного числа.
SSerge
энтузиаст
Сообщения: 971
Зарегистрирован: 12.01.2012 05:34:14
Откуда: Барнаул

Сообщение SSerge »

Код: Выделить всё

program shltest;

function One: Int32;
  begin
    One := 1;
  end;

function Two: UInt64;
begin
  Two:=1;
end;

var
  U: UInt64;
  D: UInt64;
begin
  U := One shl 63;
  Writeln(BinStr(U, 64));
  D := 1;
  U := D shl 63;
  Writeln(BinStr(U, 64));
  U := Two shl 63;
  Writeln(BinStr(U, 64));
end.   


Результат:

Код: Выделить всё

./shltest
1111111111111111111111111111111110000000000000000000000000000000
1000000000000000000000000000000000000000000000000000000000000000
1000000000000000000000000000000000000000000000000000000000000000


...Наглядно видим, что глючь не в shl, а в преобразованиях разных типов целого

Добавлено спустя 10 минут 57 секунд:
Еще красочнее:

Код: Выделить всё

program shltest;

function One: Int32;
  begin
    One := 1;
  end;

function Two: UInt64;
begin
  Two:=1;
end;

var
  U: UInt64;
  D: UInt64;
  C: Int32;
begin
  U := One shl 63;
  Writeln(BinStr(U, 64));
  D := 1;
  U := D shl 63;
  Writeln(BinStr(U, 64));
  U := Two shl 63;
  Writeln(BinStr(U, 64));
  C:=1;
  C := C shl 64;
  U:=C;
  Writeln(BinStr(U, 64));
  Writeln(BinStr(C, 32));
end.     


Код: Выделить всё

./shltest
1111111111111111111111111111111110000000000000000000000000000000
1000000000000000000000000000000000000000000000000000000000000000
1000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001
00000000000000000000000000000001

iskander
энтузиаст
Сообщения: 627
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

Да, действительно, One shl 63 -> One shl (63 mod 32) = -2147483648;
А %1111111111111111111111111111111110000000000000000000000000000000 это оно и есть. :oops:
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

Так что же это получается: паскаль абсолютно всё делает правильно? Вот эти mod 32 -- так и должны сдвигаться биты, один я про это не знал?

Кто не компилируя может предугадать что выдаст код

Код: Выделить всё

var
  U: UInt64;
begin
  U := LongInt(1) shl 63;
  Writeln(BinStr(U, 64));
end.

1000000000000000000000000000000000000000000000000000000000000000
или
1111111111111111111111111111111110000000000000000000000000000000
?
iskander
энтузиаст
Сообщения: 627
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

Дож писал(а):Вот эти mod 32 -- так и должны сдвигаться биты

Во всяком случае так было когда-то у Делфи в доке.
Дож писал(а):Кто не компилируя может предугадать что выдаст код

Если первый вариант, тогда все-таки в багтрекер.

PS
RAD Studio 10.3 Rio писал(а):Note that the value of y is interpreted modulo the size of the type of x.
Thus for example, if x is an integer, x shl 40 is interpreted as x shl 8 because an integer is 32 bits and 40 mod 32 is 8.
Последний раз редактировалось iskander 18.04.2019 09:56:57, всего редактировалось 1 раз.
Kemet
постоялец
Сообщения: 241
Зарегистрирован: 10.02.2010 18:28:32
Откуда: Временно оккупированная территория
Контактная информация:

Сообщение Kemet »

вроде как 1000000000000000000000000000000000000000000000000000000000000000, потому что константное выражение, а оно, вроде как, считается в 64 бит всегда
iskander
энтузиаст
Сообщения: 627
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

Kemet писал(а):оно, вроде как, считается в 64 бит всегда

А это где-нибудь документировано?
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

вроде как 1000000000000000000000000000000000000000000000000000000000000000, потому что константное выражение, а оно, вроде как, считается в 64 бит всегда

Поздравляю, вы угадали!

Теперь поиграемся со вторым операндом, вынесем его в функцию

Код: Выделить всё

  function SixtyThree: UInt64;
  begin
    SixtyThree := 63;
  end;
var
  U: UInt64;
begin
  U := 1 shl SixtyThree;
  Writeln(BinStr(U, 64));
end.

Здесь уже нет ни 32-битной арифметики, ни знаков. Что же выдаст программа в этот раз?
Kemet
постоялец
Сообщения: 241
Зарегистрирован: 10.02.2010 18:28:32
Откуда: Временно оккупированная территория
Контактная информация:

Сообщение Kemet »

Ну так выражение не константное, и вероятно результат будет зависеть от битности системы.

Добавлено спустя 3 минуты 29 секунд:
iskander писал(а):
Kemet писал(а):оно, вроде как, считается в 64 бит всегда

А это где-нибудь документировано?
Без понятия, я уж давно не использую ни дельфи ни Фрипаскаль/Лазарус, просто захожу по привычке почитать интересные темы))) Но подобная тема уже была несколько лет назад. И там говорилось, что константные выражения считаются в 64 бит всегда. Вроде Сергей Горелкин отвечал.
Ответить