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 nothrow
112     {
113         _val = x;
114     }
115 
116     pragma(inline, true)
117     int8_t opBinary(string op, T)(T other) nothrow
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) nothrow
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: "-")() nothrow
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 nothrow
153     {
154         _val = x;
155     }
156 
157     pragma(inline, true)
158     int16_t opBinary(string op, T)(T other) nothrow
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) nothrow
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: "-")() nothrow
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 nothrow:
187 /// Get a ushort value from an unsigned byte array
188 /** @param buf Pointer to source byte, may be unaligned
189  *  @return A 16 bit unsigned integer
190  *  The input is read in little-endian byte order.
191  */
192 ushort le_to_u16(const(ubyte)* buf)
193 {
194     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
195         return *(cast(uint16_u *) buf);
196     else
197         return cast(ushort) buf[0] | (cast(ushort) buf[1] << 8);
198 }
199 
200 /// Get a uint value from an unsigned byte array
201 /** @param buf Pointer to source byte array, may be unaligned
202  *  @return A 32 bit unsigned integer
203  *  The input is read in little-endian byte order.
204  */
205 uint le_to_u32(const(ubyte)* buf)
206 {
207     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
208         return *(cast(uint32_u *) buf);
209     else
210         return (cast(uint) buf[0] |
211             (cast(uint) buf[1] << 8) |
212             (cast(uint) buf[2] << 16) |
213             (cast(uint) buf[3] << 24));
214 }
215 
216 /// Get a ulong value from an unsigned byte array
217 /** @param buf Pointer to source byte array, may be unaligned
218  *  @return A 64 bit unsigned integer
219  *  The input is read in little-endian byte order.
220  */
221 ulong le_to_u64(const(ubyte)* buf)
222 {
223     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
224         return *(cast(uint64_u *) buf);
225     else
226         return (cast(ulong) buf[0] |
227             (cast(ulong) buf[1] << 8) |
228             (cast(ulong) buf[2] << 16) |
229             (cast(ulong) buf[3] << 24) |
230             (cast(ulong) buf[4] << 32) |
231             (cast(ulong) buf[5] << 40) |
232             (cast(ulong) buf[6] << 48) |
233             (cast(ulong) buf[7] << 56));
234 }
235 
236 /// Store a ushort value in little-endian byte order
237 /** @param val The value to store
238  *  @param buf Where to store it (may be unaligned)
239  */
240 void u16_to_le(ushort val, ubyte* buf)
241 {
242     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
243         *(cast(uint16_u *) buf) = val;
244     else{
245         buf[0] = val & 0xff;
246         buf[1] = (val >> 8) & 0xff;
247     }
248 }
249 
250 /// Store a uint value in little-endian byte order
251 /** @param val The value to store
252  *  @param buf Where to store it (may be unaligned)
253  */
254 void u32_to_le(uint val, ubyte* buf)
255 {
256     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
257         *(cast(uint32_u *) buf) = val;
258     else{
259         buf[0] = val & 0xff;
260         buf[1] = (val >> 8) & 0xff;
261         buf[2] = (val >> 16) & 0xff;
262         buf[3] = (val >> 24) & 0xff;
263     }
264 }
265 
266 /// Store a ulong value in little-endian byte order
267 /** @param val The value to store
268  *  @param buf Where to store it (may be unaligned)
269  */
270 void u64_to_le(ulong val, ubyte* buf)
271 {
272     static if(HTS_LITTLE_ENDIAN && HTS_ALLOW_UNALIGNED)
273         *(cast(uint64_u *) buf) = val;
274     else{
275         buf[0] = val & 0xff;
276         buf[1] = (val >> 8) & 0xff;
277         buf[2] = (val >> 16) & 0xff;
278         buf[3] = (val >> 24) & 0xff;
279         buf[4] = (val >> 32) & 0xff;
280         buf[5] = (val >> 40) & 0xff;
281         buf[6] = (val >> 48) & 0xff;
282         buf[7] = (val >> 56) & 0xff;
283     }
284 }
285 
286 /* Signed values.  Grab the data as unsigned, then convert to signed without
287  * triggering undefined behaviour.  On any sensible platform, the conversion
288  * should optimise away to nothing.
289  */
290 
291 /// Get an int8_t value from an unsigned byte array
292 /** @param buf Pointer to source byte array, may be unaligned
293  *  @return A 8 bit signed integer
294  *  The input data is interpreted as 2's complement representation.
295  */
296 byte le_to_i8(const(ubyte)* buf)
297 {
298     return *buf < 0x80 ? cast(int8_t) *buf : -((int8_t(cast(byte)0xff) - cast(int8_t)*buf)) - int8_t(1);
299 }
300 
301 /// Get an short value from an unsigned byte array
302 /** @param buf Pointer to source byte array, may be unaligned
303  *  @return A 16 bit signed integer
304  *  The input data is interpreted as 2's complement representation in
305  *  little-endian byte order.
306  */
307 short le_to_i16(const(ubyte)* buf)
308 {
309     ushort v = le_to_u16(buf);
310     return v < 0x8000 ? cast(int16_t) v : -((int16_t(cast(short)0xffff) - v)) - cast(int16_t)1;
311 }
312 
313 /// Get an int value from an unsigned byte array
314 /** @param buf Pointer to source byte array, may be unaligned
315  *  @return A 32 bit signed integer
316  *  The input data is interpreted as 2's complement representation in
317  *  little-endian byte order.
318  */
319 int le_to_i32(const(ubyte)* buf)
320 {
321     uint v = le_to_u32(buf);
322     return v < 0x80000000U ? cast(int) v : -(cast(int) (0xffffffffU - v)) - 1;
323 }
324 
325 /// Get an long value from an unsigned byte array
326 /** @param buf Pointer to source byte array, may be unaligned
327  *  @return A 64 bit signed integer
328  *  The input data is interpreted as 2's complement representation in
329  *  little-endian byte order.
330  */
331 long le_to_i64(const(ubyte)* buf)
332 {
333     ulong v = le_to_u64(buf);
334     return (v < 0x8000000000000000UL
335             ? cast(long) v : -(cast(long) (0xffffffffffffffffUL - v)) - 1);
336 }
337 
338 // Converting the other way is easier as signed -> unsigned is well defined.
339 
340 /// Store a ushort value in little-endian byte order
341 /** @param val The value to store
342  *  @param buf Where to store it (may be unaligned)
343  */
344 void i16_to_le(short val, ubyte* buf)
345 {
346     u16_to_le(val, buf);
347 }
348 
349 /// Store a uint value in little-endian byte order
350 /** @param val The value to store
351  *  @param buf Where to store it (may be unaligned)
352  */
353 void i32_to_le(int val, ubyte* buf)
354 {
355     u32_to_le(val, buf);
356 }
357 
358 /// Store a ulong value in little-endian byte order
359 /** @param val The value to store
360  *  @param buf Where to store it (may be unaligned)
361  */
362 void i64_to_le(long val, ubyte* buf)
363 {
364     u64_to_le(val, buf);
365 }
366 
367 /* Floating point.  Assumptions:
368  *  Platform uses IEEE 754 format
369  *  sizeof(float) == sizeof(uint)
370  *  sizeof(double) == sizeof(ulong)
371  *  Endian-ness is the same for both floating point and integer
372  *  Type-punning via a union is allowed
373  */
374 
375 /// Get a float value from an unsigned byte array
376 /** @param buf Pointer to source byte array, may be unaligned
377  *  @return A 32 bit floating point value
378  *  The input is interpreted as an IEEE 754 format float in little-endian
379  *  byte order.
380  */
381 float le_to_float(const(ubyte)* buf)
382 {
383     union CONVERT 
384     {
385         uint u;
386         float   f;
387     }
388 
389     CONVERT convert;
390     convert.u = le_to_u32(buf);
391     return convert.f;
392 }
393 
394 /// Get a double value from an unsigned byte array
395 /** @param buf Pointer to source byte array, may be unaligned
396  *  @return A 64 bit floating point value
397  *  The input is interpreted as an IEEE 754 format double in little-endian
398  *  byte order.
399  */
400 double le_to_double(const(ubyte)* buf)
401 {
402     union CONVERT 
403     {
404         ulong u;
405         double   f;
406     }
407     CONVERT convert;
408     convert.u = le_to_u64(buf);
409     return convert.f;
410 }
411 
412 /// Store a float value in little-endian byte order
413 /** @param val The value to store
414  *  @param buf Where to store it (may be unaligned)
415  */
416 void float_to_le(float val, ubyte* buf)
417 {
418     union CONVERT 
419     {
420         uint u;
421         float f;
422     }
423     CONVERT convert;
424     convert.f = val;
425     u32_to_le(convert.u, buf);
426 }
427 
428 /// Store a double value in little-endian byte order
429 /** @param val The value to store
430  *  @param buf Where to store it (may be unaligned)
431  */
432 void double_to_le(double val, ubyte* buf)
433 {
434     union CONVERT 
435     {
436         ulong u;
437         double f;
438     }
439     CONVERT convert;
440     convert.f = val;
441     u64_to_le(convert.u, buf);
442 }
443 
444 /* HTS_ENDIAN_H */