/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "literal.h"

#include <cassert>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <cstring>

#define HEX_DIGIT_BITS 4

/* The PLUS_ONE values are used because normal IEEE floats have an implicit
 * leading one, so they have an additional bit of precision. */

#define F32_SIGN_SHIFT 31
#define F32_SIG_BITS 23
#define F32_SIG_MASK 0x7fffff
#define F32_SIG_PLUS_ONE_BITS 24
#define F32_SIG_PLUS_ONE_MASK 0xffffff
#define F32_EXP_MASK 0xff
#define F32_MIN_EXP -127
#define F32_MAX_EXP 128
#define F32_EXP_BIAS 127
#define F32_QUIET_NAN_TAG 0x400000

#define F64_SIGN_SHIFT 63
#define F64_SIG_BITS 52
#define F64_SIG_MASK 0xfffffffffffffULL
#define F64_SIG_PLUS_ONE_BITS 53
#define F64_SIG_PLUS_ONE_MASK 0x1fffffffffffffULL
#define F64_EXP_MASK 0x7ff
#define F64_MIN_EXP -1023
#define F64_MAX_EXP 1024
#define F64_EXP_BIAS 1023
#define F64_QUIET_NAN_TAG 0x8000000000000ULL

namespace wabt {

static const char s_hex_digits[] = "0123456789abcdef";

Result parse_hexdigit(char c, uint32_t* out) {
  if (static_cast<unsigned int>(c - '0') <= 9) {
    *out = c - '0';
    return Result::Ok;
  } else if (static_cast<unsigned int>(c - 'a') <= 6) {
    *out = 10 + (c - 'a');
    return Result::Ok;
  } else if (static_cast<unsigned int>(c - 'A') <= 6) {
    *out = 10 + (c - 'A');
    return Result::Ok;
  }
  return Result::Error;
}

/* return 1 if the non-NULL-terminated string starting with |start| and ending
 with |end| starts with the NULL-terminated string |prefix|. */
static bool string_starts_with(const char* start,
                               const char* end,
                               const char* prefix) {
  while (start < end && *prefix) {
    if (*start != *prefix)
      return false;
    start++;
    prefix++;
  }
  return *prefix == 0;
}

Result parse_uint64(const char* s, const char* end, uint64_t* out) {
  if (s == end)
    return Result::Error;
  uint64_t value = 0;
  if (*s == '0' && s + 1 < end && s[1] == 'x') {
    s += 2;
    if (s == end)
      return Result::Error;
    for (; s < end; ++s) {
      uint32_t digit;
      if (WABT_FAILED(parse_hexdigit(*s, &digit)))
        return Result::Error;
      uint64_t old_value = value;
      value = value * 16 + digit;
      /* check for overflow */
      if (old_value > value)
        return Result::Error;
    }
  } else {
    for (; s < end; ++s) {
      uint32_t digit = (*s - '0');
      if (digit > 9)
        return Result::Error;
      uint64_t old_value = value;
      value = value * 10 + digit;
      /* check for overflow */
      if (old_value > value)
        return Result::Error;
    }
  }
  if (s != end)
    return Result::Error;
  *out = value;
  return Result::Ok;
}

Result parse_int64(const char* s,
                   const char* end,
                   uint64_t* out,
                   ParseIntType parse_type) {
  bool has_sign = false;
  if (*s == '-' || *s == '+') {
    if (parse_type == ParseIntType::UnsignedOnly)
      return Result::Error;
    if (*s == '-')
      has_sign = true;
    s++;
  }
  uint64_t value = 0;
  Result result = parse_uint64(s, end, &value);
  if (has_sign) {
    /* abs(INT64_MIN) == INT64_MAX + 1 */
    if (value > static_cast<uint64_t>(INT64_MAX) + 1)
      return Result::Error;
    value = UINT64_MAX - value + 1;
  }
  *out = value;
  return result;
}

Result parse_int32(const char* s,
                   const char* end,
                   uint32_t* out,
                   ParseIntType parse_type) {
  uint64_t value;
  bool has_sign = false;
  if (*s == '-' || *s == '+') {
    if (parse_type == ParseIntType::UnsignedOnly)
      return Result::Error;
    if (*s == '-')
      has_sign = true;
    s++;
  }
  if (WABT_FAILED(parse_uint64(s, end, &value)))
    return Result::Error;

  if (has_sign) {
    /* abs(INT32_MIN) == INT32_MAX + 1 */
    if (value > static_cast<uint64_t>(INT32_MAX) + 1)
      return Result::Error;
    value = UINT32_MAX - value + 1;
  } else {
    if (value > static_cast<uint64_t>(UINT32_MAX))
      return Result::Error;
  }
  *out = static_cast<uint32_t>(value);
  return Result::Ok;
}

/* floats */
static uint32_t make_float(bool sign, int exp, uint32_t sig) {
  assert(exp >= F32_MIN_EXP && exp <= F32_MAX_EXP);
  assert(sig <= F32_SIG_MASK);
  return (static_cast<uint32_t>(sign) << F32_SIGN_SHIFT) |
         (static_cast<uint32_t>(exp + F32_EXP_BIAS) << F32_SIG_BITS) | sig;
}

static uint32_t shift_float_and_round_to_nearest(uint32_t significand,
                                                 int shift) {
  assert(shift > 0);
  /* round ties to even */
  if (significand & (1U << shift))
    significand += 1U << (shift - 1);
  significand >>= shift;
  return significand;
}

static Result parse_float_nan(const char* s,
                              const char* end,
                              uint32_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "nan"));
  s += 3;

  uint32_t tag;
  if (s != end) {
    tag = 0;
    assert(string_starts_with(s, end, ":0x"));
    s += 3;

    for (; s < end; ++s) {
      uint32_t digit;
      if (WABT_FAILED(parse_hexdigit(*s, &digit)))
        return Result::Error;
      tag = tag * 16 + digit;
      /* check for overflow */
      if (tag > F32_SIG_MASK)
        return Result::Error;
    }

    /* NaN cannot have a zero tag, that is reserved for infinity */
    if (tag == 0)
      return Result::Error;
  } else {
    tag = F32_QUIET_NAN_TAG;
  }

  *out_bits = make_float(is_neg, F32_MAX_EXP, tag);
  return Result::Ok;
}

