tprec22.pp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // http://lists.freepascal.org/lists/fpc-devel/2008-June/013919.html
  2. // I was interested to see if bit packing works when a record member spans
  3. // byte boundaries, and in general it appears to work. However on my system
  4. // I discovered a bug that this program illustrates.
  5. //
  6. // This program demonstrates a bug using a bitpacked record where a member
  7. // crosses a byte boundary.
  8. // The record structure is (on little endian systems -- Jonas):
  9. // Member: | bit15_9 | bit8_1 | bit0 |
  10. // Bits: | 15 .. 9 | 8 .. 1 | 0 |
  11. // Value: | 0..127 | 0..255 | 0..1 |
  12. //
  13. // The structure is mapped to a word via a variant record for convenience.
  14. //
  15. // The limited amount of testing done indicates that the record member bit8_1
  16. // only causes a problem with a value of $FF, but the interesting thing is
  17. // that the result varies depending on other (unrelated) program structure.
  18. // For example the expected word result with bit 0 = 1, bits 1..9 = $FF and
  19. // the rest 0, should be $01FF but I have seen the correct value as well as
  20. // results of $0001, $0003, $0121, $012. Adding code before the tests seems
  21. // to change the result, possibly/ indicating that some variable or register
  22. // used in the bitpacking routine is not being cleared/initialized.
  23. //
  24. // Different compiler modes, optimisations, range checking were tried, but
  25. // the results were the same.
  26. //
  27. // Note that using a variant record to show the value is only a convenience
  28. // here and the error can be seen without a variant record by examining
  29. // the struct directly, or by overlaying the word using the absolute keyword.
  30. //
  31. // Tested on Intel Core 2 Duo running Windows XP Pro SP2, Compiler version
  32. // 2.2.0 [2007/09/09] and 2.3.1 [2008/02/03]
  33. uses SysUtils;
  34. type
  35. bit = 0..1;
  36. t7bit = 0..127;
  37. // A record to test behaviour over byte boundaries.
  38. BitStruct = bitpacked record
  39. bit0 : bit;
  40. bit8_1 : byte; // This set to $FF causes problems...
  41. bit15_9 : t7bit;
  42. end;
  43. // Map the record to a word for convenience - but overlaying
  44. // a word using absolute instead a variant record produces
  45. // the same result.
  46. MappedStruct = packed record
  47. case boolean of
  48. false : (AsWord : word);
  49. true : (AsBits : BitStruct);
  50. end;
  51. procedure TestBits;
  52. var
  53. TestLocal : MappedStruct;
  54. begin
  55. TestLocal.AsBits.bit0 := 1;
  56. TestLocal.AsBits.bit8_1 := $FF;
  57. TestLocal.AsBits.bit15_9 := $0;
  58. if (TestLocal.AsBits.bit0<>1) or
  59. (TestLocal.AsBits.bit8_1<>$ff) or
  60. (TestLocal.AsBits.bit15_9<>0) then
  61. halt(1);
  62. // writeln(' Expected : $01FF, Got : $',IntToHex(TestLocal.AsWord,4),' (I get $0121 V2.2.0, $0109 V2.3.1)');
  63. end;
  64. var
  65. TestGlobal : MappedStruct;
  66. begin
  67. //Do test in main routine - on my system results in $0001.
  68. // Also interesting - using 'with TestGlobal, AsBits do begin ...' instead of
  69. // fully qualified names returns different values in some cases.
  70. Writeln('Testing in main: | $00 | $FF | 1 |');
  71. TestGlobal.AsBits.bit0 := 1;
  72. TestGlobal.AsBits.bit8_1 := $FF;
  73. TestGlobal.AsBits.bit15_9 := $0;
  74. if (TestGlobal.AsBits.bit0<>1) or
  75. (TestGlobal.AsBits.bit8_1<>$ff) or
  76. (TestGlobal.AsBits.bit15_9<>0) then
  77. halt(2);
  78. // writeln(' Expected : $01FF, Got : $',IntToHex(TestGlobal.AsWord,4), ' (I get $0001 V2.2.0, $01F9 V2.3.1)');
  79. // Test it in a procedure - results in $0121 on V2.2.0
  80. writeln;
  81. Writeln('Testing in procedure: | $01 | $FF | 1 |');
  82. TestBits;
  83. // Test this in main
  84. Writeln;
  85. Writeln('Back in main: | $3F | $FF | 1 |');
  86. TestGlobal.AsBits.bit0 := 1;
  87. TestGlobal.AsBits.bit8_1 := $FF;
  88. TestGlobal.AsBits.bit15_9 := $3F;
  89. if (TestGlobal.AsBits.bit0<>1) or
  90. (TestGlobal.AsBits.bit8_1<>$ff) or
  91. (TestGlobal.AsBits.bit15_9<>$3f) then
  92. halt(3);
  93. // writeln(' Expected : $7FFF, Got : $',IntToHex(TestGlobal.AsWord,4),' ($7E01 V2.2.0, $7FF9 V2.3.1)');
  94. // and again in main.
  95. Writeln;
  96. Writeln('In main, | $7F | $FF | 1 |');
  97. TestGlobal.AsBits.bit0 := 1;
  98. TestGlobal.AsBits.bit8_1 := $FF;
  99. TestGlobal.AsBits.bit15_9 := $7F;
  100. if (TestGlobal.AsBits.bit0<>1) or
  101. (TestGlobal.AsBits.bit8_1<>$ff) or
  102. (TestGlobal.AsBits.bit15_9<>$7f) then
  103. halt(4);
  104. // writeln(' Expected : $FFFF, Got : $',IntToHex(TestGlobal.AsWord,4), ' ($FE01 V.2.2.0, $FFF9 V2.3.1)');
  105. // Now set bits 8..1 to $FE
  106. Writeln;
  107. Writeln('Above tests, but with bits 8..1 set to $FE - all work on my system');
  108. Writeln(' | $00 | $FE | 1 |');
  109. TestGlobal.AsBits.bit0 := 1;
  110. TestGlobal.AsBits.bit8_1 := $FE;
  111. TestGlobal.AsBits.bit15_9 := $0;
  112. if (TestGlobal.AsBits.bit0<>1) or
  113. (TestGlobal.AsBits.bit8_1<>$fe) or
  114. (TestGlobal.AsBits.bit15_9<>0) then
  115. halt(5);
  116. // writeln(' Expected : $01FD, Got : $',IntToHex(TestGlobal.AsWord,4));
  117. Writeln;
  118. Writeln(' | $3F | $FE | 1 |');
  119. TestGlobal.AsBits.bit0 := 1;
  120. TestGlobal.AsBits.bit8_1 := $FE;
  121. TestGlobal.AsBits.bit15_9 := $3F;
  122. if (TestGlobal.AsBits.bit0<>1) or
  123. (TestGlobal.AsBits.bit8_1<>$fe) or
  124. (TestGlobal.AsBits.bit15_9<>$3f) then
  125. halt(6);
  126. // writeln(' Expected : $7FFD, Got : $',IntToHex(TestGlobal.AsWord,4));
  127. // and again in main.
  128. Writeln;
  129. Writeln(' | $7F | $FE | 1 |');
  130. TestGlobal.AsBits.bit0 := 1;
  131. TestGlobal.AsBits.bit8_1 := $FE;
  132. TestGlobal.AsBits.bit15_9 := $7F;
  133. if (TestGlobal.AsBits.bit0<>1) or
  134. (TestGlobal.AsBits.bit8_1<>$fe) or
  135. (TestGlobal.AsBits.bit15_9<>$7f) then
  136. halt(7);
  137. // writeln(' Expected : $FFFD, Got : $',IntToHex(TestGlobal.AsWord,4));
  138. end.