Skip to content
Draft

Cpy rust #11393

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ports/espressif/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,11 @@ ifneq ($(CIRCUITPY_BLEIO_NATIVE),0)
BLE_SDKCONFIG := ;esp-idf-config/sdkconfig-ble.defaults
endif

SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig
ifeq ($(CIRCUITPY_ENABLE_MPY_NATIVE),1)
MPY_NATIVE_SDKCONFIG := ;esp-idf-config/sdkconfig-mpy-native.defaults
endif

SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG)$(MPY_NATIVE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig

# create the config headers
.PHONY: do-sdkconfig
Expand Down
18 changes: 18 additions & 0 deletions ports/espressif/esp-idf-config/sdkconfig-mpy-native.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Espressif IoT Development Framework Configuration
#
#
# Component config
#
#
# ESP System Settings
#
# Memory protection enforces W^X, which makes MALLOC_CAP_EXEC allocations
# impossible. Native .mpy modules have to be copied into executable RAM at
# import time, so it has to be off for CIRCUITPY_ENABLE_MPY_NATIVE builds.
# CONFIG_ESP_SYSTEM_MEMPROT is not set
# end of ESP System Settings

# end of Component config

# end of Espressif IoT Development Framework Configuration
7 changes: 7 additions & 0 deletions ports/espressif/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
#define MICROPY_NLR_SETJMP (1)
#define CIRCUITPY_DEFAULT_STACK_SIZE 0x6000

#if MICROPY_EMIT_XTENSAWIN
// The GC heap is not instruction-fetchable, so native code has to be moved into
// IRAM before it can be run.
void *port_native_code_commit(void *buf, size_t len, void *reloc);
#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) port_native_code_commit((buf), (len), (reloc))
#endif

// PSRAM can require more stack space for GC.
#define MICROPY_ALLOC_GC_STACK_SIZE (128)

Expand Down
42 changes: 42 additions & 0 deletions ports/espressif/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include "py/mpprint.h"
#include "py/runtime.h"

#if MICROPY_EMIT_XTENSAWIN
#include "py/persistentcode.h"
#endif

#include "esp_mac.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
Expand Down Expand Up @@ -347,6 +351,40 @@ size_t port_heap_get_largest_free_size(void) {
return free_size;
}

#if MICROPY_EMIT_XTENSAWIN
// Native code loaded from a .mpy is copied into IRAM so that it can be executed,
// and is kept in a list so it can be released when the VM resets.
typedef struct _native_code_node_t {
struct _native_code_node_t *next;
uint32_t data[];
} native_code_node_t;

static native_code_node_t *native_code_head;

void *port_native_code_commit(void *buf, size_t len, void *reloc) {
len = (len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1);
native_code_node_t *node = heap_caps_malloc(sizeof(native_code_node_t) + len, MALLOC_CAP_EXEC);
if (node == NULL) {
m_malloc_fail(len);
}
node->next = native_code_head;
native_code_head = node;
if (reloc != NULL) {
mp_native_relocate(reloc, buf, (uintptr_t)node->data);
}
memcpy(node->data, buf, len);
return node->data;
}

static void native_code_free_all(void) {
while (native_code_head != NULL) {
native_code_node_t *next = native_code_head->next;
heap_caps_free(native_code_head);
native_code_head = next;
}
}
#endif

void reset_port_early(void) {
// esp-camera adds an I2C device on the ESP I2C bus, and keeps it there. This
// is unlike busio.I2C, which adds and removes the device on each operation.
Expand All @@ -359,6 +397,10 @@ void reset_port_early(void) {

void reset_port(void) {

#if MICROPY_EMIT_XTENSAWIN
native_code_free_all();
#endif

#if CIRCUITPY_SSL
ssl_reset();
#endif
Expand Down
15 changes: 15 additions & 0 deletions ports/mimxrt10xx/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,20 @@ extern uint8_t _ld_default_stack_size;

#include "py/circuitpy_mpconfig.h"

#if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB
// The GC heap is in cacheable OCRAM and the M7 runs with both caches enabled, so
// freshly written code has to be pushed out of the data cache before the
// instruction side is allowed to fetch it.
void *port_native_code_commit(void *buf, size_t len, void *reloc);
#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) port_native_code_commit((buf), (len), (reloc))

// Defining MP_PLAT_COMMIT_EXEC otherwise tells the core that the port owns the native
// text allocation, which turns off GC tracking for it. This hook only flushes caches and
// hands back the same GC heap pointer, so the text still has to be tracked or a later
// collection reclaims code that is only referenced from inside the block.
#define MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA (1)
#define MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA (0)
#endif

// TODO:
// mp_obj_t playing_audio[AUDIO_DMA_CHANNEL_COUNT] as an MP_REGISTER_ROOT_POINTER.
30 changes: 29 additions & 1 deletion ports/mimxrt10xx/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "supervisor/board.h"
#include "supervisor/port.h"

#include "py/persistentcode.h"

#include "fsl_device_registers.h"

#if CIRCUITPY_AUDIOBUSIO
Expand Down Expand Up @@ -49,6 +51,13 @@
#define NO_EXECUTION 1
#define EXECUTION 0

#if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB
// Native code from a .mpy is loaded into the GC heap, which lives in OCRAM, and then jumped to.
#define OCRAM_EXECUTION EXECUTION
#else
#define OCRAM_EXECUTION NO_EXECUTION
#endif

// Shareable if the memory system manages coherency. This means shared between memory bus masters,
// not just CPUs.
#define NOT_SHAREABLE 0
Expand Down Expand Up @@ -256,7 +265,7 @@ __attribute__((used, naked, no_instrument_function, optimize("no-tree-loop-distr
// cost of 1/4 speed OCRAM accesses. It will leave more room for caching data from the flash
// too which might be a net win.
MPU->RBAR = ARM_MPU_RBAR(14, ((uint32_t)&_ld_ocram_start));
MPU->RASR = ARM_MPU_RASR(NO_EXECUTION, ARM_MPU_AP_FULL, NORMAL, NOT_SHAREABLE, CACHEABLE, BUFFERABLE, NO_SUBREGIONS, ARM_MPU_REGION_SIZE_512KB);
MPU->RASR = ARM_MPU_RASR(OCRAM_EXECUTION, ARM_MPU_AP_FULL, NORMAL, NOT_SHAREABLE, CACHEABLE, BUFFERABLE, NO_SUBREGIONS, ARM_MPU_REGION_SIZE_512KB);

#if IMXRT10XX
// We steal 64k from FlexRAM for ITCM and DTCM so disable those memory regions here.
Expand Down Expand Up @@ -475,6 +484,25 @@ uint32_t *port_heap_get_top(void) {
return &_ld_heap_end;
}

#if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB
// Native code from a .mpy is written into the heap as data, then jumped to. The
// heap is in cacheable OCRAM, so without this the instruction side can fetch
// stale bytes: a hard fault if they decode to nonsense, a wrong answer if they
// do not. Imports are rare, so clean and invalidate the whole of both caches
// rather than worry about the range's alignment.
void *port_native_code_commit(void *buf, size_t len, void *reloc) {
if (reloc != NULL) {
mp_native_relocate(reloc, buf, (uintptr_t)buf);
}
(void)len;
SCB_CleanDCache();
SCB_InvalidateICache();
__DSB();
__ISB();
return buf;
}
#endif

// Place the word into the low power section of the SNVS.
void PLACE_IN_ITCM(port_set_saved_word)(uint32_t value) {
SNVS->LPGPR[1] = value;
Expand Down
8 changes: 7 additions & 1 deletion py/asmxtensa.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@

#include "py/asmxtensa.h"

// N_XTENSAWIN is set by emitnxtensawin.c, but this file is its own translation
// unit so it has to be derived from the configured emitter.
#ifndef N_XTENSAWIN
#define N_XTENSAWIN MICROPY_EMIT_XTENSAWIN
#endif

#if N_XTENSAWIN
#define REG_TEMP ASM_XTENSA_REG_TEMPORARY_WIN
#else
Expand Down Expand Up @@ -289,7 +295,7 @@ void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label) {
asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A0);
}

void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset) {
static void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset) {
if (word_offset < 16) {
asm_xtensa_op_l32i_n(as, reg_dest, reg_base, word_offset);
} else if (word_offset < 256) {
Expand Down
3 changes: 3 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,9 @@ endif
$(patsubst %.c,$(BUILD)/%.o,$(SRC_LIBM)): CFLAGS += -Wno-missing-prototypes
endif

# Xtensa requires strict alignment, so the emitter's byte-buffer casts warn.
$(BUILD)/py/asmxtensa.o: CFLAGS += -Wno-cast-align

# Sources used in all ports except unix.
SRC_CIRCUITPY_COMMON = \
shared/readline/readline.c \
Expand Down
6 changes: 6 additions & 0 deletions py/circuitpy_mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ extern void common_hal_mcu_enable_interrupts(void);
#define MICROPY_COMP_MODULE_CONST (1)
#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0)
#define MICROPY_DEBUG_PRINTERS (0)
// Enable the native emitter matching the target architecture, so that native
// .mpy modules built for it can be imported.
#if defined(__XTENSA_WINDOWED_ABI__)
#define MICROPY_EMIT_XTENSAWIN (CIRCUITPY_ENABLE_MPY_NATIVE)
#else
#define MICROPY_EMIT_INLINE_THUMB (CIRCUITPY_ENABLE_MPY_NATIVE)
#define MICROPY_EMIT_THUMB (CIRCUITPY_ENABLE_MPY_NATIVE)
#endif
#define MICROPY_EMIT_X64 (0)
#define MICROPY_ENABLE_DOC_STRING (0)
#define MICROPY_ENABLE_FINALISER (1)
Expand Down
9 changes: 8 additions & 1 deletion tools/mpy-tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1795,7 +1795,14 @@ def adjust_bytecode_qstr_obj_indices(bytecode_in, qstr_table_base, obj_table_bas
opcodes.append(opcode)
ip += sz
if fmt == MP_BC_FORMAT_OFFSET:
opcode.arg += ip
# A jump is measured from the byte after its offset, which is the
# end of the instruction for every offset opcode but one.
# MP_BC_UNWIND_JUMP carries a trailing unwind count, and py/vm.c
# adds the offset before stepping over it. Counting that byte here
# puts the destination of every `break` or `continue` out of a
# `try` one past its label, and the lookup below raises KeyError.
# mp_opcode_encode is already right, so only the decode side moves.
opcode.arg += ip - 1 if extra_arg is not None else ip

# Link jump opcodes to their destination.
for opcode in opcodes:
Expand Down
19 changes: 13 additions & 6 deletions tools/mpy_ld.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET = 74

# ELF constants
R_XTENSA_NONE = 0
R_386_32 = 1
R_RISCV_32 = 1
R_X86_64_64 = 1
Expand Down Expand Up @@ -569,6 +570,10 @@ def build_got_xtensa(env):

# Look through literal relocations to find any global pointers that should be GOT entries
for r in sec.reloc:
# LLVM emits no-op entries with a null symbol at the same offset as
# the real relocation; GAS does not. Nothing to resolve.
if r["r_info_type"] == R_XTENSA_NONE:
continue
s = r.sym
s_type = s.entry["st_info"]["type"]
assert s_type in ("STT_NOTYPE", "STT_FUNC", "STT_OBJECT", "STT_SECTION"), s_type
Expand All @@ -584,7 +589,7 @@ def build_got_xtensa(env):
name = s.name
if r["r_addend"] != 0:
name = "{}+0x{:x}".format(name, r["r_addend"])
idx = "{}+0x{:x}".format(sec.filename, r["r_offset"])
idx = "{}+{}+0x{:x}".format(sec.filename, sec.name, r["r_offset"])
env.xt_literals[idx] = name
if name in env.got_entries:
# Deduplicate GOT entries
Expand All @@ -593,7 +598,7 @@ def build_got_xtensa(env):

# Go through all literal entries finding those that aren't global pointers so must be actual literals
for i in range(0, len(sec.data), env.arch.word_size):
idx = "{}+0x{:x}".format(sec.filename, i)
idx = "{}+{}+0x{:x}".format(sec.filename, sec.name, i)
if idx not in env.xt_literals:
# This entry is an actual literal
value = struct.unpack_from("<I", sec.data, i)[0]
Expand Down Expand Up @@ -790,7 +795,7 @@ def do_relocation_text(env, text_addr, r):
# it looks like R_XTENSA_SLOT0_OP into .text is already correctly relocated
return
assert sec.name.startswith(".literal"), sec.name
lit_idx = "{}+0x{:x}".format(sec.filename, r_addend)
lit_idx = "{}+{}+0x{:x}".format(sec.filename, sec.name, r_addend)
lit_ptr = env.xt_literals[lit_idx]
if isinstance(lit_ptr, str):
addr = env.got_section.addr + env.got_entries[lit_ptr].offset
Expand Down Expand Up @@ -872,9 +877,11 @@ def do_relocation_text(env, text_addr, r):
elif reloc_type == "xtensa_l32r":
l32r = unpack_u24le(env.full_text, r_offset)
assert l32r & 0xF == 1 # RI16 encoded l32r
l32r_imm16 = l32r >> 8
l32r_imm16 = (l32r_imm16 + reloc >> 2) & 0xFFFF
l32r = l32r & 0xFF | l32r_imm16 << 8
# l32r loads from ((PC + 3) & ~3) + ((0xFFFF0000 | imm16) << 2), so encode the
# final offset directly; the existing imm16 may be a non-zero assembler guess.
l32r_offset = addr - ((r_offset + 3) & ~3)
assert -0x40000 <= l32r_offset <= -4, l32r_offset
l32r = l32r & 0xFF | ((l32r_offset >> 2) & 0xFFFF) << 8
pack_u24le(env.full_text, r_offset, l32r)
else:
assert 0, reloc_type
Expand Down
Loading