static void parse_float_hex(const char* s,
                            const char* end,
                            uint32_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "0x"));
  s += 2;

  /* loop over the significand; everything up to the 'p'.
   this code is a bit nasty because we want to support extra zeroes anywhere
   without having to use many significand bits.
   e.g.
   0x00000001.0p0 => significand = 1, significand_exponent = 0
   0x10000000.0p0 => significand = 1, significand_exponent = 28
   0x0.000001p0 => significand = 1, significand_exponent = -24
   */
  bool seen_dot = false;
  uint32_t significand = 0;
  /* how much to shift |significand| if a non-zero value is appended */
  int significand_shift = 0;
  int significand_bits = 0;     /* bits of |significand| */
  int significand_exponent = 0; /* exponent adjustment due to dot placement */
  for (; s < end; ++s) {
    uint32_t digit;
    if (*s == '.') {
      if (significand != 0)
        significand_exponent += significand_shift;
      significand_shift = 0;
      seen_dot = true;
      continue;
    } else if (WABT_FAILED(parse_hexdigit(*s, &digit))) {
      break;
    }
    significand_shift += HEX_DIGIT_BITS;
    if (digit != 0 && (significand == 0 ||
                       significand_bits + significand_shift <=
                           F32_SIG_BITS + 1 + HEX_DIGIT_BITS)) {
      if (significand != 0)
        significand <<= significand_shift;
      if (seen_dot)
        significand_exponent -= significand_shift;
      significand += digit;
      significand_shift = 0;
      significand_bits += HEX_DIGIT_BITS;
    }
  }

  if (!seen_dot)
    significand_exponent += significand_shift;

  if (significand == 0) {
    /* 0 or -0 */
    *out_bits = make_float(is_neg, F32_MIN_EXP, 0);
    return;
  }

  int exponent = 0;
  bool exponent_is_neg = false;
  if (s < end) {
    assert(*s == 'p');
    s++;
    /* exponent is always positive, but significand_exponent is signed.
     significand_exponent_add is negated if exponent will be negative, so it  can
     be easily summed to see if the exponent is too large (see below) */
    int significand_exponent_add = 0;
    if (*s == '-') {
      exponent_is_neg = true;
      significand_exponent_add = -significand_exponent;
      s++;
    } else if (*s == '+') {
      s++;
      significand_exponent_add = significand_exponent;
    }

    for (; s < end; ++s) {
      uint32_t digit = (*s - '0');
      assert(digit <= 9);
      exponent = exponent * 10 + digit;
      if (exponent + significand_exponent_add >= F32_MAX_EXP)
        break;
    }
  }

  if (exponent_is_neg)
    exponent = -exponent;

  significand_bits = sizeof(uint32_t) * 8 - wabt_clz_u32(significand);
  /* -1 for the implicit 1 bit of the significand */
  exponent += significand_exponent + significand_bits - 1;

  if (exponent >= F32_MAX_EXP) {
    /* inf or -inf */
    *out_bits = make_float(is_neg, F32_MAX_EXP, 0);
  } else if (exponent <= F32_MIN_EXP) {
    /* maybe subnormal */
    if (significand_bits > F32_SIG_BITS) {
      significand = shift_float_and_round_to_nearest(
          significand, significand_bits - F32_SIG_BITS);
    } else if (significand_bits < F32_SIG_BITS) {
      significand <<= (F32_SIG_BITS - significand_bits);
    }

    int shift = F32_MIN_EXP - exponent;
    if (shift < F32_SIG_BITS) {
      if (shift) {
        significand =
            shift_float_and_round_to_nearest(significand, shift) & F32_SIG_MASK;
      }
      exponent = F32_MIN_EXP;

      if (significand != 0) {
        *out_bits = make_float(is_neg, exponent, significand);
        return;
      }
    }

    /* not subnormal, too small; return 0 or -0 */
    *out_bits = make_float(is_neg, F32_MIN_EXP, 0);
  } else {
    /* normal value */
    if (significand_bits > F32_SIG_PLUS_ONE_BITS) {
      significand = shift_float_and_round_to_nearest(
          significand, significand_bits - F32_SIG_PLUS_ONE_BITS);
      if (significand > F32_SIG_PLUS_ONE_MASK)
        exponent++;
    } else if (significand_bits < F32_SIG_PLUS_ONE_BITS) {
      significand <<= (F32_SIG_PLUS_ONE_BITS - significand_bits);
    }

    *out_bits = make_float(is_neg, exponent, significand & F32_SIG_MASK);
  }
}

