summaryrefslogtreecommitdiff
path: root/tools/mpy_ld.py
diff options
context:
space:
mode:
authorAlessandro Gatti <a.gatti@frob.it>2024-06-23 08:18:04 +0200
committerDamien George <damien@micropython.org>2024-12-23 10:02:20 +1100
commit6760e00817ff6cb449134418411850fc577d0f9c (patch)
treec5d618a92a6504a48c58a8c5517387bf03a0c669 /tools/mpy_ld.py
parent136058496f05b4b2ab5e63829b301671dc8b9a59 (diff)
tools/mpy_ld.py: Add native modules support for RV32 code.
This commit adds support for RV32IMC native modules, as in embedding native code into a self-contained MPY module and and make its exported functions available to the MicroPython environment. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
Diffstat (limited to 'tools/mpy_ld.py')
-rwxr-xr-xtools/mpy_ld.py399
1 files changed, 395 insertions, 4 deletions
diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py
index c77c46d44..54295208f 100755
--- a/tools/mpy_ld.py
+++ b/tools/mpy_ld.py
@@ -47,6 +47,7 @@ MP_NATIVE_ARCH_ARMV7EMSP = 7
MP_NATIVE_ARCH_ARMV7EMDP = 8
MP_NATIVE_ARCH_XTENSA = 9
MP_NATIVE_ARCH_XTENSAWIN = 10
+MP_NATIVE_ARCH_RV32IMC = 11
MP_PERSISTENT_OBJ_STR = 5
MP_SCOPE_FLAG_VIPERRELOC = 0x10
MP_SCOPE_FLAG_VIPERRODATA = 0x20
@@ -56,6 +57,7 @@ MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET = 73
# ELF constants
R_386_32 = 1
+R_RISCV_32 = 1
R_X86_64_64 = 1
R_XTENSA_32 = 1
R_386_PC32 = 2
@@ -70,15 +72,57 @@ R_386_GOTOFF = 9
R_386_GOTPC = 10
R_ARM_THM_CALL = 10
R_XTENSA_ASM_EXPAND = 11
+R_RISCV_BRANCH = 16
+R_RISCV_JAL = 17
+R_RISCV_CALL = 18
+R_RISCV_CALL_PLT = 19
R_XTENSA_DIFF32 = 19
R_XTENSA_SLOT0_OP = 20
+R_RISCV_GOT_HI20 = 20
+R_RISCV_TLS_GD_HI20 = 22
+R_RISCV_PCREL_HI20 = 23
+R_RISCV_PCREL_LO12_I = 24
+R_RISCV_PCREL_LO12_S = 25
R_ARM_BASE_PREL = 25 # aka R_ARM_GOTPC
R_ARM_GOT_BREL = 26 # aka R_ARM_GOT32
R_ARM_THM_JUMP24 = 30
+R_RISCV_HI20 = 26
+R_RISCV_LO12_I = 27
+R_RISCV_LO12_S = 28
+R_RISCV_TPREL_HI20 = 29
+R_RISCV_TPREL_LO12_I = 30
+R_RISCV_TPREL_LO12_S = 31
+R_RISCV_TPREL_ADD = 32
+R_RISCV_ADD8 = 33
+R_RISCV_ADD16 = 34
+R_RISCV_ADD32 = 35
+R_RISCV_ADD64 = 36
+R_RISCV_SUB8 = 37
+R_RISCV_SUB16 = 38
+R_RISCV_SUB32 = 39
+R_RISCV_SUB64 = 40
+R_RISCV_GOT32_PCREL = 41
R_X86_64_GOTPCREL = 9
R_X86_64_REX_GOTPCRELX = 42
R_386_GOT32X = 43
+R_RISCV_ALIGN = 43
+R_RISCV_RVC_BRANCH = 44
+R_RISCV_RVC_JUMP = 45
+R_RISCV_RELAX = 51
+R_RISCV_SUB6 = 52
+R_RISCV_SET6 = 53
+R_RISCV_SET8 = 54
+R_RISCV_SET16 = 55
+R_RISCV_SET32 = 56
+R_RISCV_32_PCREL = 57
+R_RISCV_PLT32 = 59
R_XTENSA_PDIFF32 = 59
+R_RISCV_SET_ULEB128 = 60
+R_RISCV_SUB_ULEB128 = 61
+R_RISCV_TLSDESC_HI20 = 62
+R_RISCC_TLSDESC_LOAD_LO12 = 63
+R_RISCV_TLSDESC_ADD_LO12 = 64
+R_RISCV_TLSDESC_CALL = 65
################################################################################
# Architecture configuration
@@ -130,6 +174,18 @@ def asm_jump_xtensa(entry):
return struct.pack("<BH", jump_op & 0xFF, jump_op >> 8)
+def asm_jump_rv32(entry):
+ # This could be 6 bytes shorter, but the code currently cannot
+ # support a trampoline with varying length depending on the offset.
+
+ # auipc t6, HI(entry)
+ # jalr zero, t6, LO(entry)
+ upper, lower = split_riscv_address(entry)
+ return struct.pack(
+ "<II", (upper | 0x00000F97) & 0xFFFFFFFF, ((lower << 20) | 0x000F8067) & 0xFFFFFFFF
+ )
+
+
class ArchData:
def __init__(self, name, mpy_feature, word_size, arch_got, asm_jump, *, separate_rodata=False):
self.name = name
@@ -199,6 +255,13 @@ ARCH_DATA = {
asm_jump_xtensa,
separate_rodata=True,
),
+ "rv32imc": ArchData(
+ "EM_RISCV",
+ MP_NATIVE_ARCH_RV32IMC << 2,
+ 4,
+ (R_RISCV_32, R_RISCV_GOT_HI20, R_RISCV_GOT32_PCREL),
+ asm_jump_rv32,
+ ),
}
################################################################################
@@ -219,6 +282,21 @@ def pack_u24le(data, offset, value):
data[offset + 2] = value >> 16 & 0xFF
+def split_riscv_address(value):
+ # The address can be represented with just the lowest 12 bits
+ if value < 0 and value > -2048:
+ value = 4096 + value
+ return 0, value
+ # 2s complement
+ if value < 0:
+ value = 0x100000000 + value
+ upper, lower = (value & 0xFFFFF000), (value & 0xFFF)
+ if lower & 0x800 != 0:
+ # Reverse lower part sign extension
+ upper += 0x1000
+ return upper & 0xFFFFFFFF, lower & 0xFFFFFFFF
+
+
def xxd(text):
for i in range(0, len(text), 16):
print("{:08x}:".format(i), end="")
@@ -346,7 +424,7 @@ def build_got_generic(env):
for r in sec.reloc:
s = r.sym
if not (
- s.entry["st_info"]["bind"] == "STB_GLOBAL"
+ s.entry["st_info"]["bind"] in ("STB_GLOBAL", "STB_WEAK")
and r["r_info_type"] in env.arch.arch_got
):
continue
@@ -487,6 +565,8 @@ def do_relocation_text(env, text_addr, r):
# Default relocation type and name for logging
reloc_type = "le32"
log_name = None
+ addr = None
+ value = None
if (
env.arch.name == "EM_386"
@@ -590,12 +670,46 @@ def do_relocation_text(env, text_addr, r):
return
assert 0
+ elif env.arch.name == "EM_RISCV" and r_info_type in (
+ R_RISCV_TLS_GD_HI20,
+ R_RISCV_TLSDESC_HI20,
+ R_RISCV_TLSDESC_ADD_LO12,
+ R_RISCV_TLSDESC_CALL,
+ ):
+ # TLS relocations are not supported.
+ raise LinkError("{}: RISC-V TLS relocation: {}".format(s.filename, s.name))
+
+ elif env.arch.name == "EM_RISCV" and r_info_type in (
+ R_RISCV_TPREL_HI20,
+ R_RISCV_TPREL_LO12_I,
+ R_RISCV_TPREL_LO12_S,
+ R_RISCV_TPREL_ADD,
+ ):
+ # ThreadPointer-relative relocations are not supported.
+ raise LinkError("{}: RISC-V TP-relative relocation: {}".format(s.filename, s.name))
+
+ elif env.arch.name == "EM_RISCV" and r_info_type in (R_RISCV_SET_ULEB128, R_RISCV_SUB_ULEB128):
+ # 128-bit value relocations are not supported
+ raise LinkError("{}: RISC-V ULEB128 relocation: {}".format(s.filename, s.name))
+
+ elif env.arch.name == "EM_RISCV" and r_info_type in (R_RISCV_RELAX, R_RISCV_ALIGN):
+ # To keep things simple, no relocations are relaxed and thus no
+ # size optimisation is performed even if there is the chance, along
+ # with no offsets to fix up.
+ return
+
+ elif env.arch.name == "EM_RISCV":
+ (addr, value) = process_riscv32_relocation(env, text_addr, r)
+
else:
# Unknown/unsupported relocation
assert 0, r_info_type
# Write relocation
- if reloc_type == "le32":
+ if env.arch.name == "EM_RISCV":
+ # This case is already handled by `process_riscv_relocation`.
+ pass
+ elif reloc_type == "le32":
(existing,) = struct.unpack_from("<I", env.full_text, r_offset)
struct.pack_into("<I", env.full_text, r_offset, (existing + reloc) & 0xFFFFFFFF)
elif reloc_type == "thumb_b":
@@ -623,7 +737,10 @@ def do_relocation_text(env, text_addr, r):
log_name = s.section.name
else:
log_name = s.name
- log(LOG_LEVEL_3, " {:08x} {} -> {:08x}".format(r_offset, log_name, addr))
+ if addr is not None:
+ log(LOG_LEVEL_3, " {:08x} {} -> {:08x}".format(r_offset, log_name, addr))
+ else:
+ log(LOG_LEVEL_3, " {:08x} {} == {:08x}".format(r_offset, log_name, value))
def do_relocation_data(env, text_addr, r):
@@ -646,12 +763,16 @@ def do_relocation_data(env, text_addr, r):
and r_info_type == R_ARM_ABS32
or env.arch.name == "EM_XTENSA"
and r_info_type == R_XTENSA_32
+ or env.arch.name == "EM_RISCV"
+ and r_info_type == R_RISCV_32
):
# Relocation in data.rel.ro to internal/external symbol
if env.arch.word_size == 4:
struct_type = "<I"
elif env.arch.word_size == 8:
struct_type = "<Q"
+ if hasattr(s, "resolved"):
+ s = s.resolved
sec = s.section
assert r_offset % env.arch.word_size == 0
addr = sec.addr + s["st_value"] + r_addend
@@ -684,6 +805,276 @@ def do_relocation_data(env, text_addr, r):
assert 0, r_info_type
+RISCV_RELOCATIONS_TYPE_MAP = {
+ R_RISCV_ADD8: ("riscv_addsub", "B", 8, 1),
+ R_RISCV_ADD16: ("riscv_addsub", "<H", 16, 1),
+ R_RISCV_ADD32: ("riscv_addsub", "<I", 32, 1),
+ R_RISCV_ADD64: ("riscv_addsub", "<Q", 64, 1),
+ R_RISCV_SUB6: ("riscv_addsub", "B", 6, -1),
+ R_RISCV_SUB8: ("riscv_addsub", "B", 8, -1),
+ R_RISCV_SUB16: ("riscv_addsub", "<H", 16, -1),
+ R_RISCV_SUB32: ("riscv_addsub", "<I", 32, -1),
+ R_RISCV_SUB64: ("riscv_addsub", "<Q", 64, -1),
+ R_RISCV_SET6: ("riscv_set6", "B", 6),
+ R_RISCV_SET8: ("riscv_set8", "B", 8),
+ R_RISCV_SET16: ("riscv_set16", "<H", 16),
+ R_RISCV_SET32: ("riscv_set32", "<I", 32),
+ R_RISCV_JAL: "riscv_j",
+ R_RISCV_BRANCH: "riscv_b",
+ R_RISCV_RVC_BRANCH: "riscv_cb",
+ R_RISCV_RVC_JUMP: "riscv_cj",
+ R_RISCV_CALL: "riscv_call",
+ R_RISCV_CALL_PLT: "riscv_call",
+ R_RISCV_PCREL_LO12_I: "riscv_lo12i",
+ R_RISCV_PCREL_LO12_S: "riscv_lo12s",
+ R_RISCV_LO12_I: "riscv_lo12i",
+ R_RISCV_LO12_S: "riscv_lo12s",
+ R_RISCV_32_PCREL: "riscv_32pcrel",
+ R_RISCV_PLT32: "riscv_32pcrel",
+}
+
+
+def process_riscv32_relocation(env, text_addr, r):
+ assert env.arch.name == "EM_RISCV"
+
+ addr = None
+ value = None
+ s = r.sym
+
+ if hasattr(s, "resolved"):
+ s = s.resolved
+
+ r_offset = r["r_offset"] + text_addr
+ r_info_type = r["r_info_type"]
+ try:
+ r_addend = r["r_addend"]
+ except KeyError:
+ r_addend = 0
+
+ if r_info_type == R_RISCV_GOT_HI20:
+ got_entry = env.got_entries[s.name]
+ addr = env.got_section.addr + got_entry.offset
+ reloc = addr + r_addend - r_offset
+ r.computed_reloc = reloc
+ reloc_type = "riscv_hi20"
+
+ elif r_info_type == R_RISCV_GOT32_PCREL:
+ got_entry = env.got_entries[s.name]
+ addr = env.got_section.addr + got_entry.offset
+ value = addr + r_addend - r_offset
+ reloc_type = "riscv_set32"
+
+ elif r_info_type == R_RISCV_PCREL_HI20:
+ addr = s.section.addr + s["st_value"]
+ reloc = addr + r_addend - r_offset
+ r.computed_reloc = reloc
+ reloc_type = "riscv_hi20"
+
+ elif r_info_type == R_RISCV_HI20:
+ addr = s.section.addr + s["st_value"]
+ reloc = addr + r_addend
+ r.computed_reloc = reloc
+ reloc_type = "riscv_hi20"
+
+ elif r_info_type in (
+ R_RISCV_PCREL_LO12_I,
+ R_RISCV_PCREL_LO12_S,
+ R_RISCV_LO12_I,
+ R_RISCV_LO12_S,
+ ):
+ parent = None
+ for potential_parent in s.section.reloc:
+ if potential_parent["r_offset"] != s["st_value"]:
+ continue
+ if potential_parent["r_info_type"] not in (
+ R_RISCV_GOT_HI20,
+ R_RISCV_PCREL_HI20,
+ R_RISCV_HI20,
+ ):
+ continue
+ parent = potential_parent
+ break
+ if parent is None:
+ assert 0, r
+ addr = s.section.addr + s["st_value"]
+ reloc = parent.computed_reloc
+ reloc_type = RISCV_RELOCATIONS_TYPE_MAP[r_info_type]
+
+ elif r_info_type in (
+ R_RISCV_JAL,
+ R_RISCV_RVC_BRANCH,
+ R_RISCV_RVC_JUMP,
+ R_RISCV_CALL,
+ R_RISCV_CALL_PLT,
+ R_RISCV_BRANCH,
+ R_RISCV_32_PCREL,
+ R_RISCV_PLT32,
+ ):
+ addr = s.section.addr + s["st_value"]
+ reloc = addr + r_addend - r_offset
+ reloc_type = RISCV_RELOCATIONS_TYPE_MAP[r_info_type]
+
+ elif r_info_type in (
+ R_RISCV_ADD8,
+ R_RISCV_ADD16,
+ R_RISCV_ADD32,
+ R_RISCV_ADD64,
+ R_RISCV_SUB6,
+ R_RISCV_SUB8,
+ R_RISCV_SUB16,
+ R_RISCV_SUB32,
+ R_RISCV_SUB64,
+ R_RISCV_SET6,
+ R_RISCV_SET8,
+ R_RISCV_SET16,
+ R_RISCV_SET32,
+ ):
+ value = s.section.addr + s["st_value"] + r_addend
+ reloc_type, *reloc_args = RISCV_RELOCATIONS_TYPE_MAP[r_info_type]
+
+ else:
+ # Unknown/unsupported relocation
+ assert 0, r_info_type
+
+ # Write relocation
+ if reloc_type == "riscv_hi20":
+ # Patch the upper 20 bits of the opcode
+ upper, _ = split_riscv_address(reloc)
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ ((existing & 0xFFF) | upper) & 0xFFFFFFFF,
+ )
+ elif reloc_type == "riscv_lo12i":
+ # Patch the lower 12 bits of an I-opcode immediate.
+ _, lower = split_riscv_address(reloc)
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ ((existing & 0xFFFFF) | ((lower & 0xFFF) << 20)) & 0xFFFFFFFF,
+ )
+ elif reloc_type == "riscv_lo12s":
+ # Patch the lower 12 bits of an S-opcode immediate.
+ _, lower = split_riscv_address(reloc)
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ ((existing & 0xFE000F80) | ((lower & 0xFE0) << 20) | ((lower & 0x1F) << 7))
+ & 0xFFFFFFFF,
+ )
+ elif reloc_type == "riscv_cb":
+ # Patch the target of a compressed branch opcode
+ (existing,) = struct.unpack_from("<H", env.full_text, r_offset)
+ struct.pack_into(
+ "<H",
+ env.full_text,
+ r_offset,
+ (
+ (existing & 0xE383)
+ | ((reloc & 0x100) << 4)
+ | ((reloc & 0xC0) >> 1)
+ | ((reloc & 0x20) >> 3)
+ | ((reloc & 0x18) << 7)
+ | ((reloc & 0x06) << 2)
+ )
+ & 0xFFFF,
+ )
+ elif reloc_type == "riscv_cj":
+ # Patch the target of a compressed jump opcode
+ (existing,) = struct.unpack_from("<H", env.full_text, r_offset)
+ struct.pack_into(
+ "<H",
+ env.full_text,
+ r_offset,
+ (
+ (existing & 0xE003)
+ | ((reloc & 0x800) << 1)
+ | ((reloc & 0x400) >> 2)
+ | ((reloc & 0x300) << 1)
+ | ((reloc & 0x80) >> 1)
+ | ((reloc & 0x40) << 1)
+ | ((reloc & 0x20) >> 3)
+ | ((reloc & 0x10) << 7)
+ | ((reloc & 0x0E) << 2)
+ )
+ & 0xFFFF,
+ )
+ elif reloc_type == "riscv_call":
+ # Patch a pair of opcodes forming a call operation
+ upper, lower = split_riscv_address(reloc)
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ ((existing & 0xFFF) | upper) & 0xFFFFFFFF,
+ )
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset + 4)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset + 4,
+ ((existing & 0xFFFFF) | (lower << 20)) & 0xFFFFFFFF,
+ )
+ elif reloc_type == "riscv_b":
+ # Patch a conditional opcode
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ (
+ (existing & 0x01FFF07F)
+ | ((reloc & 0x1000) << 19)
+ | ((reloc & 0x800) >> 4)
+ | ((reloc & 0x7E0) << 20)
+ | ((reloc & 0x1E) << 7)
+ )
+ & 0xFFFFFFFF,
+ )
+ elif reloc_type == "riscv_j":
+ # Patch a jump/jump with link opcode
+ (existing,) = struct.unpack_from("<I", env.full_text, r_offset)
+ struct.pack_into(
+ "<I",
+ env.full_text,
+ r_offset,
+ (
+ (existing & 0xFFF)
+ | ((reloc & 0x100000) << 11)
+ | (reloc & 0xFF000)
+ | ((reloc & 0x800) << 9)
+ | ((reloc & 0x7FE) << 20)
+ ),
+ )
+ elif reloc_type == "riscv_addsub":
+ (fmt, bits, multiplier) = reloc_args
+ (existing,) = struct.unpack_from(fmt, env.full_text, r_offset)
+ mask = (1 << bits) - 1
+ value = (existing & mask) + (value * multiplier)
+ if value < 0:
+ value = (1 << bits) + value
+ struct.pack_into(fmt, env.full_text, r_offset, (existing & ~mask) | (value & mask))
+ elif reloc_type == "riscv_set":
+ (fmt, bits) = reloc_args
+ (existing,) = struct.unpack_from(fmt, env.full_text, r_offset)
+ mask = (1 << bits) - 1
+ struct.pack_into(fmt, env.full_text, r_offset, (existing & ~mask) | (value & mask))
+ elif reloc_type == "riscv_32pcrel":
+ # Write the distance from the current PC
+ struct.pack_into("<I", env.full_text, r_offset, reloc & 0xFFFFFFFF)
+ else:
+ assert 0, reloc_type
+
+ return addr, value
+
+
def load_object_file(env, felf):
with open(felf, "rb") as f:
elf = elffile.ELFFile(f)
@@ -727,7 +1118,7 @@ def load_object_file(env, felf):
if shndx in sections_shndx:
# Symbol with associated section
sym.section = sections_shndx[shndx]
- if sym["st_info"]["bind"] == "STB_GLOBAL":
+ if sym["st_info"]["bind"] in ("STB_GLOBAL", "STB_WEAK"):
# Defined global symbol
if sym.name in env.known_syms and not sym.name.startswith(
"__x86.get_pc_thunk."