generate_hash_macro_1.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. def generate_pw_tokenizer_65599_fixed_length_hash_macro(hash_length):
  2. """Generate macro that hashes a string literal using a modified x65599 hash.
  3. The macros generated by this function only operate on string literals.
  4. Since macros can only operate on fixed-length strings, the hash macro only
  5. hashes up to a fixed length, and characters beyond that length are ignored.
  6. To eliminate some collisions, the length of the string is hashed as if it
  7. were the first character.
  8. This hash is calculated with the following equation, where s is the string
  9. and k is the maximum hash length:
  10. H(s, k) = len(s) + 65599 * s[0] + 65599^2 * s[1] + ... + 65599^k * s[k-1]
  11. The hash algorithm is a modified version of the x65599 hash used by the SDBM
  12. open source project. This hash has the following differences from x65599:
  13. - Characters are only hashed up to a fixed maximum string length.
  14. - Characters are hashed in reverse order.
  15. - The string length is hashed as the first character in the string.
  16. The code generated by this function is intentionally sparse. Each character
  17. appears hash_length times per log message, so using fewer characters results
  18. in faster compilation times.
  19. Args:
  20. hash_length: maximum string size to hash; extra characters are ignored
  21. Returns:
  22. the macro header file as a string
  23. """
  24. first_hash_term = ('(uint32_t)(sizeof(str "") - 1 + '
  25. '/* The argument must be a string literal. */ \\\n')
  26. # Use this to add the aligned backslash at the end of the macro lines.
  27. line_format = '{{:<{}}}\\\n'.format(len(first_hash_term))
  28. lines = [
  29. FILE_HEADER.format(script=os.path.basename(__file__),
  30. hash_length=hash_length,
  31. year=datetime.date.today().year)
  32. ]
  33. lines.append(
  34. line_format.format('#define {}_{}_HASH(str)'.format(
  35. HASH_NAME.upper(), hash_length)))
  36. lines.append(' ' + first_hash_term) # add indendation and the macro line
  37. indent = ' ' * len(' (uint32_t)(')
  38. coefficient_format = '0x{coefficient:0>8x}u'
  39. # The string will have at least a null terminator
  40. lines.append(
  41. line_format.format('{}0x{:0>8x}u * (uint8_t)str[0] +'.format(
  42. indent, HASH_CONSTANT)))
  43. # Format string to use for the remaining terms.
  44. term_format = (
  45. '{indent}{coefficient} * '
  46. '(uint8_t)({index} < sizeof(str) ? str[{index}] : 0) +').format(
  47. indent=indent,
  48. coefficient=coefficient_format,
  49. index='{{index:>{}}}'.format(len(str(hash_length - 1))))
  50. for i in range(1, hash_length):
  51. coefficient = HASH_CONSTANT**(i + 1) % 2**32
  52. term = term_format.format(index=i, coefficient=coefficient)
  53. lines.append(line_format.format(term))
  54. # Remove the extra + and \ and add the closing )
  55. lines[-1] = lines[-1].rstrip(' +\\\n') + ')'
  56. lines.append('\n\n// clang-format on\n')
  57. return ''.join(lines)