static void parse_float_infinity(const char* s,
                                 const char* end,
                                 uint32_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "inf"));
  *out_bits = make_float(is_neg, F32_MAX_EXP, 0);
}

Result parse_float(LiteralType literal_type,
                   const char* s,
                   const char* end,
                   uint32_t* out_bits) {
#if COMPILER_IS_MSVC
  if (literal_type == LiteralType::Int && string_starts_with(s, end, "0x"))
  {
    // Some MSVC crt implementation of strtof doesn't support hex strings
    literal_type = LiteralType::Hexfloat;
  }
#endif
  switch (literal_type) {
    case LiteralType::Int:
    case LiteralType::Float: {
      errno = 0;
      char* endptr;
      float value;
      value = strtof(s, &endptr);
      if (endptr != end ||
          ((value == 0 || value == HUGE_VALF || value == -HUGE_VALF) &&
           errno != 0))
        return Result::Error;

      memcpy(out_bits, &value, sizeof(value));
      return Result::Ok;
    }

    case LiteralType::Hexfloat:
      parse_float_hex(s, end, out_bits);
      return Result::Ok;

    case LiteralType::Infinity:
      parse_float_infinity(s, end, out_bits);
      return Result::Ok;

    case LiteralType::Nan:
      return parse_float_nan(s, end, out_bits);

    default:
      assert(0);
      return Result::Error;
  }
}

