1 /// @file hts_endian.h
2 /// Byte swapping and unaligned access functions.
3 /*
4    Copyright (C) 2017 Genome Research Ltd.
5 
6     Author: Rob Davies <rmd@sanger.ac.uk>
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 DEALINGS IN THE SOFTWARE.  */
25 module htslib.hts_endian;
26 
27 import core.stdc.config;
28 
29 @system:
30 @nogc:
31 
32 /*
33  * Compile-time endianness tests.
34  *
35  * Note that these tests may fail.  They should only be used to enable
36  * faster versions of endian-neutral implementations.  The endian-neutral
37  * version should always be available as a fall-back.
38  *
39  * See https://sourceforge.net/p/predef/wiki/Endianness/
40  */
41 
42 version(X86) enum HTS_x86 = true;
43 version(X86_64) enum HTS_x86 = true;
44 else enum HTS_x86 = false;
45 
46 /* Save typing as both endian and unaligned tests want to know about x86 */ /* x86 and x86_64 platform */
47 
48 /** @def HTS_LITTLE_ENDIAN
49  *  @brief Defined if platform is known to be little-endian
50  */
51 
52 version(LittleEndian) enum HTS_LITTLE_ENDIAN = true;
53 else enum HTS_LITTLE_ENDIAN = false;
54 
55 /** @def HTS_BIG_ENDIAN
56  *  @brief Defined if platform is known to be big-endian
57  */
58 
59 version(BigEndian) enum HTS_BIG_ENDIAN = true;
60 else enum HTS_BIG_ENDIAN = false;
61 
62 /** @def HTS_ENDIAN_NEUTRAL
63  *  @brief Define this to disable any endian-specific optimizations
64  */
65 
66 /* Disable all endian-specific code. */
67 version(HTS_ENDIAN_NEUTRAL)
68 {
69     enum HTS_LITTLE_ENDIAN = false;
70     enum HTS_BIG_ENDIAN = false;
71 }
72 
73 /** @def HTS_ALLOW_UNALIGNED
74  *  @brief Control use of unaligned memory access.
75  *
76  * Defining HTS_ALLOW_UNALIGNED=1 converts shift-and-or to simple casts on
77  * little-endian platforms that can tolerate unaligned access (notably Intel
78  * x86).
79  *
80  * Defining HTS_ALLOW_UNALIGNED=0 forces shift-and-or.
81  */
82 
83 static if(HTS_x86) enum HTS_ALLOW_UNALIGNED = true;
84 else enum HTS_ALLOW_UNALIGNED = false;
85 
86 // Consider using AX_CHECK_ALIGNED_ACCESS_REQUIRED in autoconf.
87 
88 // This prevents problems with gcc's vectoriser generating the wrong
89 // instructions for unaligned data.
90 
91 static if(HTS_ALLOW_UNALIGNED){
92     alias uint16_u = align(1) ushort;
93     alias uint32_u = align(1) uint;
94     alias uint64_u = align(1) c_ulong;
95 }else{
96     alias uint16_u = ushort;
97     alias uint32_u = uint;
98     alias uint64_u = c_ulong;    
99 }
100 
101 /// Basically just a byte
102 /// This workaround to avoid int promotion issues
103 /// implmentation direvied from this post on the dlang forums
104 /// https://forum.dlang.org/post/r0imk7$14b7$3@digitalmars.com
105 private struct int8_t
106 {
107     byte _val;
108     alias _val this;
109 
110     pragma(inline, true)
111     this(byte x) @nogc
112     {
113         _val = x;
114     }
115 
116     pragma(inline, true)
117     int8_t opBinary(string op, T)(T other)
118         if ((is(T == int8_t) || is(T == byte) || is(T == ubyte)))
119     {
120         return mixin("int8_t(cast(byte)(_val " ~ op ~ " cast(byte)other))");
121     }
122 
123     pragma(inline, true)
124     int8_t opBinaryRight(string op, T)(T other)
125         if ((is(T == int8_t) || is(T == byte) || is(T == ubyte)))
126     {
127         return mixin("int8_t(cast(byte)(_val " ~ op ~ " cast(byte)other))");
128     }
129 
130     pragma(inline, true)
131     int8_t opUnary(string op: "-")()
132     {
133         return (cast(int8_t) _val ^ cast(ubyte) 0xFF) + cast(ubyte) 1;
134     }
135 }
136 
137 unittest
138 {
139     assert(-int8_t(cast(byte) 8) == int8_t(cast(byte) -8));
140 }
141 
142 /// Basically just a short
143 /// This workaround to avoid int promotion issues
144 /// implmentation direvied from this post on the dlang forums
145 /// https://forum.dlang.org/post/r0imk7$14b7$3@digitalmars.com
146 private struct int16_t
147 {
148     short _val;
149     alias _val this;
150 
151     pragma(inline, true)
152     this(short x) @nogc
153     {
154         _val = x;
155     }
156 
157     pragma(inline, true)
158     int16_t opBinary(string op, T)(T other)
159         if ((is(T == int16_t) || is(T == short) || is(T == ushort)))
160     {
161         return mixin("int16_t(cast(short)(_val " ~ op ~ " cast(short)other))");
162     }
163 
164     pragma(inline, true)
165     int16_t opBinaryRight(string op, T)(T other)
166         if ((is(T == int16_t) || is(T == short) || is(T == ushort)))
167     {
168         return mixin("int16_t(cast(short)(_val " ~ op ~ " cast(short)other))");
169     }
170 
171     pragma(inline, true)
172     int16_t opUnary(string op: "-")()
173     {
174         return (cast(int16_t) _val ^ cast(ushort) 0xFFFF) + cast(ushort) 1;
175     }
176 }
177 
178 unittest
179 {
180     assert(-int16_t(cast(short) 8) == int16_t(cast(short) -8));
181 }
182 
183 pragma(inline, true):
184 @nogc:
185 @system:
186 /// Get a ushort value from an unsigned byte array
187 /** @param buf Pointer to source byte, may be unaligned
188  *  @return A 16 bit unsigned integer
189  *  The input is read in little-endian byte order.
190  */
191 ushort le_to_u16(const(ubyte)* buf)
192 {
193     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
194         return *(cast(uint16_u *) buf);
195     else
196         return cast(ushort) buf[0] | (cast(ushort) buf[1] << 8);
197 }
198 
199 /// Get a uint value from an unsigned byte array
200 /** @param buf Pointer to source byte array, may be unaligned
201  *  @return A 32 bit unsigned integer
202  *  The input is read in little-endian byte order.
203  */
204 uint le_to_u32(const(ubyte)* buf)
205 {
206     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
207         return *(cast(uint32_u *) buf);
208     else
209         return (cast(uint) buf[0] |
210             (cast(uint) buf[1] << 8) |
211             (cast(uint) buf[2] << 16) |
212             (cast(uint) buf[3] << 24));
213 }
214 
215 /// Get a ulong value from an unsigned byte array
216 /** @param buf Pointer to source byte array, may be unaligned
217  *  @return A 64 bit unsigned integer
218  *  The input is read in little-endian byte order.
219  */
220 ulong le_to_u64(const(ubyte)* buf)
221 {
222     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
223         return *(cast(uint64_u *) buf);
224     else
225         return (cast(ulong) buf[0] |
226             (cast(ulong) buf[1] << 8) |
227             (cast(ulong) buf[2] << 16) |
228             (cast(ulong) buf[3] << 24) |
229             (cast(ulong) buf[4] << 32) |
230             (cast(ulong) buf[5] << 40) |
231             (cast(ulong) buf[6] << 48) |
232             (cast(ulong) buf[7] << 56));
233 }
234 
235 /// Store a ushort value in little-endian byte order
236 /** @param val The value to store
237  *  @param buf Where to store it (may be unaligned)
238  */
239 void u16_to_le(ushort val, ubyte* buf)
240 {
241     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
242         *(cast(uint16_u *) buf) = val;
243     else{
244         buf[0] = val & 0xff;
245         buf[1] = (val >> 8) & 0xff;
246     }
247 }
248 
249 /// Store a uint value in little-endian byte order
250 /** @param val The value to store
251  *  @param buf Where to store it (may be unaligned)
252  */
253 void u32_to_le(uint val, ubyte* buf)
254 {
255     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
256         *(cast(uint32_u *) buf) = val;
257     else{
258         buf[0] = val & 0xff;
259         buf[1] = (val >> 8) & 0xff;
260         buf[2] = (val >> 16) & 0xff;
261         buf[3] = (val >> 24) & 0xff;
262     }
263 }
264 
265 /// Store a ulong value in little-endian byte order
266 /** @param val The value to store
267  *  @param buf Where to store it (may be unaligned)
268  */
269 void u64_to_le(ulong val, ubyte* buf)
270 {
271     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
272         *(cast(uint64_u *) buf) = val;
273     else{
274         buf[0] = val & 0xff;
275         buf[1] = (val >> 8) & 0xff;
276         buf[2] = (val >> 16) & 0xff;
277         buf[3] = (val >> 24) & 0xff;
278         buf[4] = (val >> 32) & 0xff;
279         buf[5] = (val >> 40) & 0xff;
280         buf[6] = (val >> 48) & 0xff;
281         buf[7] = (val >> 56) & 0xff;
282     }
283 }
284 
285 /* Signed values.  Grab the data as unsigned, then convert to signed without
286  * triggering undefined behaviour.  On any sensible platform, the conversion
287  * should optimise away to nothing.
288  */
289 
290 /// Get an int8_t value from an unsigned byte array
291 /** @param buf Pointer to source byte array, may be unaligned
292  *  @return A 8 bit signed integer
293  *  The input data is interpreted as 2's complement representation.
294  */
295 byte le_to_i8(const(ubyte)* buf)
296 {
297     return *buf < 0x80 ? cast(int8_t) *buf : -((int8_t(cast(byte)0xff) - cast(int8_t)*buf)) - int8_t(1);
298 }
299 
300 /// Get an short value from an unsigned byte array
301 /** @param buf Pointer to source byte array, may be unaligned
302  *  @return A 16 bit signed integer
303  *  The input data is interpreted as 2's complement representation in
304  *  little-endian byte order.
305  */
306 short le_to_i16(const(ubyte)* buf)
307 {
308     ushort v = le_to_u16(buf);
309     return v < 0x8000 ? cast(int16_t) v : -((int16_t(cast(short)0xffff) - v)) - cast(int16_t)1;
310 }
311 
312 /// Get an int value from an unsigned byte array
313 /** @param buf Pointer to source byte array, may be unaligned
314  *  @return A 32 bit signed integer
315  *  The input data is interpreted as 2's complement representation in
316  *  little-endian byte order.
317  */
318 int le_to_i32(const(ubyte)* buf)
319 {
320     uint v = le_to_u32(buf);
321     return v < 0x80000000U ? cast(int) v : -(cast(int) (0xffffffffU - v)) - 1;
322 }
323 
324 /// Get an long value from an unsigned byte array
325 /** @param buf Pointer to source byte array, may be unaligned
326  *  @return A 64 bit signed integer
327  *  The input data is interpreted as 2's complement representation in
328  *  little-endian byte order.
329  */
330 long le_to_i64(const(ubyte)* buf)
331 {
332     ulong v = le_to_u64(buf);
333     return (v < 0x8000000000000000UL
334             ? cast(long) v : -(cast(long) (0xffffffffffffffffUL - v)) - 1);
335 }
336 
337 // Converting the other way is easier as signed -> unsigned is well defined.
338 
339 /// Store a ushort value in little-endian byte order
340 /** @param val The value to store
341  *  @param buf Where to store it (may be unaligned)
342  */
343 void i16_to_le(short val, ubyte* buf)
344 {
345     u16_to_le(val, buf);
346 }
347 
348 /// Store a uint value in little-endian byte order
349 /** @param val The value to store
350  *  @param buf Where to store it (may be unaligned)
351  */
352 void i32_to_le(int val, ubyte* buf)
353 {
354     u32_to_le(val, buf);
355 }
356 
357 /// Store a ulong value in little-endian byte order
358 /** @param val The value to store
359  *  @param buf Where to store it (may be unaligned)
360  */
361 void i64_to_le(long val, ubyte* buf)
362 {
363     u64_to_le(val, buf);
364 }
365 
366 /* Floating point.  Assumptions:
367  *  Platform uses IEEE 754 format
368  *  sizeof(float) == sizeof(uint)
369  *  sizeof(double) == sizeof(ulong)
370  *  Endian-ness is the same for both floating point and integer
371  *  Type-punning via a union is allowed
372  */
373 
374 /// Get a float value from an unsigned byte array
375 /** @param buf Pointer to source byte array, may be unaligned
376  *  @return A 32 bit floating point value
377  *  The input is interpreted as an IEEE 754 format float in little-endian
378  *  byte order.
379  */
380 float le_to_float(const(ubyte)* buf)
381 {
382     union CONVERT 
383     {
384         uint u;
385         float   f;
386     }
387 
388     CONVERT convert;
389     convert.u = le_to_u32(buf);
390     return convert.f;
391 }
392 
393 /// Get a double value from an unsigned byte array
394 /** @param buf Pointer to source byte array, may be unaligned
395  *  @return A 64 bit floating point value
396  *  The input is interpreted as an IEEE 754 format double in little-endian
397  *  byte order.
398  */
399 double le_to_double(const(ubyte)* buf)
400 {
401     union CONVERT 
402     {
403         ulong u;
404         double   f;
405     }
406     CONVERT convert;
407     convert.u = le_to_u64(buf);
408     return convert.f;
409 }
410 
411 /// Store a float value in little-endian byte order
412 /** @param val The value to store
413  *  @param buf Where to store it (may be unaligned)
414  */
415 void float_to_le(float val, ubyte* buf)
416 {
417     union CONVERT 
418     {
419         uint u;
420         float f;
421     }
422     CONVERT convert;
423     convert.f = val;
424     u32_to_le(convert.u, buf);
425 }
426 
427 /// Store a double value in little-endian byte order
428 /** @param val The value to store
429  *  @param buf Where to store it (may be unaligned)
430  */
431 void double_to_le(double val, ubyte* buf)
432 {
433     union CONVERT 
434     {
435         ulong u;
436         double f;
437     }
438     CONVERT convert;
439     convert.f = val;
440     u64_to_le(convert.u, buf);
441 }
442 
443 /* HTS_ENDIAN_H */