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