void write_float_hex(char* out, size_t size, uint32_t bits) {
  /* 1234567890123456 */
  /* -0x#.######p-### */
  /* -nan:0x###### */
  /* -inf */
  char buffer[WABT_MAX_FLOAT_HEX];
  char* p = buffer;
  bool is_neg = (bits >> F32_SIGN_SHIFT);
  int exp = ((bits >> F32_SIG_BITS) & F32_EXP_MASK) - F32_EXP_BIAS;
  uint32_t sig = bits & F32_SIG_MASK;

  if (is_neg)
    *p++ = '-';
  if (exp == F32_MAX_EXP) {
    /* infinity or nan */
    if (sig == 0) {
      strcpy(p, "inf");
      p += 3;
    } else {
      strcpy(p, "nan");
      p += 3;
      if (sig != F32_QUIET_NAN_TAG) {
        strcpy(p, ":0x");
        p += 3;
        /* skip leading zeroes */
        int num_nybbles = sizeof(uint32_t) * 8 / 4;
        while ((sig & 0xf0000000) == 0) {
          sig <<= 4;
          num_nybbles--;
        }
        while (num_nybbles) {
          uint32_t nybble = (sig >> (sizeof(uint32_t) * 8 - 4)) & 0xf;
          *p++ = s_hex_digits[nybble];
          sig <<= 4;
          --num_nybbles;
        }
      }
    }
  } else {
    bool is_zero = sig == 0 && exp == F32_MIN_EXP;
    strcpy(p, "0x");
    p += 2;
    *p++ = is_zero ? '0' : '1';

    /* shift sig up so the top 4-bits are at the top of the uint32 */
    sig <<= sizeof(uint32_t) * 8 - F32_SIG_BITS;

    if (sig) {
      if (exp == F32_MIN_EXP) {
        /* subnormal; shift the significand up, and shift out the implicit 1 */
        uint32_t leading_zeroes = wabt_clz_u32(sig);
        if (leading_zeroes < 31)
          sig <<= leading_zeroes + 1;
        else
          sig = 0;
        exp -= leading_zeroes;
      }

      *p++ = '.';
      while (sig) {
        uint32_t nybble = (sig >> (sizeof(uint32_t) * 8 - 4)) & 0xf;
        *p++ = s_hex_digits[nybble];
        sig <<= 4;
      }
    }
    *p++ = 'p';
    if (is_zero) {
      strcpy(p, "+0");
      p += 2;
    } else {
      if (exp < 0) {
        *p++ = '-';
        exp = -exp;
      } else {
        *p++ = '+';
      }
      if (exp >= 100)
        *p++ = '1';
      if (exp >= 10)
        *p++ = '0' + (exp / 10) % 10;
      *p++ = '0' + exp % 10;
    }
  }

  size_t len = p - buffer;
  if (len >= size)
    len = size - 1;
  memcpy(out, buffer, len);
  out[len] = '\0';
}

/* doubles */
static uint64_t make_double(bool sign, int exp, uint64_t sig) {
  assert(exp >= F64_MIN_EXP && exp <= F64_MAX_EXP);
  assert(sig <= F64_SIG_MASK);
  return (static_cast<uint64_t>(sign) << F64_SIGN_SHIFT) |
         (static_cast<uint64_t>(exp + F64_EXP_BIAS) << F64_SIG_BITS) | sig;
}

static uint64_t shift_double_and_round_to_nearest(uint64_t significand,
                                                  int shift) {
  assert(shift > 0);
  /* round ties to even */
  if (significand & (static_cast<uint64_t>(1) << shift))
    significand += static_cast<uint64_t>(1) << (shift - 1);
  significand >>= shift;
  return significand;
}

static Result parse_double_nan(const char* s,
                               const char* end,
                               uint64_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "nan"));
  s += 3;

  uint64_t tag;
  if (s != end) {
    tag = 0;
    if (!string_starts_with(s, end, ":0x"))
      return Result::Error;
    s += 3;

    for (; s < end; ++s) {
      uint32_t digit;
      if (WABT_FAILED(parse_hexdigit(*s, &digit)))
        return Result::Error;
      tag = tag * 16 + digit;
      /* check for overflow */
      if (tag > F64_SIG_MASK)
        return Result::Error;
    }

    /* NaN cannot have a zero tag, that is reserved for infinity */
    if (tag == 0)
      return Result::Error;
  } else {
    tag = F64_QUIET_NAN_TAG;
  }

  *out_bits = make_double(is_neg, F64_MAX_EXP, tag);
  return Result::Ok;
}

