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 */