append_aot_to_wasm.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2019 Intel Corporation. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. """
  7. It is used to append a .aot to a .wasm as a custom section.
  8. The custom section name is "aot".
  9. e.g.
  10. $ python3 append_aot_to_wasm.py --wasm quicksort.wasm --aot quicksort.aot --output quicksort.aot.wasm
  11. """
  12. import argparse
  13. from pathlib import Path
  14. def leb128_encode_uint(value: int) -> bytes:
  15. """
  16. encode unsigned int into a leb128 bytes
  17. """
  18. binary = []
  19. while value != 0:
  20. lower_7_bits = value & 0x7F
  21. value >>= 7
  22. if value != 0:
  23. current_byte = 0x80 | lower_7_bits
  24. else:
  25. current_byte = 0x00 | lower_7_bits
  26. binary.append(current_byte)
  27. return bytes(binary)
  28. def leb128_decode_uint(binary: bytes) -> (int, int):
  29. """
  30. decode binary unsigned from a leb128 bytes
  31. """
  32. result = 0
  33. shift = 0
  34. for i, b in enumerate(binary):
  35. lower_7_bits = b & 0x7F
  36. result |= lower_7_bits << shift
  37. highest_bit = b & 0x80
  38. if not highest_bit:
  39. break
  40. shift += 7
  41. return i + 1, result
  42. def is_aligned(n: int, alignment: int):
  43. return (n & (alignment - 1)) == 0
  44. def align_up(n: int, alignment: int):
  45. return n + (alignment - 1) & ~(alignment - 1)
  46. def present_as_vector(content: bytes) -> bytes:
  47. v_l = len(content)
  48. v_bin = leb128_encode_uint(v_l) if v_l else b"\x00"
  49. return v_bin + content
  50. def calc_padding(
  51. alignment: int, name_bin_len: int, content_len: int, start_pos: int
  52. ) -> bytes:
  53. for padding in range(alignment * 2):
  54. padding_bin = present_as_vector(b"\x00" * padding)
  55. section_length = name_bin_len + len(padding_bin) + content_len
  56. section_length_bin = leb128_encode_uint(section_length)
  57. pos = start_pos + 1 + len(section_length_bin) + name_bin_len + len(padding_bin)
  58. if is_aligned(pos, alignment):
  59. return padding_bin
  60. def build_content(content: bytes, pos: int, adding: bytes) -> (int, bytes):
  61. return pos + len(adding), content + adding
  62. def create_custom_section_aligned(
  63. start_pos: int, name: str, content: bytes, alignment: int = 4
  64. ) -> bytes:
  65. """
  66. be sure the section_content starts at a X alignment position
  67. 1B
  68. | \x00 | length | name vec | padding vec | content |
  69. ^ ^
  70. | |
  71. start address aligned address
  72. """
  73. name_bin = present_as_vector(name.encode("ascii"))
  74. padding_bin = calc_padding(alignment, len(name_bin), len(content), start_pos)
  75. full_content_bin = b""
  76. pos = start_pos
  77. # custom section id 0
  78. pos, full_content_bin = build_content(full_content_bin, pos, b"\x00")
  79. # custom section length
  80. section_length = len(name_bin) + len(padding_bin) + len(content)
  81. section_length_bin = leb128_encode_uint(section_length)
  82. pos, full_content_bin = build_content(full_content_bin, pos, section_length_bin)
  83. # custom section name
  84. pos, full_content_bin = build_content(full_content_bin, pos, name_bin)
  85. # padding
  86. pos, full_content_bin = build_content(full_content_bin, pos, padding_bin)
  87. assert is_aligned(pos, alignment), f"{pos} is not aligned to {alignment}"
  88. print(f"append .aot @ offset {pos}(0x{pos:X})")
  89. _, full_content_bin = build_content(full_content_bin, pos, content)
  90. return full_content_bin
  91. def main(wasm_file: str, aot_file: str, output: str, ver_str: str) -> None:
  92. cwd = Path.cwd()
  93. wasm_file = cwd.joinpath(wasm_file).resolve()
  94. aot_file = cwd.joinpath(aot_file).resolve()
  95. output = cwd.joinpath(output).resolve()
  96. assert wasm_file.exists()
  97. assert aot_file.exists()
  98. output.unlink(missing_ok=True)
  99. # read aot content
  100. with open(aot_file, "rb") as f:
  101. aot_content = f.read()
  102. # append to .wasm
  103. with open(wasm_file, "rb") as f_in, open(output, "wb") as f_out:
  104. wasm_content = f_in.read(1024)
  105. while wasm_content:
  106. f_out.write(wasm_content)
  107. wasm_content = f_in.read(1024)
  108. section_name = f"wamr-aot-{ver_str}" if ver_str else "wamr-aot"
  109. f_out.write(
  110. create_custom_section_aligned(f_out.tell(), section_name, aot_content, 4)
  111. )
  112. print(f"{wasm_file.name} + {aot_file.name} ==> {output}")
  113. if __name__ == "__main__":
  114. argparse = argparse.ArgumentParser()
  115. argparse.add_argument("--wasm", help="a .wasm")
  116. argparse.add_argument("--aot", help="a .aot")
  117. argparse.add_argument(
  118. "--ver-str", help="a version string will be used to construct section name"
  119. )
  120. argparse.add_argument("-o", "--output", help="the output, still be a .wasm")
  121. args = argparse.parse_args()
  122. main(args.wasm, args.aot, args.output, args.ver_str)