static void parse_double_hex(const char* s,
                             const char* end,
                             uint64_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "0x"));
  s += 2;

  /* see the similar comment in parse_float_hex */
  bool seen_dot = false;
  uint64_t significand = 0;
  /* how much to shift |significand| if a non-zero value is appended */
  int significand_shift = 0;
  int significand_bits = 0;     /* bits of |significand| */
  int significand_exponent = 0; /* exponent adjustment due to dot placement */
  for (; s < end; ++s) {
    uint32_t digit;
    if (*s == '.') {
      if (significand != 0)
        significand_exponent += significand_shift;
      significand_shift = 0;
      seen_dot = true;
      continue;
    } else if (WABT_FAILED(parse_hexdigit(*s, &digit))) {
      break;
    }
    significand_shift += HEX_DIGIT_BITS;
    if (digit != 0 && (significand == 0 ||
                       significand_bits + significand_shift <=
                           F64_SIG_BITS + 1 + HEX_DIGIT_BITS)) {
      if (significand != 0)
        significand <<= significand_shift;
      if (seen_dot)
        significand_exponent -= significand_shift;
      significand += digit;
      significand_shift = 0;
      significand_bits += HEX_DIGIT_BITS;
    }
  }

  if (!seen_dot)
    significand_exponent += significand_shift;

  if (significand == 0) {
    /* 0 or -0 */
    *out_bits = make_double(is_neg, F64_MIN_EXP, 0);
    return;
  }

  int exponent = 0;
  bool exponent_is_neg = false;
  if (s < end) {
    assert(*s == 'p');
    s++;

    /* exponent is always positive, but significand_exponent is signed.
     significand_exponent_add is negated if exponent will be negative, so it  can
     be easily summed to see if the exponent is too large (see below) */
    int significand_exponent_add = 0;
    if (*s == '-') {
      exponent_is_neg = true;
      significand_exponent_add = -significand_exponent;
      s++;
    } else if (*s == '+') {
      s++;
      significand_exponent_add = significand_exponent;
    }

    for (; s < end; ++s) {
      uint32_t digit = (*s - '0');
      assert(digit <= 9);
      exponent = exponent * 10 + digit;
      if (exponent + significand_exponent_add >= F64_MAX_EXP)
        break;
    }
  }

  if (exponent_is_neg)
    exponent = -exponent;

  significand_bits = sizeof(uint64_t) * 8 - wabt_clz_u64(significand);
  /* -1 for the implicit 1 bit of the significand */
  exponent += significand_exponent + significand_bits - 1;

  if (exponent >= F64_MAX_EXP) {
    /* inf or -inf */
    *out_bits = make_double(is_neg, F64_MAX_EXP, 0);
  } else if (exponent <= F64_MIN_EXP) {
    /* maybe subnormal */
    if (significand_bits > F64_SIG_BITS) {
      significand = shift_double_and_round_to_nearest(
          significand, significand_bits - F64_SIG_BITS);
    } else if (significand_bits < F64_SIG_BITS) {
      significand <<= (F64_SIG_BITS - significand_bits);
    }

    int shift = F64_MIN_EXP - exponent;
    if (shift < F64_SIG_BITS) {
      if (shift) {
        significand = shift_double_and_round_to_nearest(significand, shift) &
                      F64_SIG_MASK;
      }
      exponent = F64_MIN_EXP;

      if (significand != 0) {
        *out_bits = make_double(is_neg, exponent, significand);
        return;
      }
    }

    /* not subnormal, too small; return 0 or -0 */
    *out_bits = make_double(is_neg, F64_MIN_EXP, 0);
  } else {
    /* normal value */
    if (significand_bits > F64_SIG_PLUS_ONE_BITS) {
      significand = shift_double_and_round_to_nearest(
          significand, significand_bits - F64_SIG_PLUS_ONE_BITS);
      if (significand > F64_SIG_PLUS_ONE_MASK)
        exponent++;
    } else if (significand_bits < F64_SIG_PLUS_ONE_BITS) {
      significand <<= (F64_SIG_PLUS_ONE_BITS - significand_bits);
    }

    *out_bits = make_double(is_neg, exponent, significand & F64_SIG_MASK);
  }
}

static void parse_double_infinity(const char* s,
                                  const char* end,
                                  uint64_t* out_bits) {
  bool is_neg = false;
  if (*s == '-') {
    is_neg = true;
    s++;
  } else if (*s == '+') {
    s++;
  }
  assert(string_starts_with(s, end, "inf"));
  *out_bits = make_double(is_neg, F64_MAX_EXP, 0);
}

Result parse_double(LiteralType literal_type,
                    const char* s,
                    const char* end,
                    uint64_t* out_bits) {

#if COMPILER_IS_MSVC
  if (literal_type == LiteralType::Int && string_starts_with(s, end, "0x"))
  {
    // Some MSVC crt implementation of strtod doesn't support hex strings
    literal_type = LiteralType::Hexfloat;
  }
#endif
  switch (literal_type) {
    case LiteralType::Int:
    case LiteralType::Float: {
      errno = 0;
      char* endptr;
      double value;
      value = strtod(s, &endptr);
      if (endptr != end ||
          ((value == 0 || value == HUGE_VAL || value == -HUGE_VAL) &&
           errno != 0))
        return Result::Error;

      memcpy(out_bits, &value, sizeof(value));
      return Result::Ok;
    }

    case LiteralType::Hexfloat:
      parse_double_hex(s, end, out_bits);
      return Result::Ok;

    case LiteralType::Infinity:
      parse_double_infinity(s, end, out_bits);
      return Result::Ok;

    case LiteralType::Nan:
      return parse_double_nan(s, end, out_bits);

    default:
      assert(0);
      return Result::Error;
  }
}

void write_double_hex(char* out, size_t size, uint64_t bits) {
  /* 123456789012345678901234 */
  /* -0x#.#############p-#### */
  /* -nan:0x############# */
  /* -inf */
  char buffer[WABT_MAX_DOUBLE_HEX];
  char* p = buffer;
  bool is_neg = (bits >> F64_SIGN_SHIFT);
  int exp = ((bits >> F64_SIG_BITS) & F64_EXP_MASK) - F64_EXP_BIAS;
  uint64_t sig = bits & F64_SIG_MASK;

  if (is_neg)
    *p++ = '-';
  if (exp == F64_MAX_EXP) {
    /* infinity or nan */
    if (sig == 0) {
      strcpy(p, "inf");
      p += 3;
    } else {
      strcpy(p, "nan");
      p += 3;
      if (sig != F64_QUIET_NAN_TAG) {
        strcpy(p, ":0x");
        p += 3;
        /* skip leading zeroes */
        int num_nybbles = sizeof(uint64_t) * 8 / 4;
        while ((sig & 0xf000000000000000ULL) == 0) {
          sig <<= 4;
          num_nybbles--;
        }
        while (num_nybbles) {
          uint32_t nybble = (sig >> (sizeof(uint64_t) * 8 - 4)) & 0xf;
          *p++ = s_hex_digits[nybble];
          sig <<= 4;
          --num_nybbles;
        }
      }
    }
  } else {
    bool is_zero = sig == 0 && exp == F64_MIN_EXP;
    strcpy(p, "0x");
    p += 2;
    *p++ = is_zero ? '0' : '1';

    /* shift sig up so the top 4-bits are at the top of the uint32 */
    sig <<= sizeof(uint64_t) * 8 - F64_SIG_BITS;

    if (sig) {
      if (exp == F64_MIN_EXP) {
        /* subnormal; shift the significand up, and shift out the implicit 1 */
        uint32_t leading_zeroes = wabt_clz_u64(sig);
        if (leading_zeroes < 63)
          sig <<= leading_zeroes + 1;
        else
          sig = 0;
        exp -= leading_zeroes;
      }

      *p++ = '.';
      while (sig) {
        uint32_t nybble = (sig >> (sizeof(uint64_t) * 8 - 4)) & 0xf;
        *p++ = s_hex_digits[nybble];
        sig <<= 4;
      }
    }
    *p++ = 'p';
    if (is_zero) {
      strcpy(p, "+0");
      p += 2;
    } else {
      if (exp < 0) {
        *p++ = '-';
        exp = -exp;
      } else {
        *p++ = '+';
      }
      if (exp >= 1000)
        *p++ = '1';
      if (exp >= 100)
        *p++ = '0' + (exp / 100) % 10;
      if (exp >= 10)
        *p++ = '0' + (exp / 10) % 10;
      *p++ = '0' + exp % 10;
    }
  }

  size_t len = p - buffer;
  if (len >= size)
    len = size - 1;
  memcpy(out, buffer, len);
  out[len] = '\0';
}

}  // namespace wabt
