// ----------- Dubious vertex reentry mechanism ---------- // #include "ipusetjmp.h" jmp_buf IPUpy_exit_env = {0}; jmp_buf IPUpy_checkpoint_env = {0}; static void (*IPUpy_syscall_callback)(); void IPUpy_register_syscall_callback(void (*f)()) { IPUpy_syscall_callback = f; } // void IPUpy_read_cycles(); // --------------- py/*.c -------------------- // #include "mpconfigport.h" #include "py/mpstate.h" #if MICROPY_DYNAMIC_COMPILER mp_dynamic_compiler_t mp_dynamic_compiler = {0}; #endif mp_state_ctx_t mp_state_ctx; #include "py/mpstate.h" #if !MICROPY_NLR_SETJMP // When not using setjmp, nlr_push_tail is called from inline asm so needs special care #if MICROPY_NLR_X86 && MICROPY_NLR_OS_WINDOWS // On these 32-bit platforms make sure nlr_push_tail doesn't have a leading underscore unsigned int nlr_push_tail(nlr_buf_t *nlr) asm ("nlr_push_tail"); #else // LTO can't see inside inline asm functions so explicitly mark nlr_push_tail as used __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); #endif #endif unsigned int nlr_push_tail(nlr_buf_t *nlr) { nlr_buf_t **top = &MP_STATE_THREAD(nlr_top); nlr->prev = *top; MP_NLR_SAVE_PYSTACK(nlr); *top = nlr; return 0; // normal return } void nlr_pop(void) { nlr_buf_t **top = &MP_STATE_THREAD(nlr_top); *top = (*top)->prev; } #include "py/mpstate.h" #if MICROPY_NLR_X86 #undef nlr_push // For reference, x86 callee save regs are: // ebx, esi, edi, ebp, esp, eip #if MICROPY_NLR_OS_WINDOWS unsigned int nlr_push_tail(nlr_buf_t *nlr) asm ("nlr_push_tail"); #else __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); #endif #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 8 // Since gcc 8.0 the naked attribute is supported #define USE_NAKED (1) #define UNDO_PRELUDE (0) #elif defined(__ZEPHYR__) || defined(__ANDROID__) // Zephyr and Android use a different calling convention by default #define USE_NAKED (0) #define UNDO_PRELUDE (0) #else #define USE_NAKED (0) #define UNDO_PRELUDE (1) #endif #if USE_NAKED __attribute__((naked)) #endif unsigned int nlr_push(nlr_buf_t *nlr) { (void)nlr; __asm volatile ( #if UNDO_PRELUDE "pop %ebp \n" // undo function's prelude #endif "mov 4(%esp), %edx \n" // load nlr_buf "mov (%esp), %eax \n" // load return %eip "mov %eax, 8(%edx) \n" // store %eip into nlr_buf "mov %ebp, 12(%edx) \n" // store %ebp into nlr_buf "mov %esp, 16(%edx) \n" // store %esp into nlr_buf "mov %ebx, 20(%edx) \n" // store %ebx into nlr_buf "mov %edi, 24(%edx) \n" // store %edi into nlr_buf "mov %esi, 28(%edx) \n" // store %esi into nlr_buf "jmp nlr_push_tail \n" // do the rest in C ); #if !USE_NAKED return 0; // needed to silence compiler warning #endif } NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "mov %0, %%edx \n" // %edx points to nlr_buf "mov 28(%%edx), %%esi \n" // load saved %esi "mov 24(%%edx), %%edi \n" // load saved %edi "mov 20(%%edx), %%ebx \n" // load saved %ebx "mov 16(%%edx), %%esp \n" // load saved %esp "mov 12(%%edx), %%ebp \n" // load saved %ebp "mov 8(%%edx), %%eax \n" // load saved %eip "mov %%eax, (%%esp) \n" // store saved %eip to stack "xor %%eax, %%eax \n" // clear return register "inc %%al \n" // increase to make 1, non-local return "ret \n" // return : // output operands : "r" (top) // input operands : // clobbered registers ); MP_UNREACHABLE } #endif // MICROPY_NLR_X86 #include "py/mpstate.h" #if MICROPY_NLR_X64 #undef nlr_push // x86-64 callee-save registers are: // rbx, rbp, rsp, r12, r13, r14, r15 __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); unsigned int nlr_push(nlr_buf_t *nlr) { (void)nlr; #if MICROPY_NLR_OS_WINDOWS __asm volatile ( "movq (%rsp), %rax \n" // load return %rip "movq %rax, 16(%rcx) \n" // store %rip into nlr_buf "movq %rbp, 24(%rcx) \n" // store %rbp into nlr_buf "movq %rsp, 32(%rcx) \n" // store %rsp into nlr_buf "movq %rbx, 40(%rcx) \n" // store %rbx into nlr_buf "movq %r12, 48(%rcx) \n" // store %r12 into nlr_buf "movq %r13, 56(%rcx) \n" // store %r13 into nlr_buf "movq %r14, 64(%rcx) \n" // store %r14 into nlr_buf "movq %r15, 72(%rcx) \n" // store %r15 into nlr_buf "movq %rdi, 80(%rcx) \n" // store %rdr into nlr_buf "movq %rsi, 88(%rcx) \n" // store %rsi into nlr_buf "jmp nlr_push_tail \n" // do the rest in C ); #else __asm volatile ( #if defined(__APPLE__) && defined(__MACH__) "pop %rbp \n" // undo function's prelude #endif "movq (%rsp), %rax \n" // load return %rip "movq %rax, 16(%rdi) \n" // store %rip into nlr_buf "movq %rbp, 24(%rdi) \n" // store %rbp into nlr_buf "movq %rsp, 32(%rdi) \n" // store %rsp into nlr_buf "movq %rbx, 40(%rdi) \n" // store %rbx into nlr_buf "movq %r12, 48(%rdi) \n" // store %r12 into nlr_buf "movq %r13, 56(%rdi) \n" // store %r13 into nlr_buf "movq %r14, 64(%rdi) \n" // store %r14 into nlr_buf "movq %r15, 72(%rdi) \n" // store %r15 into nlr_buf #if defined(__APPLE__) && defined(__MACH__) "jmp _nlr_push_tail \n" // do the rest in C #else "jmp nlr_push_tail \n" // do the rest in C #endif ); #endif return 0; // needed to silence compiler warning } NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "movq %0, %%rcx \n" // %rcx points to nlr_buf #if MICROPY_NLR_OS_WINDOWS "movq 88(%%rcx), %%rsi \n" // load saved %rsi "movq 80(%%rcx), %%rdi \n" // load saved %rdi #endif "movq 72(%%rcx), %%r15 \n" // load saved %r15 "movq 64(%%rcx), %%r14 \n" // load saved %r14 "movq 56(%%rcx), %%r13 \n" // load saved %r13 "movq 48(%%rcx), %%r12 \n" // load saved %r12 "movq 40(%%rcx), %%rbx \n" // load saved %rbx "movq 32(%%rcx), %%rsp \n" // load saved %rsp "movq 24(%%rcx), %%rbp \n" // load saved %rbp "movq 16(%%rcx), %%rax \n" // load saved %rip "movq %%rax, (%%rsp) \n" // store saved %rip to stack "xorq %%rax, %%rax \n" // clear return register "inc %%al \n" // increase to make 1, non-local return "ret \n" // return : // output operands : "r" (top) // input operands : // clobbered registers ); MP_UNREACHABLE } #endif // MICROPY_NLR_X64 #include "py/mpstate.h" #if MICROPY_NLR_THUMB #undef nlr_push // We only need the functions here if we are on arm/thumb, and we are not // using setjmp/longjmp. // // For reference, arm/thumb callee save regs are: // r4-r11, r13=sp __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { __asm volatile ( "str r4, [r0, #12] \n" // store r4 into nlr_buf "str r5, [r0, #16] \n" // store r5 into nlr_buf "str r6, [r0, #20] \n" // store r6 into nlr_buf "str r7, [r0, #24] \n" // store r7 into nlr_buf #if !defined(__thumb2__) "mov r1, r8 \n" "str r1, [r0, #28] \n" // store r8 into nlr_buf "mov r1, r9 \n" "str r1, [r0, #32] \n" // store r9 into nlr_buf "mov r1, r10 \n" "str r1, [r0, #36] \n" // store r10 into nlr_buf "mov r1, r11 \n" "str r1, [r0, #40] \n" // store r11 into nlr_buf "mov r1, r13 \n" "str r1, [r0, #44] \n" // store r13=sp into nlr_buf "mov r1, lr \n" "str r1, [r0, #8] \n" // store lr into nlr_buf #else "str r8, [r0, #28] \n" // store r8 into nlr_buf "str r9, [r0, #32] \n" // store r9 into nlr_buf "str r10, [r0, #36] \n" // store r10 into nlr_buf "str r11, [r0, #40] \n" // store r11 into nlr_buf "str r13, [r0, #44] \n" // store r13=sp into nlr_buf #if MICROPY_NLR_NUM_REGS == 16 "vstr d8, [r0, #48] \n" // store s16-s17 into nlr_buf "vstr d9, [r0, #56] \n" // store s18-s19 into nlr_buf "vstr d10, [r0, #64] \n" // store s20-s21 into nlr_buf #endif "str lr, [r0, #8] \n" // store lr into nlr_buf #endif #if !defined(__thumb2__) "ldr r1, nlr_push_tail_var \n" "bx r1 \n" // do the rest in C ".align 2 \n" "nlr_push_tail_var: .word nlr_push_tail \n" #else #if defined(__APPLE__) || defined(__MACH__) "b _nlr_push_tail \n" // do the rest in C #else "b nlr_push_tail \n" // do the rest in C #endif #endif ); #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) // Older versions of gcc give an error when naked functions don't return a value // Additionally exclude Clang as it also defines __GNUC__ but doesn't need this statement return 0; #endif } NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "mov r0, %0 \n" // r0 points to nlr_buf "ldr r4, [r0, #12] \n" // load r4 from nlr_buf "ldr r5, [r0, #16] \n" // load r5 from nlr_buf "ldr r6, [r0, #20] \n" // load r6 from nlr_buf "ldr r7, [r0, #24] \n" // load r7 from nlr_buf #if !defined(__thumb2__) "ldr r1, [r0, #28] \n" // load r8 from nlr_buf "mov r8, r1 \n" "ldr r1, [r0, #32] \n" // load r9 from nlr_buf "mov r9, r1 \n" "ldr r1, [r0, #36] \n" // load r10 from nlr_buf "mov r10, r1 \n" "ldr r1, [r0, #40] \n" // load r11 from nlr_buf "mov r11, r1 \n" "ldr r1, [r0, #44] \n" // load r13=sp from nlr_buf "mov r13, r1 \n" "ldr r1, [r0, #8] \n" // load lr from nlr_buf "mov lr, r1 \n" #else "ldr r8, [r0, #28] \n" // load r8 from nlr_buf "ldr r9, [r0, #32] \n" // load r9 from nlr_buf "ldr r10, [r0, #36] \n" // load r10 from nlr_buf "ldr r11, [r0, #40] \n" // load r11 from nlr_buf "ldr r13, [r0, #44] \n" // load r13=sp from nlr_buf #if MICROPY_NLR_NUM_REGS == 16 "vldr d8, [r0, #48] \n" // load s16-s17 from nlr_buf "vldr d9, [r0, #56] \n" // load s18-s19 from nlr_buf "vldr d10, [r0, #64] \n" // load s20-s21 from nlr_buf #endif "ldr lr, [r0, #8] \n" // load lr from nlr_buf #endif "movs r0, #1 \n" // return 1, non-local return "bx lr \n" // return : // output operands : "r" (top) // input operands : // clobbered registers ); MP_UNREACHABLE } #endif // MICROPY_NLR_THUMB #include "py/mpstate.h" // needed for NLR defs #if MICROPY_NLR_AARCH64 // AArch64 callee-saved registers are x19-x29. // https://en.wikipedia.org/wiki/Calling_convention#ARM_(A64) // Implemented purely as inline assembly; inside a function, we have to deal with undoing the prologue, restoring // SP and LR. This way, we don't. __asm( #if defined(__APPLE__) && defined(__MACH__) "_nlr_push: \n" ".global _nlr_push \n" #else "nlr_push: \n" ".global nlr_push \n" #endif "mov x9, sp \n" "stp lr, x9, [x0, #16]\n" // 16 == offsetof(nlr_buf_t, regs) "stp x19, x20, [x0, #32]\n" "stp x21, x22, [x0, #48]\n" "stp x23, x24, [x0, #64]\n" "stp x25, x26, [x0, #80]\n" "stp x27, x28, [x0, #96]\n" "str x29, [x0, #112]\n" #if defined(__APPLE__) && defined(__MACH__) "b _nlr_push_tail \n" // do the rest in C #else "b nlr_push_tail \n" // do the rest in C #endif ); NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) MP_STATIC_ASSERT(offsetof(nlr_buf_t, regs) == 16); // asm assumes it __asm volatile ( "ldr x29, [%0, #112]\n" "ldp x27, x28, [%0, #96]\n" "ldp x25, x26, [%0, #80]\n" "ldp x23, x24, [%0, #64]\n" "ldp x21, x22, [%0, #48]\n" "ldp x19, x20, [%0, #32]\n" "ldp lr, x9, [%0, #16]\n" // 16 == offsetof(nlr_buf_t, regs) "mov sp, x9 \n" "mov x0, #1 \n" // non-local return "ret \n" : : "r" (top) : ); MP_UNREACHABLE } #endif // MICROPY_NLR_AARCH64 #include "py/mpstate.h" #if MICROPY_NLR_POWERPC #undef nlr_push // Saving all ABI non-vol registers here unsigned int nlr_push(nlr_buf_t *nlr) { __asm__ volatile ( "li 4, 0x4eed ; " // Store canary "std 4, 0x00(%0) ;" "std 0, 0x08(%0) ;" "std 1, 0x10(%0) ;" "std 2, 0x18(%0) ;" "std 14, 0x20(%0) ;" "std 15, 0x28(%0) ;" "std 16, 0x30(%0) ;" "std 17, 0x38(%0) ;" "std 18, 0x40(%0) ;" "std 19, 0x48(%0) ;" "std 20, 0x50(%0) ;" "std 21, 0x58(%0) ;" "std 22, 0x60(%0) ;" "std 23, 0x68(%0) ;" "std 24, 0x70(%0) ;" "std 25, 0x78(%0) ;" "std 26, 0x80(%0) ;" "std 27, 0x88(%0) ;" "std 28, 0x90(%0) ;" "std 29, 0x98(%0) ;" "std 30, 0xA0(%0) ;" "std 31, 0xA8(%0) ;" "mfcr 4 ; " "std 4, 0xB0(%0) ;" "mflr 4 ;" "std 4, 0xB8(%0) ;" "li 4, nlr_push_tail@l ;" "oris 4, 4, nlr_push_tail@h ;" "mtctr 4 ;" "mr 3, %1 ; " "bctr ;" : : "r" (&nlr->regs), "r" (nlr) : ); return 0; } NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( "ld 3, 0x0(%0) ;" "cmpdi 3, 0x4eed ; " // Check canary "bne . ; " "ld 0, 0x08(%0) ;" "ld 1, 0x10(%0) ;" "ld 2, 0x18(%0) ;" "ld 14, 0x20(%0) ;" "ld 15, 0x28(%0) ;" "ld 16, 0x30(%0) ;" "ld 17, 0x38(%0) ;" "ld 18, 0x40(%0) ;" "ld 19, 0x48(%0) ;" "ld 20, 0x50(%0) ;" "ld 21, 0x58(%0) ;" "ld 22, 0x60(%0) ;" "ld 23, 0x68(%0) ;" "ld 24, 0x70(%0) ;" "ld 25, 0x78(%0) ;" "ld 26, 0x80(%0) ;" "ld 27, 0x88(%0) ;" "ld 28, 0x90(%0) ;" "ld 29, 0x98(%0) ;" "ld 30, 0xA0(%0) ;" "ld 31, 0xA8(%0) ;" "ld 3, 0xB0(%0) ;" "mtcr 3 ;" "ld 3, 0xB8(%0) ;" "mtlr 3 ; " "li 3, 1;" "blr ;" : : "r" (&top->regs) : ); MP_UNREACHABLE; } #endif // MICROPY_NLR_POWERPC #include "py/mpstate.h" #if MICROPY_NLR_XTENSA #undef nlr_push // Xtensa calling conventions: // a0 = return address // a1 = stack pointer // a2 = first arg, return value // a3-a7 = rest of args unsigned int nlr_push(nlr_buf_t *nlr) { __asm volatile ( "s32i.n a0, a2, 8 \n" // save regs... "s32i.n a1, a2, 12 \n" "s32i.n a8, a2, 16 \n" "s32i.n a9, a2, 20 \n" "s32i.n a10, a2, 24 \n" "s32i.n a11, a2, 28 \n" "s32i.n a12, a2, 32 \n" "s32i.n a13, a2, 36 \n" "s32i.n a14, a2, 40 \n" "s32i.n a15, a2, 44 \n" "j nlr_push_tail \n" // do the rest in C ); return 0; // needed to silence compiler warning } NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "mov.n a2, %0 \n" // a2 points to nlr_buf "l32i.n a0, a2, 8 \n" // restore regs... "l32i.n a1, a2, 12 \n" "l32i.n a8, a2, 16 \n" "l32i.n a9, a2, 20 \n" "l32i.n a10, a2, 24 \n" "l32i.n a11, a2, 28 \n" "l32i.n a12, a2, 32 \n" "l32i.n a13, a2, 36 \n" "l32i.n a14, a2, 40 \n" "l32i.n a15, a2, 44 \n" "movi.n a2, 1 \n" // return 1, non-local return "ret.n \n" // return : // output operands : "r" (top) // input operands : // clobbered registers ); MP_UNREACHABLE } #endif // MICROPY_NLR_XTENSA #include "py/mpstate.h" #if MICROPY_NLR_SETJMP void nlr_jump(void *val) { nlr_buf_t **top_ptr = &MP_STATE_THREAD(nlr_top); nlr_buf_t *top = *top_ptr; if (top == NULL) { nlr_jump_fail(val); } top->ret_val = val; MP_NLR_RESTORE_PYSTACK(top); *top_ptr = top->prev; longjmp(top->jmpbuf, 1); } #endif #ifndef __IPU__ #include #endif #include #include #include "py/gc.h" #include "py/runtime.h" #if MICROPY_ENABLE_GC #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif // make this 1 to dump the heap each time it changes #define EXTENSIVE_HEAP_PROFILING (0) // make this 1 to zero out swept memory to more eagerly // detect untraced object still in use #define CLEAR_ON_SWEEP (0) #define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD) #define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK) // ATB = allocation table byte // 0b00 = FREE -- free block // 0b01 = HEAD -- head of a chain of blocks // 0b10 = TAIL -- in the tail of a chain of blocks // 0b11 = MARK -- marked head block #define AT_FREE (0) #define AT_HEAD (1) #define AT_TAIL (2) #define AT_MARK (3) #define BLOCKS_PER_ATB (4) #define ATB_MASK_0 (0x03) #define ATB_MASK_1 (0x0c) #define ATB_MASK_2 (0x30) #define ATB_MASK_3 (0xc0) #define ATB_0_IS_FREE(a) (((a) & ATB_MASK_0) == 0) #define ATB_1_IS_FREE(a) (((a) & ATB_MASK_1) == 0) #define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0) #define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0) #define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1))) #define ATB_GET_KIND(block) ((MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3) #define ATB_ANY_TO_FREE(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0) #define ATB_FREE_TO_HEAD(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0) #define ATB_FREE_TO_TAIL(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0) #define ATB_HEAD_TO_MARK(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0) #define ATB_MARK_TO_HEAD(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0) #define BLOCK_FROM_PTR(ptr) (((byte *)(ptr) - MP_STATE_MEM(gc_pool_start)) / BYTES_PER_BLOCK) #define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (uintptr_t)MP_STATE_MEM(gc_pool_start))) #define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB) #if MICROPY_ENABLE_FINALISER // FTB = finaliser table byte // if set, then the corresponding block may have a finaliser #define BLOCKS_PER_FTB (8) #define FTB_GET(block) ((MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1) #define FTB_SET(block) do { MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0) #define FTB_CLEAR(block) do { MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0) #endif #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL #define GC_ENTER() mp_thread_mutex_lock(&MP_STATE_MEM(gc_mutex), 1) #define GC_EXIT() mp_thread_mutex_unlock(&MP_STATE_MEM(gc_mutex)) #else #define GC_ENTER() #define GC_EXIT() #endif // TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool void gc_init(void *start, void *end) { // align end pointer on block boundary end = (void *)((uintptr_t)end & (~(BYTES_PER_BLOCK - 1))); DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte *)end - (byte *)start); // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes): // T = A + F + P // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) size_t total_byte_len = (byte *)end - (byte *)start; #if MICROPY_ENABLE_FINALISER MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len * MP_BITS_PER_BYTE / (MP_BITS_PER_BYTE + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK); #else MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len / (1 + MP_BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); #endif MP_STATE_MEM(gc_alloc_table_start) = (byte *)start; #if MICROPY_ENABLE_FINALISER size_t gc_finaliser_table_byte_len = (MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB; MP_STATE_MEM(gc_finaliser_table_start) = MP_STATE_MEM(gc_alloc_table_start) + MP_STATE_MEM(gc_alloc_table_byte_len); #endif size_t gc_pool_block_len = MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; MP_STATE_MEM(gc_pool_start) = (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK; MP_STATE_MEM(gc_pool_end) = end; #if MICROPY_ENABLE_FINALISER assert(MP_STATE_MEM(gc_pool_start) >= MP_STATE_MEM(gc_finaliser_table_start) + gc_finaliser_table_byte_len); #endif // clear ATBs memset(MP_STATE_MEM(gc_alloc_table_start), 0, MP_STATE_MEM(gc_alloc_table_byte_len)); #if MICROPY_ENABLE_FINALISER // clear FTBs memset(MP_STATE_MEM(gc_finaliser_table_start), 0, gc_finaliser_table_byte_len); #endif // set last free ATB index to start of heap MP_STATE_MEM(gc_last_free_atb_index) = 0; // unlock the GC MP_STATE_THREAD(gc_lock_depth) = 0; // allow auto collection MP_STATE_MEM(gc_auto_collect_enabled) = 1; #if MICROPY_GC_ALLOC_THRESHOLD // by default, maxuint for gc threshold, effectively turning gc-by-threshold off MP_STATE_MEM(gc_alloc_threshold) = (size_t)-1; MP_STATE_MEM(gc_alloc_amount) = 0; #endif #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_MEM(gc_mutex)); #endif DEBUG_printf("GC layout:\n"); DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(gc_alloc_table_start), MP_STATE_MEM(gc_alloc_table_byte_len), MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB); #if MICROPY_ENABLE_FINALISER DEBUG_printf(" finaliser table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(gc_finaliser_table_start), gc_finaliser_table_byte_len, gc_finaliser_table_byte_len * BLOCKS_PER_FTB); #endif DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(gc_pool_start), gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len); } void gc_lock(void) { // This does not need to be atomic or have the GC mutex because: // - each thread has its own gc_lock_depth so there are no races between threads; // - a hard interrupt will only change gc_lock_depth during its execution, and // upon return will restore the value of gc_lock_depth. MP_STATE_THREAD(gc_lock_depth)++; } void gc_unlock(void) { // This does not need to be atomic, See comment above in gc_lock. MP_STATE_THREAD(gc_lock_depth)--; } bool gc_is_locked(void) { return MP_STATE_THREAD(gc_lock_depth) != 0; } // ptr should be of type void* #define VERIFY_PTR(ptr) ( \ ((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \ && ptr >= (void *)MP_STATE_MEM(gc_pool_start) /* must be above start of pool */ \ && ptr < (void *)MP_STATE_MEM(gc_pool_end) /* must be below end of pool */ \ ) #ifndef TRACE_MARK #if DEBUG_PRINT #define TRACE_MARK(block, ptr) DEBUG_printf("gc_mark(%p)\n", ptr) #else #define TRACE_MARK(block, ptr) #endif #endif // Take the given block as the topmost block on the stack. Check all it's // children: mark the unmarked child blocks and put those newly marked // blocks on the stack. When all children have been checked, pop off the // topmost block on the stack and repeat with that one. STATIC void gc_mark_subtree(size_t block) { // Start with the block passed in the argument. size_t sp = 0; for (;;) { MICROPY_GC_HOOK_LOOP // work out number of consecutive blocks in the chain starting with this one size_t n_blocks = 0; do { n_blocks += 1; } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL); // check this block's children void **ptrs = (void **)PTR_FROM_BLOCK(block); for (size_t i = n_blocks * BYTES_PER_BLOCK / sizeof(void *); i > 0; i--, ptrs++) { MICROPY_GC_HOOK_LOOP void *ptr = *ptrs; if (VERIFY_PTR(ptr)) { // Mark and push this pointer size_t childblock = BLOCK_FROM_PTR(ptr); if (ATB_GET_KIND(childblock) == AT_HEAD) { // an unmarked head, mark it, and push it on gc stack TRACE_MARK(childblock, ptr); ATB_HEAD_TO_MARK(childblock); if (sp < MICROPY_ALLOC_GC_STACK_SIZE) { MP_STATE_MEM(gc_stack)[sp++] = childblock; } else { MP_STATE_MEM(gc_stack_overflow) = 1; } } } } // Are there any blocks on the stack? if (sp == 0) { break; // No, stack is empty, we're done. } // pop the next block off the stack block = MP_STATE_MEM(gc_stack)[--sp]; } } STATIC void gc_deal_with_stack_overflow(void) { while (MP_STATE_MEM(gc_stack_overflow)) { MP_STATE_MEM(gc_stack_overflow) = 0; // scan entire memory looking for blocks which have been marked but not their children for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { MICROPY_GC_HOOK_LOOP // trace (again) if mark bit set if (ATB_GET_KIND(block) == AT_MARK) { gc_mark_subtree(block); } } } } STATIC void gc_sweep(void) { #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected) = 0; #endif // free unmarked heads and their tails int free_tail = 0; for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { MICROPY_GC_HOOK_LOOP switch (ATB_GET_KIND(block)) { case AT_HEAD: #if MICROPY_ENABLE_FINALISER if (FTB_GET(block)) { mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(block); if (obj->type != NULL) { // if the object has a type then see if it has a __del__ method mp_obj_t dest[2]; mp_load_method_maybe(MP_OBJ_FROM_PTR(obj), MP_QSTR___del__, dest); if (dest[0] != MP_OBJ_NULL) { // load_method returned a method, execute it in a protected environment #if MICROPY_ENABLE_SCHEDULER mp_sched_lock(); #endif mp_call_function_1_protected(dest[0], dest[1]); #if MICROPY_ENABLE_SCHEDULER mp_sched_unlock(); #endif } } // clear finaliser flag FTB_CLEAR(block); } #endif free_tail = 1; DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(block)); #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected)++; #endif // fall through to free the head MP_FALLTHROUGH case AT_TAIL: if (free_tail) { ATB_ANY_TO_FREE(block); #if CLEAR_ON_SWEEP memset((void *)PTR_FROM_BLOCK(block), 0, BYTES_PER_BLOCK); #endif } break; case AT_MARK: ATB_MARK_TO_HEAD(block); free_tail = 0; break; } } } void gc_collect_start(void) { GC_ENTER(); MP_STATE_THREAD(gc_lock_depth)++; #if MICROPY_GC_ALLOC_THRESHOLD MP_STATE_MEM(gc_alloc_amount) = 0; #endif MP_STATE_MEM(gc_stack_overflow) = 0; // Trace root pointers. This relies on the root pointers being organised // correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals, // dict_globals, then the root pointer section of mp_state_vm. void **ptrs = (void **)(void *)&mp_state_ctx; size_t root_start = offsetof(mp_state_ctx_t, thread.dict_locals); size_t root_end = offsetof(mp_state_ctx_t, vm.qstr_last_chunk); gc_collect_root(ptrs + root_start / sizeof(void *), (root_end - root_start) / sizeof(void *)); #if MICROPY_ENABLE_PYSTACK // Trace root pointers from the Python stack. ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start); gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *)); #endif } // Address sanitizer needs to know that the access to ptrs[i] must always be // considered OK, even if it's a load from an address that would normally be // prohibited (due to being undefined, in a red zone, etc). #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) __attribute__((no_sanitize_address)) #endif static void *gc_get_ptr(void **ptrs, int i) { return ptrs[i]; } void gc_collect_root(void **ptrs, size_t len) { for (size_t i = 0; i < len; i++) { MICROPY_GC_HOOK_LOOP void *ptr = gc_get_ptr(ptrs, i); if (VERIFY_PTR(ptr)) { size_t block = BLOCK_FROM_PTR(ptr); if (ATB_GET_KIND(block) == AT_HEAD) { // An unmarked head: mark it, and mark all its children TRACE_MARK(block, ptr); ATB_HEAD_TO_MARK(block); gc_mark_subtree(block); } } } } void gc_collect_end(void) { gc_deal_with_stack_overflow(); gc_sweep(); MP_STATE_MEM(gc_last_free_atb_index) = 0; MP_STATE_THREAD(gc_lock_depth)--; GC_EXIT(); } void gc_sweep_all(void) { GC_ENTER(); MP_STATE_THREAD(gc_lock_depth)++; MP_STATE_MEM(gc_stack_overflow) = 0; gc_collect_end(); } void gc_info(gc_info_t *info) { GC_ENTER(); info->total = MP_STATE_MEM(gc_pool_end) - MP_STATE_MEM(gc_pool_start); info->used = 0; info->free = 0; info->max_free = 0; info->num_1block = 0; info->num_2block = 0; info->max_block = 0; bool finish = false; for (size_t block = 0, len = 0, len_free = 0; !finish;) { size_t kind = ATB_GET_KIND(block); switch (kind) { case AT_FREE: info->free += 1; len_free += 1; len = 0; break; case AT_HEAD: info->used += 1; len = 1; break; case AT_TAIL: info->used += 1; len += 1; break; case AT_MARK: // shouldn't happen break; } block++; finish = (block == MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB); // Get next block type if possible if (!finish) { kind = ATB_GET_KIND(block); } if (finish || kind == AT_FREE || kind == AT_HEAD) { if (len == 1) { info->num_1block += 1; } else if (len == 2) { info->num_2block += 1; } if (len > info->max_block) { info->max_block = len; } if (finish || kind == AT_HEAD) { if (len_free > info->max_free) { info->max_free = len_free; } len_free = 0; } } } info->used *= BYTES_PER_BLOCK; info->free *= BYTES_PER_BLOCK; GC_EXIT(); } void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { bool has_finaliser = alloc_flags & GC_ALLOC_FLAG_HAS_FINALISER; size_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK; DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks); // check for 0 allocation if (n_blocks == 0) { return NULL; } // check if GC is locked if (MP_STATE_THREAD(gc_lock_depth) > 0) { return NULL; } GC_ENTER(); size_t i; size_t end_block; size_t start_block; size_t n_free; int collected = !MP_STATE_MEM(gc_auto_collect_enabled); #if MICROPY_GC_ALLOC_THRESHOLD if (!collected && MP_STATE_MEM(gc_alloc_amount) >= MP_STATE_MEM(gc_alloc_threshold)) { GC_EXIT(); gc_collect(); collected = 1; GC_ENTER(); } #endif for (;;) { // look for a run of n_blocks available blocks n_free = 0; for (i = MP_STATE_MEM(gc_last_free_atb_index); i < MP_STATE_MEM(gc_alloc_table_byte_len); i++) { byte a = MP_STATE_MEM(gc_alloc_table_start)[i]; // *FORMAT-OFF* if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; } if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; } if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; } if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; } // *FORMAT-ON* } GC_EXIT(); // nothing found! if (collected) { return NULL; } DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes); gc_collect(); collected = 1; GC_ENTER(); } // found, ending at block i inclusive found: // get starting and end blocks, both inclusive end_block = i; start_block = i - n_free + 1; // Set last free ATB index to block after last block we found, for start of // next scan. To reduce fragmentation, we only do this if we were looking // for a single free block, which guarantees that there are no free blocks // before this one. Also, whenever we free or shink a block we must check // if this index needs adjusting (see gc_realloc and gc_free). if (n_free == 1) { MP_STATE_MEM(gc_last_free_atb_index) = (i + 1) / BLOCKS_PER_ATB; } // mark first block as used head ATB_FREE_TO_HEAD(start_block); // mark rest of blocks as used tail // TODO for a run of many blocks can make this more efficient for (size_t bl = start_block + 1; bl <= end_block; bl++) { ATB_FREE_TO_TAIL(bl); } // get pointer to first block // we must create this pointer before unlocking the GC so a collection can find it void *ret_ptr = (void *)(MP_STATE_MEM(gc_pool_start) + start_block * BYTES_PER_BLOCK); DEBUG_printf("gc_alloc(%p)\n", ret_ptr); #if MICROPY_GC_ALLOC_THRESHOLD MP_STATE_MEM(gc_alloc_amount) += n_blocks; #endif GC_EXIT(); #if MICROPY_GC_CONSERVATIVE_CLEAR // be conservative and zero out all the newly allocated blocks memset((byte *)ret_ptr, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK); #else // zero out the additional bytes of the newly allocated blocks // This is needed because the blocks may have previously held pointers // to the heap and will not be set to something else if the caller // doesn't actually use the entire block. As such they will continue // to point to the heap and may prevent other blocks from being reclaimed. memset((byte *)ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes); #endif #if MICROPY_ENABLE_FINALISER if (has_finaliser) { // clear type pointer in case it is never set ((mp_obj_base_t *)ret_ptr)->type = NULL; // set mp_obj flag only if it has a finaliser GC_ENTER(); FTB_SET(start_block); GC_EXIT(); } #else (void)has_finaliser; #endif #if EXTENSIVE_HEAP_PROFILING gc_dump_alloc_table(); #endif return ret_ptr; } /* void *gc_alloc(mp_uint_t n_bytes) { return _gc_alloc(n_bytes, false); } void *gc_alloc_with_finaliser(mp_uint_t n_bytes) { return _gc_alloc(n_bytes, true); } */ // force the freeing of a piece of memory // TODO: freeing here does not call finaliser void gc_free(void *ptr) { if (MP_STATE_THREAD(gc_lock_depth) > 0) { // TODO how to deal with this error? return; } GC_ENTER(); DEBUG_printf("gc_free(%p)\n", ptr); if (ptr == NULL) { GC_EXIT(); } else { // get the GC block number corresponding to this pointer assert(VERIFY_PTR(ptr)); size_t block = BLOCK_FROM_PTR(ptr); assert(ATB_GET_KIND(block) == AT_HEAD); #if MICROPY_ENABLE_FINALISER FTB_CLEAR(block); #endif // set the last_free pointer to this block if it's earlier in the heap if (block / BLOCKS_PER_ATB < MP_STATE_MEM(gc_last_free_atb_index)) { MP_STATE_MEM(gc_last_free_atb_index) = block / BLOCKS_PER_ATB; } // free head and all of its tail blocks do { ATB_ANY_TO_FREE(block); block += 1; } while (ATB_GET_KIND(block) == AT_TAIL); GC_EXIT(); #if EXTENSIVE_HEAP_PROFILING gc_dump_alloc_table(); #endif } } size_t gc_nbytes(const void *ptr) { GC_ENTER(); if (VERIFY_PTR(ptr)) { size_t block = BLOCK_FROM_PTR(ptr); if (ATB_GET_KIND(block) == AT_HEAD) { // work out number of consecutive blocks in the chain starting with this on size_t n_blocks = 0; do { n_blocks += 1; } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL); GC_EXIT(); return n_blocks * BYTES_PER_BLOCK; } } // invalid pointer GC_EXIT(); return 0; } #if 0 // old, simple realloc that didn't expand memory in place void *gc_realloc(void *ptr, mp_uint_t n_bytes) { mp_uint_t n_existing = gc_nbytes(ptr); if (n_bytes <= n_existing) { return ptr; } else { bool has_finaliser; if (ptr == NULL) { has_finaliser = false; } else { #if MICROPY_ENABLE_FINALISER has_finaliser = FTB_GET(BLOCK_FROM_PTR((mp_uint_t)ptr)); #else has_finaliser = false; #endif } void *ptr2 = gc_alloc(n_bytes, has_finaliser); if (ptr2 == NULL) { return ptr2; } memcpy(ptr2, ptr, n_existing); gc_free(ptr); return ptr2; } } #else // Alternative gc_realloc impl void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) { // check for pure allocation if (ptr_in == NULL) { return gc_alloc(n_bytes, false); } // check for pure free if (n_bytes == 0) { gc_free(ptr_in); return NULL; } if (MP_STATE_THREAD(gc_lock_depth) > 0) { return NULL; } void *ptr = ptr_in; GC_ENTER(); // get the GC block number corresponding to this pointer assert(VERIFY_PTR(ptr)); size_t block = BLOCK_FROM_PTR(ptr); assert(ATB_GET_KIND(block) == AT_HEAD); // compute number of new blocks that are requested size_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK; // Get the total number of consecutive blocks that are already allocated to // this chunk of memory, and then count the number of free blocks following // it. Stop if we reach the end of the heap, or if we find enough extra // free blocks to satisfy the realloc. Note that we need to compute the // total size of the existing memory chunk so we can correctly and // efficiently shrink it (see below for shrinking code). size_t n_free = 0; size_t n_blocks = 1; // counting HEAD block size_t max_block = MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; for (size_t bl = block + n_blocks; bl < max_block; bl++) { byte block_type = ATB_GET_KIND(bl); if (block_type == AT_TAIL) { n_blocks++; continue; } if (block_type == AT_FREE) { n_free++; if (n_blocks + n_free >= new_blocks) { // stop as soon as we find enough blocks for n_bytes break; } continue; } break; } // return original ptr if it already has the requested number of blocks if (new_blocks == n_blocks) { GC_EXIT(); return ptr_in; } // check if we can shrink the allocated area if (new_blocks < n_blocks) { // free unneeded tail blocks for (size_t bl = block + new_blocks, count = n_blocks - new_blocks; count > 0; bl++, count--) { ATB_ANY_TO_FREE(bl); } // set the last_free pointer to end of this block if it's earlier in the heap if ((block + new_blocks) / BLOCKS_PER_ATB < MP_STATE_MEM(gc_last_free_atb_index)) { MP_STATE_MEM(gc_last_free_atb_index) = (block + new_blocks) / BLOCKS_PER_ATB; } GC_EXIT(); #if EXTENSIVE_HEAP_PROFILING gc_dump_alloc_table(); #endif return ptr_in; } // check if we can expand in place if (new_blocks <= n_blocks + n_free) { // mark few more blocks as used tail for (size_t bl = block + n_blocks; bl < block + new_blocks; bl++) { assert(ATB_GET_KIND(bl) == AT_FREE); ATB_FREE_TO_TAIL(bl); } GC_EXIT(); #if MICROPY_GC_CONSERVATIVE_CLEAR // be conservative and zero out all the newly allocated blocks memset((byte *)ptr_in + n_blocks * BYTES_PER_BLOCK, 0, (new_blocks - n_blocks) * BYTES_PER_BLOCK); #else // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc) memset((byte *)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes); #endif #if EXTENSIVE_HEAP_PROFILING gc_dump_alloc_table(); #endif return ptr_in; } #if MICROPY_ENABLE_FINALISER bool ftb_state = FTB_GET(block); #else bool ftb_state = false; #endif GC_EXIT(); if (!allow_move) { // not allowed to move memory block so return failure return NULL; } // can't resize inplace; try to find a new contiguous chain void *ptr_out = gc_alloc(n_bytes, ftb_state); // check that the alloc succeeded if (ptr_out == NULL) { return NULL; } DEBUG_printf("gc_realloc(%p -> %p)\n", ptr_in, ptr_out); memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK); gc_free(ptr_in); return ptr_out; } #endif // Alternative gc_realloc impl void gc_dump_info(void) { gc_info_t info; gc_info(&info); mp_printf(&mp_plat_print, "GC: total: %u, used: %u, free: %u\n", (uint)info.total, (uint)info.used, (uint)info.free); mp_printf(&mp_plat_print, " No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", (uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free); } void gc_dump_alloc_table(void) { GC_ENTER(); static const size_t DUMP_BYTES_PER_LINE = 64; #if !EXTENSIVE_HEAP_PROFILING // When comparing heap output we don't want to print the starting // pointer of the heap because it changes from run to run. mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start)); #endif for (size_t bl = 0; bl < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; bl++) { if (bl % DUMP_BYTES_PER_LINE == 0) { // a new line of blocks { // check if this line contains only free blocks size_t bl2 = bl; while (bl2 < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB && ATB_GET_KIND(bl2) == AT_FREE) { bl2++; } if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) { // there are at least 2 lines containing only free blocks, so abbreviate their printing mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE); bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1)); if (bl >= MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB) { // got to end of heap break; } } } // print header for new line of blocks // (the cast to uint32_t is for 16-bit ports) // mp_printf(&mp_plat_print, "\n%05x: ", (uint)(PTR_FROM_BLOCK(bl) & (uint32_t)0xfffff)); mp_printf(&mp_plat_print, "\n%05x: ", (uint)((bl * BYTES_PER_BLOCK) & (uint32_t)0xfffff)); } int c = ' '; switch (ATB_GET_KIND(bl)) { case AT_FREE: c = '.'; break; /* this prints out if the object is reachable from BSS or STACK (for unix only) case AT_HEAD: { c = 'h'; void **ptrs = (void**)(void*)&mp_state_ctx; mp_uint_t len = offsetof(mp_state_ctx_t, vm.stack_top) / sizeof(mp_uint_t); for (mp_uint_t i = 0; i < len; i++) { mp_uint_t ptr = (mp_uint_t)ptrs[i]; if (VERIFY_PTR(ptr) && BLOCK_FROM_PTR(ptr) == bl) { c = 'B'; break; } } if (c == 'h') { ptrs = (void**)&c; len = ((mp_uint_t)MP_STATE_THREAD(stack_top) - (mp_uint_t)&c) / sizeof(mp_uint_t); for (mp_uint_t i = 0; i < len; i++) { mp_uint_t ptr = (mp_uint_t)ptrs[i]; if (VERIFY_PTR(ptr) && BLOCK_FROM_PTR(ptr) == bl) { c = 'S'; break; } } } break; } */ /* this prints the uPy object type of the head block */ case AT_HEAD: { void **ptr = (void **)(MP_STATE_MEM(gc_pool_start) + bl * BYTES_PER_BLOCK); if (*ptr == &mp_type_tuple) { c = 'T'; } else if (*ptr == &mp_type_list) { c = 'L'; } else if (*ptr == &mp_type_dict) { c = 'D'; } else if (*ptr == &mp_type_str || *ptr == &mp_type_bytes) { c = 'S'; } #if MICROPY_PY_BUILTINS_BYTEARRAY else if (*ptr == &mp_type_bytearray) { c = 'A'; } #endif #if MICROPY_PY_ARRAY else if (*ptr == &mp_type_array) { c = 'A'; } #endif #if MICROPY_PY_BUILTINS_FLOAT else if (*ptr == &mp_type_float) { c = 'F'; } #endif else if (*ptr == &mp_type_fun_bc) { c = 'B'; } else if (*ptr == &mp_type_module) { c = 'M'; } else { c = 'h'; #if 0 // This code prints "Q" for qstr-pool data, and "q" for qstr-str // data. It can be useful to see how qstrs are being allocated, // but is disabled by default because it is very slow. for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); c == 'h' && pool != NULL; pool = pool->prev) { if ((const qstr_pool_t *)ptr == pool) { c = 'Q'; break; } for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) { if ((const char *)ptr == *q) { c = 'q'; break; } } } #endif } break; } case AT_TAIL: c = '='; break; case AT_MARK: c = 'm'; break; } mp_printf(&mp_plat_print, "%c", c); } mp_print_str(&mp_plat_print, "\n"); GC_EXIT(); } #if 0 // For testing the GC functions void gc_test(void) { mp_uint_t len = 500; mp_uint_t *heap = malloc(len); gc_init(heap, heap + len / sizeof(mp_uint_t)); void *ptrs[100]; { mp_uint_t **p = gc_alloc(16, false); p[0] = gc_alloc(64, false); p[1] = gc_alloc(1, false); p[2] = gc_alloc(1, false); p[3] = gc_alloc(1, false); mp_uint_t ***p2 = gc_alloc(16, false); p2[0] = p; p2[1] = p; ptrs[0] = p2; } for (int i = 0; i < 25; i += 2) { mp_uint_t *p = gc_alloc(i, false); printf("p=%p\n", p); if (i & 3) { // ptrs[i] = p; } } printf("Before GC:\n"); gc_dump_alloc_table(); printf("Starting GC...\n"); gc_collect_start(); gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void *)); gc_collect_end(); printf("After GC:\n"); gc_dump_alloc_table(); } #endif #endif // MICROPY_ENABLE_GC #ifndef __IPU__ #include #endif #include "py/runtime.h" #if MICROPY_ENABLE_PYSTACK void mp_pystack_init(void *start, void *end) { MP_STATE_THREAD(pystack_start) = start; MP_STATE_THREAD(pystack_end) = end; MP_STATE_THREAD(pystack_cur) = start; } void *mp_pystack_alloc(size_t n_bytes) { n_bytes = (n_bytes + (MICROPY_PYSTACK_ALIGN - 1)) & ~(MICROPY_PYSTACK_ALIGN - 1); #if MP_PYSTACK_DEBUG n_bytes += MICROPY_PYSTACK_ALIGN; #endif if (MP_STATE_THREAD(pystack_cur) + n_bytes > MP_STATE_THREAD(pystack_end)) { // out of memory in the pystack mp_raise_type_arg(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_pystack_space_exhausted)); } void *ptr = MP_STATE_THREAD(pystack_cur); MP_STATE_THREAD(pystack_cur) += n_bytes; #if MP_PYSTACK_DEBUG *(size_t *)(MP_STATE_THREAD(pystack_cur) - MICROPY_PYSTACK_ALIGN) = n_bytes; #endif return ptr; } #endif #ifndef __IPU__ #include #endif #include #include #include "py/mpstate.h" #include "py/qstr.h" #include "py/gc.h" #include "py/runtime.h" // NOTE: we are using linear arrays to store and search for qstr's (unique strings, interned strings) // ultimately we will replace this with a static hash table of some kind #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_printf(...) (void)0 #endif // A qstr is an index into the qstr pool. // The data for a qstr is \0 terminated (so they can be printed using printf) #define Q_HASH_MASK ((1 << (8 * MICROPY_QSTR_BYTES_IN_HASH)) - 1) #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL #define QSTR_ENTER() mp_thread_mutex_lock(&MP_STATE_VM(qstr_mutex), 1) #define QSTR_EXIT() mp_thread_mutex_unlock(&MP_STATE_VM(qstr_mutex)) #else #define QSTR_ENTER() #define QSTR_EXIT() #endif // Initial number of entries for qstr pool, set so that the first dynamically // allocated pool is twice this size. The value here must be <= MP_QSTRnumber_of. #define MICROPY_ALLOC_QSTR_ENTRIES_INIT (10) // this must match the equivalent function in makeqstrdata.py mp_uint_t qstr_compute_hash(const byte *data, size_t len) { // djb2 algorithm; see http://www.cse.yorku.ca/~oz/hash.html mp_uint_t hash = 5381; for (const byte *top = data + len; data < top; data++) { hash = ((hash << 5) + hash) ^ (*data); // hash * 33 ^ data } hash &= Q_HASH_MASK; // Make sure that valid hash is never zero, zero means "hash not computed" if (hash == 0) { hash++; } return hash; } const qstr_hash_t mp_qstr_const_hashes[] = { #ifndef NO_QSTR #define QDEF(id, hash, len, str) hash, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif }; const qstr_len_t mp_qstr_const_lengths[] = { #ifndef NO_QSTR #define QDEF(id, hash, len, str) len, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif }; const qstr_pool_t mp_qstr_const_pool = { NULL, // no previous pool 0, // no previous pool MICROPY_ALLOC_QSTR_ENTRIES_INIT, MP_QSTRnumber_of, // corresponds to number of strings in array just below (qstr_hash_t *)mp_qstr_const_hashes, (qstr_len_t *)mp_qstr_const_lengths, { #ifndef NO_QSTR #define QDEF(id, hash, len, str) str, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif }, }; #ifdef MICROPY_QSTR_EXTRA_POOL extern const qstr_pool_t MICROPY_QSTR_EXTRA_POOL; #define CONST_POOL MICROPY_QSTR_EXTRA_POOL #else #define CONST_POOL mp_qstr_const_pool #endif void qstr_init(void) { MP_STATE_VM(last_pool) = (qstr_pool_t *)&CONST_POOL; // we won't modify the const_pool since it has no allocated room left MP_STATE_VM(qstr_last_chunk) = NULL; #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(qstr_mutex)); #endif } STATIC const qstr_pool_t *find_qstr(qstr *q) { // search pool for this qstr // total_prev_len==0 in the final pool, so the loop will always terminate const qstr_pool_t *pool = MP_STATE_VM(last_pool); while (*q < pool->total_prev_len) { pool = pool->prev; } *q -= pool->total_prev_len; assert(*q < pool->len); return pool; } // qstr_mutex must be taken while in this function STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) { DEBUG_printf("QSTR: add hash=%d len=%d data=%.*s\n", hash, len, len, q_ptr); // make sure we have room in the pool for a new qstr if (MP_STATE_VM(last_pool)->len >= MP_STATE_VM(last_pool)->alloc) { size_t new_alloc = MP_STATE_VM(last_pool)->alloc * 2; #ifdef MICROPY_QSTR_EXTRA_POOL // Put a lower bound on the allocation size in case the extra qstr pool has few entries new_alloc = MAX(MICROPY_ALLOC_QSTR_ENTRIES_INIT, new_alloc); #endif mp_uint_t pool_size = sizeof(qstr_pool_t) + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * new_alloc; qstr_pool_t *pool = (qstr_pool_t *)m_malloc_maybe(pool_size); if (pool == NULL) { // Keep qstr_last_chunk consistent with qstr_pool_t: qstr_last_chunk is not scanned // at garbage collection since it's reachable from a qstr_pool_t. And the caller of // this function expects q_ptr to be stored in a qstr_pool_t so it can be reached // by the collector. If qstr_pool_t allocation failed, qstr_last_chunk needs to be // NULL'd. Otherwise it may become a dangling pointer at the next garbage collection. MP_STATE_VM(qstr_last_chunk) = NULL; QSTR_EXIT(); m_malloc_fail(new_alloc); } pool->hashes = (qstr_hash_t *)(pool->qstrs + new_alloc); pool->lengths = (qstr_len_t *)(pool->hashes + new_alloc); pool->prev = MP_STATE_VM(last_pool); pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len; pool->alloc = new_alloc; pool->len = 0; MP_STATE_VM(last_pool) = pool; DEBUG_printf("QSTR: allocate new pool of size %d\n", MP_STATE_VM(last_pool)->alloc); } // add the new qstr mp_uint_t at = MP_STATE_VM(last_pool)->len; MP_STATE_VM(last_pool)->hashes[at] = hash; MP_STATE_VM(last_pool)->lengths[at] = len; MP_STATE_VM(last_pool)->qstrs[at] = q_ptr; MP_STATE_VM(last_pool)->len++; // return id for the newly-added qstr return MP_STATE_VM(last_pool)->total_prev_len + at; } qstr qstr_find_strn(const char *str, size_t str_len) { // work out hash of str mp_uint_t str_hash = qstr_compute_hash((const byte *)str, str_len); // search pools for the data for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL; pool = pool->prev) { for (mp_uint_t at = 0, top = pool->len; at < top; at++) { if (pool->hashes[at] == str_hash && pool->lengths[at] == str_len && memcmp(pool->qstrs[at], str, str_len) == 0) { return pool->total_prev_len + at; } } } // not found; return null qstr return 0; } qstr qstr_from_str(const char *str) { return qstr_from_strn(str, strlen(str)); } qstr qstr_from_strn(const char *str, size_t len) { QSTR_ENTER(); qstr q = qstr_find_strn(str, len); if (q == 0) { // qstr does not exist in interned pool so need to add it // check that len is not too big if (len >= (1 << (8 * MICROPY_QSTR_BYTES_IN_LEN))) { QSTR_EXIT(); mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); } // compute number of bytes needed to intern this string size_t n_bytes = len + 1; if (MP_STATE_VM(qstr_last_chunk) != NULL && MP_STATE_VM(qstr_last_used) + n_bytes > MP_STATE_VM(qstr_last_alloc)) { // not enough room at end of previously interned string so try to grow char *new_p = m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_alloc) + n_bytes, false); if (new_p == NULL) { // could not grow existing memory; shrink it to fit previous (void)m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_used), false); MP_STATE_VM(qstr_last_chunk) = NULL; } else { // could grow existing memory MP_STATE_VM(qstr_last_alloc) += n_bytes; } } if (MP_STATE_VM(qstr_last_chunk) == NULL) { // no existing memory for the interned string so allocate a new chunk size_t al = n_bytes; if (al < MICROPY_ALLOC_QSTR_CHUNK_INIT) { al = MICROPY_ALLOC_QSTR_CHUNK_INIT; } MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, al); if (MP_STATE_VM(qstr_last_chunk) == NULL) { // failed to allocate a large chunk so try with exact size MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, n_bytes); if (MP_STATE_VM(qstr_last_chunk) == NULL) { QSTR_EXIT(); m_malloc_fail(n_bytes); } al = n_bytes; } MP_STATE_VM(qstr_last_alloc) = al; MP_STATE_VM(qstr_last_used) = 0; } // allocate memory from the chunk for this new interned string's data char *q_ptr = MP_STATE_VM(qstr_last_chunk) + MP_STATE_VM(qstr_last_used); MP_STATE_VM(qstr_last_used) += n_bytes; // store the interned strings' data mp_uint_t hash = qstr_compute_hash((const byte *)str, len); memcpy(q_ptr, str, len); q_ptr[len] = '\0'; q = qstr_add(hash, len, q_ptr); } QSTR_EXIT(); return q; } mp_uint_t qstr_hash(qstr q) { const qstr_pool_t *pool = find_qstr(&q); return pool->hashes[q]; } size_t qstr_len(qstr q) { const qstr_pool_t *pool = find_qstr(&q); return pool->lengths[q]; } const char *qstr_str(qstr q) { const qstr_pool_t *pool = find_qstr(&q); return pool->qstrs[q]; } const byte *qstr_data(qstr q, size_t *len) { const qstr_pool_t *pool = find_qstr(&q); *len = pool->lengths[q]; return (byte *)pool->qstrs[q]; } void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, size_t *n_total_bytes) { QSTR_ENTER(); *n_pool = 0; *n_qstr = 0; *n_str_data_bytes = 0; *n_total_bytes = 0; for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { *n_pool += 1; *n_qstr += pool->len; for (qstr_len_t *l = pool->lengths, *l_top = pool->lengths + pool->len; l < l_top; l++) { *n_str_data_bytes += *l + 1; } #if MICROPY_ENABLE_GC *n_total_bytes += gc_nbytes(pool); // this counts actual bytes used in heap #else *n_total_bytes += sizeof(qstr_pool_t) + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * pool->alloc; #endif } *n_total_bytes += *n_str_data_bytes; QSTR_EXIT(); } #if MICROPY_PY_MICROPYTHON_MEM_INFO void qstr_dump_data(void) { QSTR_ENTER(); for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) { mp_printf(&mp_plat_print, "Q(%s)\n", *q); } } QSTR_EXIT(); } #endif #if MICROPY_ROM_TEXT_COMPRESSION #ifdef NO_QSTR // If NO_QSTR is set, it means we're doing QSTR extraction. // So we won't yet have "genhdr/compressed.data.h" #else // Emit the compressed_string_data string. #define MP_COMPRESSED_DATA(x) STATIC const char *compressed_string_data = x; #define MP_MATCH_COMPRESSED(a, b) #include "genhdr/compressed.data.h" #undef MP_COMPRESSED_DATA #undef MP_MATCH_COMPRESSED #endif // NO_QSTR // This implements the "common word" compression scheme (see makecompresseddata.py) where the most // common 128 words in error messages are replaced by their index into the list of common words. // The compressed string data is delimited by setting high bit in the final char of each word. // e.g. aaaa<0x80|a>bbbbbb<0x80|b>.... // This method finds the n'th string. STATIC const byte *find_uncompressed_string(uint8_t n) { const byte *c = (byte *)compressed_string_data; while (n > 0) { while ((*c & 0x80) == 0) { ++c; } ++c; --n; } return c; } // Given a compressed string in src, decompresses it into dst. // dst must be large enough (use MP_MAX_UNCOMPRESSED_TEXT_LEN+1). void mp_decompress_rom_string(byte *dst, const mp_rom_error_text_t src_chr) { // Skip past the 0xff marker. const byte *src = (byte *)src_chr + 1; // Need to add spaces around compressed words, except for the first (i.e. transition from 1<->2). // 0 = start, 1 = compressed, 2 = regular. int state = 0; while (*src) { if ((byte) * src >= 128) { if (state != 0) { *dst++ = ' '; } state = 1; // High bit set, replace with common word. const byte *word = find_uncompressed_string(*src & 0x7f); // The word is terminated by the final char having its high bit set. while ((*word & 0x80) == 0) { *dst++ = *word++; } *dst++ = (*word & 0x7f); } else { // Otherwise just copy one char. if (state == 1) { *dst++ = ' '; } state = 2; *dst++ = *src; } ++src; } // Add null-terminator. *dst = 0; } #endif // MICROPY_ROM_TEXT_COMPRESSION #ifndef __IPU__ #include #endif #include #include #include #include "py/mpconfig.h" #include "py/runtime.h" #include "py/mpprint.h" // returned value is always at least 1 greater than argument #define ROUND_ALLOC(a) (((a) & ((~0U) - 7)) + 8) // Init the vstr so it allocs exactly given number of bytes. Set length to zero. void vstr_init(vstr_t *vstr, size_t alloc) { if (alloc < 1) { alloc = 1; } vstr->alloc = alloc; vstr->len = 0; vstr->buf = m_new(char, vstr->alloc); vstr->fixed_buf = false; } // Init the vstr so it allocs exactly enough ram to hold a null-terminated // string of the given length, and set the length. void vstr_init_len(vstr_t *vstr, size_t len) { vstr_init(vstr, len + 1); vstr->len = len; } void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf) { vstr->alloc = alloc; vstr->len = 0; vstr->buf = buf; vstr->fixed_buf = true; } void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) { vstr_init(vstr, alloc); print->data = vstr; print->print_strn = (mp_print_strn_t)vstr_add_strn; } void vstr_clear(vstr_t *vstr) { if (!vstr->fixed_buf) { m_del(char, vstr->buf, vstr->alloc); } vstr->buf = NULL; } vstr_t *vstr_new(size_t alloc) { vstr_t *vstr = m_new_obj(vstr_t); vstr_init(vstr, alloc); return vstr; } void vstr_free(vstr_t *vstr) { if (vstr != NULL) { if (!vstr->fixed_buf) { m_del(char, vstr->buf, vstr->alloc); } m_del_obj(vstr_t, vstr); } } // Extend vstr strictly by requested size, return pointer to newly added chunk. char *vstr_extend(vstr_t *vstr, size_t size) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. mp_raise_msg(&mp_type_RuntimeError, NULL); } char *new_buf = m_renew(char, vstr->buf, vstr->alloc, vstr->alloc + size); char *p = new_buf + vstr->alloc; vstr->alloc += size; vstr->buf = new_buf; return p; } STATIC void vstr_ensure_extra(vstr_t *vstr, size_t size) { if (vstr->len + size > vstr->alloc) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. mp_raise_msg(&mp_type_RuntimeError, NULL); } size_t new_alloc = ROUND_ALLOC((vstr->len + size) + 16); char *new_buf = m_renew(char, vstr->buf, vstr->alloc, new_alloc); vstr->alloc = new_alloc; vstr->buf = new_buf; } } void vstr_hint_size(vstr_t *vstr, size_t size) { vstr_ensure_extra(vstr, size); } char *vstr_add_len(vstr_t *vstr, size_t len) { vstr_ensure_extra(vstr, len); char *buf = vstr->buf + vstr->len; vstr->len += len; return buf; } // Doesn't increase len, just makes sure there is a null byte at the end char *vstr_null_terminated_str(vstr_t *vstr) { // If there's no more room, add single byte if (vstr->alloc == vstr->len) { vstr_extend(vstr, 1); } vstr->buf[vstr->len] = '\0'; return vstr->buf; } void vstr_add_byte(vstr_t *vstr, byte b) { byte *buf = (byte *)vstr_add_len(vstr, 1); buf[0] = b; } void vstr_add_char(vstr_t *vstr, unichar c) { #if MICROPY_PY_BUILTINS_STR_UNICODE // TODO: Can this be simplified and deduplicated? // Is it worth just calling vstr_add_len(vstr, 4)? if (c < 0x80) { byte *buf = (byte *)vstr_add_len(vstr, 1); *buf = (byte)c; } else if (c < 0x800) { byte *buf = (byte *)vstr_add_len(vstr, 2); buf[0] = (c >> 6) | 0xC0; buf[1] = (c & 0x3F) | 0x80; } else if (c < 0x10000) { byte *buf = (byte *)vstr_add_len(vstr, 3); buf[0] = (c >> 12) | 0xE0; buf[1] = ((c >> 6) & 0x3F) | 0x80; buf[2] = (c & 0x3F) | 0x80; } else { assert(c < 0x110000); byte *buf = (byte *)vstr_add_len(vstr, 4); buf[0] = (c >> 18) | 0xF0; buf[1] = ((c >> 12) & 0x3F) | 0x80; buf[2] = ((c >> 6) & 0x3F) | 0x80; buf[3] = (c & 0x3F) | 0x80; } #else vstr_add_byte(vstr, c); #endif } void vstr_add_str(vstr_t *vstr, const char *str) { vstr_add_strn(vstr, str, strlen(str)); } void vstr_add_strn(vstr_t *vstr, const char *str, size_t len) { vstr_ensure_extra(vstr, len); memmove(vstr->buf + vstr->len, str, len); vstr->len += len; } STATIC char *vstr_ins_blank_bytes(vstr_t *vstr, size_t byte_pos, size_t byte_len) { size_t l = vstr->len; if (byte_pos > l) { byte_pos = l; } if (byte_len > 0) { // ensure room for the new bytes vstr_ensure_extra(vstr, byte_len); // copy up the string to make room for the new bytes memmove(vstr->buf + byte_pos + byte_len, vstr->buf + byte_pos, l - byte_pos); // increase the length vstr->len += byte_len; } return vstr->buf + byte_pos; } void vstr_ins_byte(vstr_t *vstr, size_t byte_pos, byte b) { char *s = vstr_ins_blank_bytes(vstr, byte_pos, 1); *s = b; } void vstr_ins_char(vstr_t *vstr, size_t char_pos, unichar chr) { // TODO UNICODE char *s = vstr_ins_blank_bytes(vstr, char_pos, 1); *s = chr; } void vstr_cut_head_bytes(vstr_t *vstr, size_t bytes_to_cut) { vstr_cut_out_bytes(vstr, 0, bytes_to_cut); } void vstr_cut_tail_bytes(vstr_t *vstr, size_t len) { if (len > vstr->len) { vstr->len = 0; } else { vstr->len -= len; } } void vstr_cut_out_bytes(vstr_t *vstr, size_t byte_pos, size_t bytes_to_cut) { if (byte_pos >= vstr->len) { return; } else if (byte_pos + bytes_to_cut >= vstr->len) { vstr->len = byte_pos; } else { memmove(vstr->buf + byte_pos, vstr->buf + byte_pos + bytes_to_cut, vstr->len - byte_pos - bytes_to_cut); vstr->len -= bytes_to_cut; } } void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap); int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); void vstr_printf(vstr_t *vstr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vstr_vprintf(vstr, fmt, ap); va_end(ap); } void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) { mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn}; mp_vprintf(&print, fmt, ap); } #include #include #include #ifndef __IPU__ #include #endif #include #include "py/mphal.h" #include "py/mpprint.h" #include "py/obj.h" #include "py/objint.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT #include "py/formatfloat.h" #endif static const char pad_spaces[] = " "; static const char pad_zeroes[] = "0000000000000000"; STATIC void plat_print_strn(void *env, const char *str, size_t len) { (void)env; MP_PLAT_PRINT_STRN(str, len); } const mp_print_t mp_plat_print = {NULL, plat_print_strn}; int mp_print_str(const mp_print_t *print, const char *str) { size_t len = strlen(str); if (len) { print->print_strn(print->data, str, len); } return len; } int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width) { int left_pad = 0; int right_pad = 0; int pad = width - len; int pad_size; int total_chars_printed = 0; const char *pad_chars; if (!fill || fill == ' ') { pad_chars = pad_spaces; pad_size = sizeof(pad_spaces) - 1; } else if (fill == '0') { pad_chars = pad_zeroes; pad_size = sizeof(pad_zeroes) - 1; } else { // Other pad characters are fairly unusual, so we'll take the hit // and output them 1 at a time. pad_chars = &fill; pad_size = 1; } if (flags & PF_FLAG_CENTER_ADJUST) { left_pad = pad / 2; right_pad = pad - left_pad; } else if (flags & PF_FLAG_LEFT_ADJUST) { right_pad = pad; } else { left_pad = pad; } if (left_pad > 0) { total_chars_printed += left_pad; while (left_pad > 0) { int p = left_pad; if (p > pad_size) { p = pad_size; } print->print_strn(print->data, pad_chars, p); left_pad -= p; } } if (len) { print->print_strn(print->data, str, len); total_chars_printed += len; } if (right_pad > 0) { total_chars_printed += right_pad; while (right_pad > 0) { int p = right_pad; if (p > pad_size) { p = pad_size; } print->print_strn(print->data, pad_chars, p); right_pad -= p; } } return total_chars_printed; } // 32-bits is 10 digits, add 3 for commas, 1 for sign, 1 for terminating null // We can use 16 characters for 32-bit and 32 characters for 64-bit #define INT_BUF_SIZE (sizeof(mp_int_t) * 4) // Our mp_vprintf function below does not support the '#' format modifier to // print the prefix of a non-base-10 number, so we don't need code for this. #define SUPPORT_INT_BASE_PREFIX (0) // This function is used exclusively by mp_vprintf to format ints. // It needs to be a separate function to mp_print_mp_int, since converting to a mp_int looses the MSB. STATIC int mp_print_int(const mp_print_t *print, mp_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width) { char sign = 0; if (sgn) { if ((mp_int_t)x < 0) { sign = '-'; x = -x; } else if (flags & PF_FLAG_SHOW_SIGN) { sign = '+'; } else if (flags & PF_FLAG_SPACE_SIGN) { sign = ' '; } } char buf[INT_BUF_SIZE]; char *b = buf + INT_BUF_SIZE; if (x == 0) { *(--b) = '0'; } else { do { int c = x % base; x /= base; if (c >= 10) { c += base_char - 10; } else { c += '0'; } *(--b) = c; } while (b > buf && x != 0); } #if SUPPORT_INT_BASE_PREFIX char prefix_char = '\0'; if (flags & PF_FLAG_SHOW_PREFIX) { if (base == 2) { prefix_char = base_char + 'b' - 'a'; } else if (base == 8) { prefix_char = base_char + 'o' - 'a'; } else if (base == 16) { prefix_char = base_char + 'x' - 'a'; } } #endif int len = 0; if (flags & PF_FLAG_PAD_AFTER_SIGN) { if (sign) { len += mp_print_strn(print, &sign, 1, flags, fill, 1); width--; } #if SUPPORT_INT_BASE_PREFIX if (prefix_char) { len += mp_print_strn(print, "0", 1, flags, fill, 1); len += mp_print_strn(print, &prefix_char, 1, flags, fill, 1); width -= 2; } #endif } else { #if SUPPORT_INT_BASE_PREFIX if (prefix_char && b > &buf[1]) { *(--b) = prefix_char; *(--b) = '0'; } #endif if (sign && b > buf) { *(--b) = sign; } } len += mp_print_strn(print, b, buf + INT_BUF_SIZE - b, flags, fill, width); return len; } int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec) { // These are the only values for "base" that are required to be supported by this // function, since Python only allows the user to format integers in these bases. // If needed this function could be generalised to handle other values. assert(base == 2 || base == 8 || base == 10 || base == 16); if (!mp_obj_is_int(x)) { // This will convert booleans to int, or raise an error for // non-integer types. x = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int(x)); } if ((flags & (PF_FLAG_LEFT_ADJUST | PF_FLAG_CENTER_ADJUST)) == 0 && fill == '0') { if (prec > width) { width = prec; } prec = 0; } char prefix_buf[4]; char *prefix = prefix_buf; if (mp_obj_int_sign(x) >= 0) { if (flags & PF_FLAG_SHOW_SIGN) { *prefix++ = '+'; } else if (flags & PF_FLAG_SPACE_SIGN) { *prefix++ = ' '; } } if (flags & PF_FLAG_SHOW_PREFIX) { if (base == 2) { *prefix++ = '0'; *prefix++ = base_char + 'b' - 'a'; } else if (base == 8) { *prefix++ = '0'; if (flags & PF_FLAG_SHOW_OCTAL_LETTER) { *prefix++ = base_char + 'o' - 'a'; } } else if (base == 16) { *prefix++ = '0'; *prefix++ = base_char + 'x' - 'a'; } } *prefix = '\0'; int prefix_len = prefix - prefix_buf; prefix = prefix_buf; char comma = '\0'; if (flags & PF_FLAG_SHOW_COMMA) { comma = ','; } // The size of this buffer is rather arbitrary. If it's not large // enough, a dynamic one will be allocated. char stack_buf[sizeof(mp_int_t) * 4]; char *buf = stack_buf; size_t buf_size = sizeof(stack_buf); size_t fmt_size = 0; char *str; if (prec > 1) { flags |= PF_FLAG_PAD_AFTER_SIGN; } char sign = '\0'; if (flags & PF_FLAG_PAD_AFTER_SIGN) { // We add the pad in this function, so since the pad goes after // the sign & prefix, we format without a prefix str = mp_obj_int_formatted(&buf, &buf_size, &fmt_size, x, base, NULL, base_char, comma); if (*str == '-') { sign = *str++; fmt_size--; } } else { str = mp_obj_int_formatted(&buf, &buf_size, &fmt_size, x, base, prefix, base_char, comma); } int spaces_before = 0; int spaces_after = 0; if (prec > 1) { // If prec was specified, then prec specifies the width to zero-pad the // the number to. This zero-padded number then gets left or right // aligned in width characters. int prec_width = fmt_size; // The digits if (prec_width < prec) { prec_width = prec; } if (flags & PF_FLAG_PAD_AFTER_SIGN) { if (sign) { prec_width++; } prec_width += prefix_len; } if (prec_width < width) { if (flags & PF_FLAG_LEFT_ADJUST) { spaces_after = width - prec_width; } else { spaces_before = width - prec_width; } } fill = '0'; flags &= ~PF_FLAG_LEFT_ADJUST; } int len = 0; if (spaces_before) { len += mp_print_strn(print, "", 0, 0, ' ', spaces_before); } if (flags & PF_FLAG_PAD_AFTER_SIGN) { // pad after sign implies pad after prefix as well. if (sign) { len += mp_print_strn(print, &sign, 1, 0, 0, 1); width--; } if (prefix_len) { len += mp_print_strn(print, prefix, prefix_len, 0, 0, 1); width -= prefix_len; } } if (prec > 1) { width = prec; } len += mp_print_strn(print, str, fmt_size, flags, fill, width); if (spaces_after) { len += mp_print_strn(print, "", 0, 0, ' ', spaces_after); } if (buf != stack_buf) { m_del(char, buf, buf_size); } return len; } #if MICROPY_PY_BUILTINS_FLOAT int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec) { char buf[32]; char sign = '\0'; int chrs = 0; if (flags & PF_FLAG_SHOW_SIGN) { sign = '+'; } else if (flags & PF_FLAG_SPACE_SIGN) { sign = ' '; } int len = mp_format_float(f, buf, sizeof(buf), fmt, prec, sign); char *s = buf; if ((flags & PF_FLAG_ADD_PERCENT) && (size_t)(len + 1) < sizeof(buf)) { buf[len++] = '%'; buf[len] = '\0'; } // buf[0] < '0' returns true if the first character is space, + or - if ((flags & PF_FLAG_PAD_AFTER_SIGN) && buf[0] < '0') { // We have a sign character s++; chrs += mp_print_strn(print, &buf[0], 1, 0, 0, 1); width--; len--; } chrs += mp_print_strn(print, s, len, flags, fill, width); return chrs; } #endif int mp_printf(const mp_print_t *print, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = mp_vprintf(print, fmt, ap); va_end(ap); return ret; } int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { int chrs = 0; for (;;) { { const char *f = fmt; while (*f != '\0' && *f != '%') { ++f; // XXX UTF8 advance char } if (f > fmt) { print->print_strn(print->data, fmt, f - fmt); chrs += f - fmt; fmt = f; } } if (*fmt == '\0') { break; } // move past % character ++fmt; // parse flags, if they exist int flags = 0; char fill = ' '; while (*fmt != '\0') { if (*fmt == '-') { flags |= PF_FLAG_LEFT_ADJUST; } else if (*fmt == '+') { flags |= PF_FLAG_SHOW_SIGN; } else if (*fmt == ' ') { flags |= PF_FLAG_SPACE_SIGN; } else if (*fmt == '!') { flags |= PF_FLAG_NO_TRAILZ; } else if (*fmt == '0') { flags |= PF_FLAG_PAD_AFTER_SIGN; fill = '0'; } else { break; } ++fmt; } // parse width, if it exists int width = 0; for (; '0' <= *fmt && *fmt <= '9'; ++fmt) { width = width * 10 + *fmt - '0'; } // parse precision, if it exists int prec = -1; if (*fmt == '.') { ++fmt; if (*fmt == '*') { ++fmt; prec = va_arg(args, int); } else { prec = 0; for (; '0' <= *fmt && *fmt <= '9'; ++fmt) { prec = prec * 10 + *fmt - '0'; } } if (prec < 0) { prec = 0; } } // parse long specifiers (only for LP64 model where they make a difference) #ifndef __LP64__ const #endif bool long_arg = false; if (*fmt == 'l') { ++fmt; #ifdef __LP64__ long_arg = true; #endif } if (*fmt == '\0') { break; } switch (*fmt) { case 'b': if (va_arg(args, int)) { chrs += mp_print_strn(print, "true", 4, flags, fill, width); } else { chrs += mp_print_strn(print, "false", 5, flags, fill, width); } break; case 'c': { char str = va_arg(args, int); chrs += mp_print_strn(print, &str, 1, flags, fill, width); break; } case 'q': { qstr qst = va_arg(args, qstr); size_t len; const char *str = (const char *)qstr_data(qst, &len); if (prec >= 0 && (size_t)prec < len) { len = prec; } chrs += mp_print_strn(print, str, len, flags, fill, width); break; } case 's': { const char *str = va_arg(args, const char *); #ifndef NDEBUG // With debugging enabled, catch printing of null string pointers if (prec != 0 && str == NULL) { chrs += mp_print_strn(print, "(null)", 6, flags, fill, width); break; } #endif size_t len = strlen(str); if (prec >= 0 && (size_t)prec < len) { len = prec; } chrs += mp_print_strn(print, str, len, flags, fill, width); break; } case 'd': { mp_int_t val; if (long_arg) { val = va_arg(args, long int); } else { val = va_arg(args, int); } chrs += mp_print_int(print, val, 1, 10, 'a', flags, fill, width); break; } case 'u': case 'x': case 'X': { int base = 16 - ((*fmt + 1) & 6); // maps char u/x/X to base 10/16/16 char fmt_c = (*fmt & 0xf0) - 'P' + 'A'; // maps char u/x/X to char a/a/A mp_uint_t val; if (long_arg) { val = va_arg(args, unsigned long int); } else { val = va_arg(args, unsigned int); } chrs += mp_print_int(print, val, 0, base, fmt_c, flags, fill, width); break; } case 'p': case 'P': // don't bother to handle upcase for 'P' // Use unsigned long int to work on both ILP32 and LP64 systems chrs += mp_print_int(print, va_arg(args, unsigned long int), 0, 16, 'a', flags, fill, width); break; #if MICROPY_PY_BUILTINS_FLOAT case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': { #if ((MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT) || (MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE)) mp_float_t f = (mp_float_t)va_arg(args, double); chrs += mp_print_float(print, f, *fmt, flags, fill, width, prec); #else #error Unknown MICROPY FLOAT IMPL #endif break; } #endif // Because 'l' is eaten above, another 'l' means %ll. We need to support // this length specifier for OBJ_REPR_D (64-bit NaN boxing). // TODO Either enable this unconditionally, or provide a specific config var. #if (MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D) || defined(_WIN64) case 'l': { unsigned long long int arg_value = va_arg(args, unsigned long long int); ++fmt; assert(*fmt == 'u' || *fmt == 'd' || !"unsupported fmt char"); chrs += mp_print_int(print, arg_value, *fmt == 'd', 10, 'a', flags, fill, width); break; } #endif default: // if it's not %% then it's an unsupported format character assert(*fmt == '%' || !"unsupported fmt char"); print->print_strn(print->data, fmt, 1); chrs += 1; break; } ++fmt; } return chrs; } #include #include "py/unicode.h" // attribute flags #define FL_PRINT (0x01) #define FL_SPACE (0x02) #define FL_DIGIT (0x04) #define FL_ALPHA (0x08) #define FL_UPPER (0x10) #define FL_LOWER (0x20) #define FL_XDIGIT (0x40) // shorthand character attributes #define AT_PR (FL_PRINT) #define AT_SP (FL_SPACE | FL_PRINT) #define AT_DI (FL_DIGIT | FL_PRINT | FL_XDIGIT) #define AT_AL (FL_ALPHA | FL_PRINT) #define AT_UP (FL_UPPER | FL_ALPHA | FL_PRINT) #define AT_LO (FL_LOWER | FL_ALPHA | FL_PRINT) #define AT_UX (FL_UPPER | FL_ALPHA | FL_PRINT | FL_XDIGIT) #define AT_LX (FL_LOWER | FL_ALPHA | FL_PRINT | FL_XDIGIT) // table of attributes for ascii characters STATIC const uint8_t attr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, AT_SP, AT_SP, AT_SP, AT_SP, AT_SP, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, AT_SP, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_DI, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_UX, AT_UX, AT_UX, AT_UX, AT_UX, AT_UX, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_UP, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_PR, AT_LX, AT_LX, AT_LX, AT_LX, AT_LX, AT_LX, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_LO, AT_PR, AT_PR, AT_PR, AT_PR, 0 }; #if MICROPY_PY_BUILTINS_STR_UNICODE unichar utf8_get_char(const byte *s) { unichar ord = *s++; if (!UTF8_IS_NONASCII(ord)) { return ord; } ord &= 0x7F; for (unichar mask = 0x40; ord & mask; mask >>= 1) { ord &= ~mask; } while (UTF8_IS_CONT(*s)) { ord = (ord << 6) | (*s++ & 0x3F); } return ord; } const byte *utf8_next_char(const byte *s) { ++s; while (UTF8_IS_CONT(*s)) { ++s; } return s; } mp_uint_t utf8_ptr_to_index(const byte *s, const byte *ptr) { mp_uint_t i = 0; while (ptr > s) { if (!UTF8_IS_CONT(*--ptr)) { i++; } } return i; } size_t utf8_charlen(const byte *str, size_t len) { size_t charlen = 0; for (const byte *top = str + len; str < top; ++str) { if (!UTF8_IS_CONT(*str)) { ++charlen; } } return charlen; } #endif // Be aware: These unichar_is* functions are actually ASCII-only! bool unichar_isspace(unichar c) { return c < 128 && (attr[c] & FL_SPACE) != 0; } bool unichar_isalpha(unichar c) { return c < 128 && (attr[c] & FL_ALPHA) != 0; } /* unused bool unichar_isprint(unichar c) { return c < 128 && (attr[c] & FL_PRINT) != 0; } */ bool unichar_isdigit(unichar c) { return c < 128 && (attr[c] & FL_DIGIT) != 0; } bool unichar_isxdigit(unichar c) { return c < 128 && (attr[c] & FL_XDIGIT) != 0; } bool unichar_isident(unichar c) { return c < 128 && ((attr[c] & (FL_ALPHA | FL_DIGIT)) != 0 || c == '_'); } bool unichar_isalnum(unichar c) { return c < 128 && ((attr[c] & (FL_ALPHA | FL_DIGIT)) != 0); } bool unichar_isupper(unichar c) { return c < 128 && (attr[c] & FL_UPPER) != 0; } bool unichar_islower(unichar c) { return c < 128 && (attr[c] & FL_LOWER) != 0; } unichar unichar_tolower(unichar c) { if (unichar_isupper(c)) { return c + 0x20; } return c; } unichar unichar_toupper(unichar c) { if (unichar_islower(c)) { return c - 0x20; } return c; } mp_uint_t unichar_xdigit_value(unichar c) { // c is assumed to be hex digit mp_uint_t n = c - '0'; if (n > 9) { n &= ~('a' - 'A'); n -= ('A' - ('9' + 1)); } return n; } #if MICROPY_PY_BUILTINS_STR_UNICODE bool utf8_check(const byte *p, size_t len) { uint8_t need = 0; const byte *end = p + len; for (; p < end; p++) { byte c = *p; if (need) { if (UTF8_IS_CONT(c)) { need--; } else { // mismatch return 0; } } else { if (c >= 0xc0) { if (c >= 0xf8) { // mismatch return 0; } need = (0xe5 >> ((c >> 3) & 0x6)) & 3; } else if (c >= 0x80) { // mismatch return 0; } } } return need == 0; // no pending fragments allowed } #endif #include #include #include "py/mpz.h" #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ #define DIG_SIZE (MPZ_DIG_SIZE) #define DIG_MASK ((MPZ_LONG_1 << DIG_SIZE) - 1) #define DIG_MSB (MPZ_LONG_1 << (DIG_SIZE - 1)) #define DIG_BASE (MPZ_LONG_1 << DIG_SIZE) /* mpz is an arbitrary precision integer type with a public API. mpn functions act on non-negative integers represented by an array of generalised digits (eg a word per digit). You also need to specify separately the length of the array. There is no public API for mpn. Rather, the functions are used by mpz to implement its features. Integer values are stored little endian (first digit is first in memory). Definition of normalise: ? */ STATIC size_t mpn_remove_trailing_zeros(mpz_dig_t *oidig, mpz_dig_t *idig) { for (--idig; idig >= oidig && *idig == 0; --idig) { } return idig + 1 - oidig; } /* compares i with j returns sign(i - j) assumes i, j are normalised */ STATIC int mpn_cmp(const mpz_dig_t *idig, size_t ilen, const mpz_dig_t *jdig, size_t jlen) { if (ilen < jlen) { return -1; } if (ilen > jlen) { return 1; } for (idig += ilen, jdig += ilen; ilen > 0; --ilen) { mpz_dbl_dig_signed_t cmp = (mpz_dbl_dig_t)*(--idig) - (mpz_dbl_dig_t)*(--jdig); if (cmp < 0) { return -1; } if (cmp > 0) { return 1; } } return 0; } /* computes i = j << n returns number of digits in i assumes enough memory in i; assumes normalised j; assumes n > 0 can have i, j pointing to same memory */ STATIC size_t mpn_shl(mpz_dig_t *idig, mpz_dig_t *jdig, size_t jlen, mp_uint_t n) { mp_uint_t n_whole = (n + DIG_SIZE - 1) / DIG_SIZE; mp_uint_t n_part = n % DIG_SIZE; if (n_part == 0) { n_part = DIG_SIZE; } // start from the high end of the digit arrays idig += jlen + n_whole - 1; jdig += jlen - 1; // shift the digits mpz_dbl_dig_t d = 0; for (size_t i = jlen; i > 0; i--, idig--, jdig--) { d |= *jdig; *idig = (d >> (DIG_SIZE - n_part)) & DIG_MASK; d <<= DIG_SIZE; } // store remaining bits *idig = (d >> (DIG_SIZE - n_part)) & DIG_MASK; idig -= n_whole - 1; memset(idig, 0, (n_whole - 1) * sizeof(mpz_dig_t)); // work out length of result jlen += n_whole; while (jlen != 0 && idig[jlen - 1] == 0) { jlen--; } // return length of result return jlen; } /* computes i = j >> n returns number of digits in i assumes enough memory in i; assumes normalised j; assumes n > 0 can have i, j pointing to same memory */ STATIC size_t mpn_shr(mpz_dig_t *idig, mpz_dig_t *jdig, size_t jlen, mp_uint_t n) { mp_uint_t n_whole = n / DIG_SIZE; mp_uint_t n_part = n % DIG_SIZE; if (n_whole >= jlen) { return 0; } jdig += n_whole; jlen -= n_whole; for (size_t i = jlen; i > 0; i--, idig++, jdig++) { mpz_dbl_dig_t d = *jdig; if (i > 1) { d |= (mpz_dbl_dig_t)jdig[1] << DIG_SIZE; } d >>= n_part; *idig = d & DIG_MASK; } if (idig[-1] == 0) { jlen--; } return jlen; } /* computes i = j + k returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes jlen >= klen can have i, j, k pointing to same memory */ STATIC size_t mpn_add(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; mpz_dbl_dig_t carry = 0; jlen -= klen; for (; klen > 0; --klen, ++idig, ++jdig, ++kdig) { carry += (mpz_dbl_dig_t)*jdig + (mpz_dbl_dig_t)*kdig; *idig = carry & DIG_MASK; carry >>= DIG_SIZE; } for (; jlen > 0; --jlen, ++idig, ++jdig) { carry += *jdig; *idig = carry & DIG_MASK; carry >>= DIG_SIZE; } if (carry != 0) { *idig++ = carry; } return idig - oidig; } /* computes i = j - k returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes j >= k can have i, j, k pointing to same memory */ STATIC size_t mpn_sub(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; mpz_dbl_dig_signed_t borrow = 0; jlen -= klen; for (; klen > 0; --klen, ++idig, ++jdig, ++kdig) { borrow += (mpz_dbl_dig_t)*jdig - (mpz_dbl_dig_t)*kdig; *idig = borrow & DIG_MASK; borrow >>= DIG_SIZE; } for (; jlen > 0; --jlen, ++idig, ++jdig) { borrow += *jdig; *idig = borrow & DIG_MASK; borrow >>= DIG_SIZE; } return mpn_remove_trailing_zeros(oidig, idig); } #if MICROPY_OPT_MPZ_BITWISE /* computes i = j & k returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes jlen >= klen (jlen argument not needed) can have i, j, k pointing to same memory */ STATIC size_t mpn_and(mpz_dig_t *idig, const mpz_dig_t *jdig, const mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; for (; klen > 0; --klen, ++idig, ++jdig, ++kdig) { *idig = *jdig & *kdig; } return mpn_remove_trailing_zeros(oidig, idig); } #endif /* i = -((-j) & (-k)) = ~((~j + 1) & (~k + 1)) + 1 i = (j & (-k)) = (j & (~k + 1)) = ( j & (~k + 1)) i = ((-j) & k) = ((~j + 1) & k) = ((~j + 1) & k ) computes general form: i = (im ^ (((j ^ jm) + jc) & ((k ^ km) + kc))) + ic where Xm = Xc == 0 ? 0 : DIG_MASK returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes length j >= length k can have i, j, k pointing to same memory */ STATIC size_t mpn_and_neg(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen, mpz_dbl_dig_t carryi, mpz_dbl_dig_t carryj, mpz_dbl_dig_t carryk) { mpz_dig_t *oidig = idig; mpz_dig_t imask = (0 == carryi) ? 0 : DIG_MASK; mpz_dig_t jmask = (0 == carryj) ? 0 : DIG_MASK; mpz_dig_t kmask = (0 == carryk) ? 0 : DIG_MASK; for (; jlen > 0; ++idig, ++jdig) { carryj += *jdig ^ jmask; carryk += (--klen <= --jlen) ? (*kdig++ ^ kmask) : kmask; carryi += ((carryj & carryk) ^ imask) & DIG_MASK; *idig = carryi & DIG_MASK; carryk >>= DIG_SIZE; carryj >>= DIG_SIZE; carryi >>= DIG_SIZE; } if (0 != carryi) { *idig++ = carryi; } return mpn_remove_trailing_zeros(oidig, idig); } #if MICROPY_OPT_MPZ_BITWISE /* computes i = j | k returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes jlen >= klen can have i, j, k pointing to same memory */ STATIC size_t mpn_or(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; jlen -= klen; for (; klen > 0; --klen, ++idig, ++jdig, ++kdig) { *idig = *jdig | *kdig; } for (; jlen > 0; --jlen, ++idig, ++jdig) { *idig = *jdig; } return idig - oidig; } #endif /* i = -((-j) | (-k)) = ~((~j + 1) | (~k + 1)) + 1 i = -(j | (-k)) = -(j | (~k + 1)) = ~( j | (~k + 1)) + 1 i = -((-j) | k) = -((~j + 1) | k) = ~((~j + 1) | k ) + 1 computes general form: i = ~(((j ^ jm) + jc) | ((k ^ km) + kc)) + 1 where Xm = Xc == 0 ? 0 : DIG_MASK returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes length j >= length k can have i, j, k pointing to same memory */ #if MICROPY_OPT_MPZ_BITWISE STATIC size_t mpn_or_neg(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen, mpz_dbl_dig_t carryj, mpz_dbl_dig_t carryk) { mpz_dig_t *oidig = idig; mpz_dbl_dig_t carryi = 1; mpz_dig_t jmask = (0 == carryj) ? 0 : DIG_MASK; mpz_dig_t kmask = (0 == carryk) ? 0 : DIG_MASK; for (; jlen > 0; ++idig, ++jdig) { carryj += *jdig ^ jmask; carryk += (--klen <= --jlen) ? (*kdig++ ^ kmask) : kmask; carryi += ((carryj | carryk) ^ DIG_MASK) & DIG_MASK; *idig = carryi & DIG_MASK; carryk >>= DIG_SIZE; carryj >>= DIG_SIZE; carryi >>= DIG_SIZE; } // At least one of j,k must be negative so the above for-loop runs at least // once. For carryi to be non-zero here it must be equal to 1 at the end of // each iteration of the loop. So the accumulation of carryi must overflow // each time, ie carryi += 0xff..ff. So carryj|carryk must be 0 in the // DIG_MASK bits on each iteration. But considering all cases of signs of // j,k one sees that this is not possible. assert(carryi == 0); return mpn_remove_trailing_zeros(oidig, idig); } #else STATIC size_t mpn_or_neg(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen, mpz_dbl_dig_t carryi, mpz_dbl_dig_t carryj, mpz_dbl_dig_t carryk) { mpz_dig_t *oidig = idig; mpz_dig_t imask = (0 == carryi) ? 0 : DIG_MASK; mpz_dig_t jmask = (0 == carryj) ? 0 : DIG_MASK; mpz_dig_t kmask = (0 == carryk) ? 0 : DIG_MASK; for (; jlen > 0; ++idig, ++jdig) { carryj += *jdig ^ jmask; carryk += (--klen <= --jlen) ? (*kdig++ ^ kmask) : kmask; carryi += ((carryj | carryk) ^ imask) & DIG_MASK; *idig = carryi & DIG_MASK; carryk >>= DIG_SIZE; carryj >>= DIG_SIZE; carryi >>= DIG_SIZE; } // See comment in above mpn_or_neg for why carryi must be 0. assert(carryi == 0); return mpn_remove_trailing_zeros(oidig, idig); } #endif #if MICROPY_OPT_MPZ_BITWISE /* computes i = j ^ k returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes jlen >= klen can have i, j, k pointing to same memory */ STATIC size_t mpn_xor(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; jlen -= klen; for (; klen > 0; --klen, ++idig, ++jdig, ++kdig) { *idig = *jdig ^ *kdig; } for (; jlen > 0; --jlen, ++idig, ++jdig) { *idig = *jdig; } return mpn_remove_trailing_zeros(oidig, idig); } #endif /* i = (-j) ^ (-k) = ~(j - 1) ^ ~(k - 1) = (j - 1) ^ (k - 1) i = -(j ^ (-k)) = -(j ^ ~(k - 1)) = ~(j ^ ~(k - 1)) + 1 = (j ^ (k - 1)) + 1 i = -((-j) ^ k) = -(~(j - 1) ^ k) = ~(~(j - 1) ^ k) + 1 = ((j - 1) ^ k) + 1 computes general form: i = ((j - 1 + jc) ^ (k - 1 + kc)) + ic returns number of digits in i assumes enough memory in i; assumes normalised j, k; assumes length j >= length k can have i, j, k pointing to same memory */ STATIC size_t mpn_xor_neg(mpz_dig_t *idig, const mpz_dig_t *jdig, size_t jlen, const mpz_dig_t *kdig, size_t klen, mpz_dbl_dig_t carryi, mpz_dbl_dig_t carryj, mpz_dbl_dig_t carryk) { mpz_dig_t *oidig = idig; for (; jlen > 0; ++idig, ++jdig) { carryj += *jdig + DIG_MASK; carryk += (--klen <= --jlen) ? (*kdig++ + DIG_MASK) : DIG_MASK; carryi += (carryj ^ carryk) & DIG_MASK; *idig = carryi & DIG_MASK; carryk >>= DIG_SIZE; carryj >>= DIG_SIZE; carryi >>= DIG_SIZE; } if (0 != carryi) { *idig++ = carryi; } return mpn_remove_trailing_zeros(oidig, idig); } /* computes i = i * d1 + d2 returns number of digits in i assumes enough memory in i; assumes normalised i; assumes dmul != 0 */ STATIC size_t mpn_mul_dig_add_dig(mpz_dig_t *idig, size_t ilen, mpz_dig_t dmul, mpz_dig_t dadd) { mpz_dig_t *oidig = idig; mpz_dbl_dig_t carry = dadd; for (; ilen > 0; --ilen, ++idig) { carry += (mpz_dbl_dig_t)*idig * (mpz_dbl_dig_t)dmul; // will never overflow so long as DIG_SIZE <= 8*sizeof(mpz_dbl_dig_t)/2 *idig = carry & DIG_MASK; carry >>= DIG_SIZE; } if (carry != 0) { *idig++ = carry; } return idig - oidig; } /* computes i = j * k returns number of digits in i assumes enough memory in i; assumes i is zeroed; assumes normalised j, k can have j, k point to same memory */ STATIC size_t mpn_mul(mpz_dig_t *idig, mpz_dig_t *jdig, size_t jlen, mpz_dig_t *kdig, size_t klen) { mpz_dig_t *oidig = idig; size_t ilen = 0; for (; klen > 0; --klen, ++idig, ++kdig) { mpz_dig_t *id = idig; mpz_dbl_dig_t carry = 0; size_t jl = jlen; for (mpz_dig_t *jd = jdig; jl > 0; --jl, ++jd, ++id) { carry += (mpz_dbl_dig_t)*id + (mpz_dbl_dig_t)*jd * (mpz_dbl_dig_t)*kdig; // will never overflow so long as DIG_SIZE <= 8*sizeof(mpz_dbl_dig_t)/2 *id = carry & DIG_MASK; carry >>= DIG_SIZE; } if (carry != 0) { *id++ = carry; } ilen = id - oidig; } return ilen; } /* natural_div - quo * den + new_num = old_num (ie num is replaced with rem) assumes den != 0 assumes num_dig has enough memory to be extended by 1 digit assumes quo_dig has enough memory (as many digits as num) assumes quo_dig is filled with zeros */ STATIC void mpn_div(mpz_dig_t *num_dig, size_t *num_len, const mpz_dig_t *den_dig, size_t den_len, mpz_dig_t *quo_dig, size_t *quo_len) { mpz_dig_t *orig_num_dig = num_dig; mpz_dig_t *orig_quo_dig = quo_dig; mpz_dig_t norm_shift = 0; mpz_dbl_dig_t lead_den_digit; // handle simple cases { int cmp = mpn_cmp(num_dig, *num_len, den_dig, den_len); if (cmp == 0) { *num_len = 0; quo_dig[0] = 1; *quo_len = 1; return; } else if (cmp < 0) { // numerator remains the same *quo_len = 0; return; } } // We need to normalise the denominator (leading bit of leading digit is 1) // so that the division routine works. Since the denominator memory is // read-only we do the normalisation on the fly, each time a digit of the // denominator is needed. We need to know is how many bits to shift by. // count number of leading zeros in leading digit of denominator { mpz_dig_t d = den_dig[den_len - 1]; while ((d & DIG_MSB) == 0) { d <<= 1; ++norm_shift; } } // now need to shift numerator by same amount as denominator // first, increase length of numerator in case we need more room to shift num_dig[*num_len] = 0; ++(*num_len); for (mpz_dig_t *num = num_dig, carry = 0; num < num_dig + *num_len; ++num) { mpz_dig_t n = *num; *num = ((n << norm_shift) | carry) & DIG_MASK; carry = (mpz_dbl_dig_t)n >> (DIG_SIZE - norm_shift); } // cache the leading digit of the denominator lead_den_digit = (mpz_dbl_dig_t)den_dig[den_len - 1] << norm_shift; if (den_len >= 2) { lead_den_digit |= (mpz_dbl_dig_t)den_dig[den_len - 2] >> (DIG_SIZE - norm_shift); } // point num_dig to last digit in numerator num_dig += *num_len - 1; // calculate number of digits in quotient *quo_len = *num_len - den_len; // point to last digit to store for quotient quo_dig += *quo_len - 1; // keep going while we have enough digits to divide while (*num_len > den_len) { mpz_dbl_dig_t quo = ((mpz_dbl_dig_t)*num_dig << DIG_SIZE) | num_dig[-1]; // get approximate quotient quo /= lead_den_digit; // Multiply quo by den and subtract from num to get remainder. // Must be careful with overflow of the borrow variable. Both // borrow and low_digs are signed values and need signed right-shift, // but x is unsigned and may take a full-range value. const mpz_dig_t *d = den_dig; mpz_dbl_dig_t d_norm = 0; mpz_dbl_dig_signed_t borrow = 0; for (mpz_dig_t *n = num_dig - den_len; n < num_dig; ++n, ++d) { // Get the next digit in (den). d_norm = ((mpz_dbl_dig_t)*d << norm_shift) | (d_norm >> DIG_SIZE); // Multiply the next digit in (quo * den). mpz_dbl_dig_t x = (mpz_dbl_dig_t)quo * (d_norm & DIG_MASK); // Compute the low DIG_MASK bits of the next digit in (num - quo * den) mpz_dbl_dig_signed_t low_digs = (borrow & DIG_MASK) + *n - (x & DIG_MASK); // Store the digit result for (num). *n = low_digs & DIG_MASK; // Compute the borrow, shifted right before summing to avoid overflow. borrow = (borrow >> DIG_SIZE) - (x >> DIG_SIZE) + (low_digs >> DIG_SIZE); } // At this point we have either: // // 1. quo was the correct value and the most-sig-digit of num is exactly // cancelled by borrow (borrow + *num_dig == 0). In this case there is // nothing more to do. // // 2. quo was too large, we subtracted too many den from num, and the // most-sig-digit of num is less than needed (borrow + *num_dig < 0). // In this case we must reduce quo and add back den to num until the // carry from this operation cancels out the borrow. // borrow += *num_dig; for (; borrow != 0; --quo) { d = den_dig; d_norm = 0; mpz_dbl_dig_t carry = 0; for (mpz_dig_t *n = num_dig - den_len; n < num_dig; ++n, ++d) { d_norm = ((mpz_dbl_dig_t)*d << norm_shift) | (d_norm >> DIG_SIZE); carry += (mpz_dbl_dig_t)*n + (d_norm & DIG_MASK); *n = carry & DIG_MASK; carry >>= DIG_SIZE; } borrow += carry; } // store this digit of the quotient *quo_dig = quo & DIG_MASK; --quo_dig; // move down to next digit of numerator --num_dig; --(*num_len); } // unnormalise numerator (remainder now) for (mpz_dig_t *num = orig_num_dig + *num_len - 1, carry = 0; num >= orig_num_dig; --num) { mpz_dig_t n = *num; *num = ((n >> norm_shift) | carry) & DIG_MASK; carry = (mpz_dbl_dig_t)n << (DIG_SIZE - norm_shift); } // strip trailing zeros while (*quo_len > 0 && orig_quo_dig[*quo_len - 1] == 0) { --(*quo_len); } while (*num_len > 0 && orig_num_dig[*num_len - 1] == 0) { --(*num_len); } } #define MIN_ALLOC (2) void mpz_init_zero(mpz_t *z) { z->neg = 0; z->fixed_dig = 0; z->alloc = 0; z->len = 0; z->dig = NULL; } void mpz_init_from_int(mpz_t *z, mp_int_t val) { mpz_init_zero(z); mpz_set_from_int(z, val); } void mpz_init_fixed_from_int(mpz_t *z, mpz_dig_t *dig, size_t alloc, mp_int_t val) { z->neg = 0; z->fixed_dig = 1; z->alloc = alloc; z->len = 0; z->dig = dig; mpz_set_from_int(z, val); } void mpz_deinit(mpz_t *z) { if (z != NULL && !z->fixed_dig) { m_del(mpz_dig_t, z->dig, z->alloc); } } #if 0 these functions are unused mpz_t *mpz_zero(void) { mpz_t *z = m_new_obj(mpz_t); mpz_init_zero(z); return z; } mpz_t *mpz_from_int(mp_int_t val) { mpz_t *z = mpz_zero(); mpz_set_from_int(z, val); return z; } mpz_t *mpz_from_ll(long long val, bool is_signed) { mpz_t *z = mpz_zero(); mpz_set_from_ll(z, val, is_signed); return z; } #if MICROPY_PY_BUILTINS_FLOAT mpz_t *mpz_from_float(mp_float_t val) { mpz_t *z = mpz_zero(); mpz_set_from_float(z, val); return z; } #endif mpz_t *mpz_from_str(const char *str, size_t len, bool neg, unsigned int base) { mpz_t *z = mpz_zero(); mpz_set_from_str(z, str, len, neg, base); return z; } #endif STATIC void mpz_free(mpz_t *z) { if (z != NULL) { m_del(mpz_dig_t, z->dig, z->alloc); m_del_obj(mpz_t, z); } } STATIC void mpz_need_dig(mpz_t *z, size_t need) { if (need < MIN_ALLOC) { need = MIN_ALLOC; } if (z->dig == NULL || z->alloc < need) { // if z has fixed digit buffer there's not much we can do as the caller will // be expecting a buffer with at least "need" bytes (but it shouldn't happen) assert(!z->fixed_dig); z->dig = m_renew(mpz_dig_t, z->dig, z->alloc, need); z->alloc = need; } } STATIC mpz_t *mpz_clone(const mpz_t *src) { assert(src->alloc != 0); mpz_t *z = m_new_obj(mpz_t); z->neg = src->neg; z->fixed_dig = 0; z->alloc = src->alloc; z->len = src->len; z->dig = m_new(mpz_dig_t, z->alloc); memcpy(z->dig, src->dig, src->alloc * sizeof(mpz_dig_t)); return z; } /* sets dest = src can have dest, src the same */ void mpz_set(mpz_t *dest, const mpz_t *src) { mpz_need_dig(dest, src->len); dest->neg = src->neg; dest->len = src->len; memcpy(dest->dig, src->dig, src->len * sizeof(mpz_dig_t)); } void mpz_set_from_int(mpz_t *z, mp_int_t val) { if (val == 0) { z->neg = 0; z->len = 0; return; } mpz_need_dig(z, MPZ_NUM_DIG_FOR_INT); mp_uint_t uval; if (val < 0) { z->neg = 1; uval = -val; } else { z->neg = 0; uval = val; } z->len = 0; while (uval > 0) { z->dig[z->len++] = uval & DIG_MASK; uval >>= DIG_SIZE; } } void mpz_set_from_ll(mpz_t *z, long long val, bool is_signed) { mpz_need_dig(z, MPZ_NUM_DIG_FOR_LL); unsigned long long uval; if (is_signed && val < 0) { z->neg = 1; uval = -(unsigned long long)val; } else { z->neg = 0; uval = val; } z->len = 0; while (uval > 0) { z->dig[z->len++] = uval & DIG_MASK; uval >>= DIG_SIZE; } } #if MICROPY_PY_BUILTINS_FLOAT void mpz_set_from_float(mpz_t *z, mp_float_t src) { mp_float_union_t u = {src}; z->neg = u.p.sgn; if (u.p.exp == 0) { // value == 0 || value < 1 mpz_set_from_int(z, 0); } else if (u.p.exp == ((1 << MP_FLOAT_EXP_BITS) - 1)) { // u.p.frc == 0 indicates inf, else NaN // should be handled by caller mpz_set_from_int(z, 0); } else { const int adj_exp = (int)u.p.exp - MP_FLOAT_EXP_BIAS; if (adj_exp < 0) { // value < 1 , truncates to 0 mpz_set_from_int(z, 0); } else if (adj_exp == 0) { // 1 <= value < 2 , so truncates to 1 mpz_set_from_int(z, 1); } else { // 2 <= value const int dig_cnt = (adj_exp + 1 + (DIG_SIZE - 1)) / DIG_SIZE; const unsigned int rem = adj_exp % DIG_SIZE; int dig_ind, shft; mp_float_uint_t frc = u.p.frc | ((mp_float_uint_t)1 << MP_FLOAT_FRAC_BITS); if (adj_exp < MP_FLOAT_FRAC_BITS) { shft = 0; dig_ind = 0; frc >>= MP_FLOAT_FRAC_BITS - adj_exp; } else { shft = (rem - MP_FLOAT_FRAC_BITS) % DIG_SIZE; dig_ind = (adj_exp - MP_FLOAT_FRAC_BITS) / DIG_SIZE; } mpz_need_dig(z, dig_cnt); z->len = dig_cnt; if (dig_ind != 0) { memset(z->dig, 0, dig_ind * sizeof(mpz_dig_t)); } if (shft != 0) { z->dig[dig_ind++] = (frc << shft) & DIG_MASK; frc >>= DIG_SIZE - shft; } #if DIG_SIZE < (MP_FLOAT_FRAC_BITS + 1) while (dig_ind != dig_cnt) { z->dig[dig_ind++] = frc & DIG_MASK; frc >>= DIG_SIZE; } #else if (dig_ind != dig_cnt) { z->dig[dig_ind] = frc; } #endif } } } #endif // returns number of bytes from str that were processed size_t mpz_set_from_str(mpz_t *z, const char *str, size_t len, bool neg, unsigned int base) { assert(base <= 36); const char *cur = str; const char *top = str + len; mpz_need_dig(z, len * 8 / DIG_SIZE + 1); if (neg) { z->neg = 1; } else { z->neg = 0; } z->len = 0; for (; cur < top; ++cur) { // XXX UTF8 next char // mp_uint_t v = char_to_numeric(cur#); // XXX UTF8 get char mp_uint_t v = *cur; if ('0' <= v && v <= '9') { v -= '0'; } else if ('A' <= v && v <= 'Z') { v -= 'A' - 10; } else if ('a' <= v && v <= 'z') { v -= 'a' - 10; } else { break; } if (v >= base) { break; } z->len = mpn_mul_dig_add_dig(z->dig, z->len, base, v); } return cur - str; } void mpz_set_from_bytes(mpz_t *z, bool big_endian, size_t len, const byte *buf) { int delta = 1; if (big_endian) { buf += len - 1; delta = -1; } mpz_need_dig(z, (len * 8 + DIG_SIZE - 1) / DIG_SIZE); mpz_dig_t d = 0; int num_bits = 0; z->neg = 0; z->len = 0; while (len) { while (len && num_bits < DIG_SIZE) { d |= *buf << num_bits; num_bits += 8; buf += delta; len--; } z->dig[z->len++] = d & DIG_MASK; // Need this #if because it's C undefined behavior to do: uint32_t >> 32 #if DIG_SIZE != 8 && DIG_SIZE != 16 && DIG_SIZE != 32 d >>= DIG_SIZE; #else d = 0; #endif num_bits -= DIG_SIZE; } z->len = mpn_remove_trailing_zeros(z->dig, z->dig + z->len); } #if 0 these functions are unused bool mpz_is_pos(const mpz_t *z) { return z->len > 0 && z->neg == 0; } bool mpz_is_odd(const mpz_t *z) { return z->len > 0 && (z->dig[0] & 1) != 0; } bool mpz_is_even(const mpz_t *z) { return z->len == 0 || (z->dig[0] & 1) == 0; } #endif int mpz_cmp(const mpz_t *z1, const mpz_t *z2) { int cmp = (int)z2->neg - (int)z1->neg; if (cmp != 0) { return cmp; } cmp = mpn_cmp(z1->dig, z1->len, z2->dig, z2->len); if (z1->neg != 0) { cmp = -cmp; } return cmp; } #if 0 // obsolete // compares mpz with an integer that fits within DIG_SIZE bits mp_int_t mpz_cmp_sml_int(const mpz_t *z, mp_int_t sml_int) { mp_int_t cmp; if (z->neg == 0) { if (sml_int < 0) { return 1; } if (sml_int == 0) { if (z->len == 0) { return 0; } return 1; } if (z->len == 0) { return -1; } assert(sml_int < (1 << DIG_SIZE)); if (z->len != 1) { return 1; } cmp = z->dig[0] - sml_int; } else { if (sml_int > 0) { return -1; } if (sml_int == 0) { if (z->len == 0) { return 0; } return -1; } if (z->len == 0) { return 1; } assert(sml_int > -(1 << DIG_SIZE)); if (z->len != 1) { return -1; } cmp = -z->dig[0] - sml_int; } if (cmp < 0) { return -1; } if (cmp > 0) { return 1; } return 0; } #endif #if 0 these functions are unused /* returns abs(z) */ mpz_t *mpz_abs(const mpz_t *z) { // TODO: handle case of z->alloc=0 mpz_t *z2 = mpz_clone(z); z2->neg = 0; return z2; } /* returns -z */ mpz_t *mpz_neg(const mpz_t *z) { // TODO: handle case of z->alloc=0 mpz_t *z2 = mpz_clone(z); z2->neg = 1 - z2->neg; return z2; } /* returns lhs + rhs can have lhs, rhs the same */ mpz_t *mpz_add(const mpz_t *lhs, const mpz_t *rhs) { mpz_t *z = mpz_zero(); mpz_add_inpl(z, lhs, rhs); return z; } /* returns lhs - rhs can have lhs, rhs the same */ mpz_t *mpz_sub(const mpz_t *lhs, const mpz_t *rhs) { mpz_t *z = mpz_zero(); mpz_sub_inpl(z, lhs, rhs); return z; } /* returns lhs * rhs can have lhs, rhs the same */ mpz_t *mpz_mul(const mpz_t *lhs, const mpz_t *rhs) { mpz_t *z = mpz_zero(); mpz_mul_inpl(z, lhs, rhs); return z; } /* returns lhs ** rhs can have lhs, rhs the same */ mpz_t *mpz_pow(const mpz_t *lhs, const mpz_t *rhs) { mpz_t *z = mpz_zero(); mpz_pow_inpl(z, lhs, rhs); return z; } /* computes new integers in quo and rem such that: quo * rhs + rem = lhs 0 <= rem < rhs can have lhs, rhs the same */ void mpz_divmod(const mpz_t *lhs, const mpz_t *rhs, mpz_t **quo, mpz_t **rem) { *quo = mpz_zero(); *rem = mpz_zero(); mpz_divmod_inpl(*quo, *rem, lhs, rhs); } #endif /* computes dest = abs(z) can have dest, z the same */ void mpz_abs_inpl(mpz_t *dest, const mpz_t *z) { if (dest != z) { mpz_set(dest, z); } dest->neg = 0; } /* computes dest = -z can have dest, z the same */ void mpz_neg_inpl(mpz_t *dest, const mpz_t *z) { if (dest != z) { mpz_set(dest, z); } if (dest->len) { dest->neg = 1 - dest->neg; } } /* computes dest = ~z (= -z - 1) can have dest, z the same */ void mpz_not_inpl(mpz_t *dest, const mpz_t *z) { if (dest != z) { mpz_set(dest, z); } if (dest->len == 0) { mpz_need_dig(dest, 1); dest->dig[0] = 1; dest->len = 1; dest->neg = 1; } else if (dest->neg) { dest->neg = 0; mpz_dig_t k = 1; dest->len = mpn_sub(dest->dig, dest->dig, dest->len, &k, 1); } else { mpz_need_dig(dest, dest->len + 1); mpz_dig_t k = 1; dest->len = mpn_add(dest->dig, dest->dig, dest->len, &k, 1); dest->neg = 1; } } /* computes dest = lhs << rhs can have dest, lhs the same */ void mpz_shl_inpl(mpz_t *dest, const mpz_t *lhs, mp_uint_t rhs) { if (lhs->len == 0 || rhs == 0) { mpz_set(dest, lhs); } else { mpz_need_dig(dest, lhs->len + (rhs + DIG_SIZE - 1) / DIG_SIZE); dest->len = mpn_shl(dest->dig, lhs->dig, lhs->len, rhs); dest->neg = lhs->neg; } } /* computes dest = lhs >> rhs can have dest, lhs the same */ void mpz_shr_inpl(mpz_t *dest, const mpz_t *lhs, mp_uint_t rhs) { if (lhs->len == 0 || rhs == 0) { mpz_set(dest, lhs); } else { mpz_need_dig(dest, lhs->len); dest->len = mpn_shr(dest->dig, lhs->dig, lhs->len, rhs); dest->neg = lhs->neg; if (dest->neg) { // arithmetic shift right, rounding to negative infinity mp_uint_t n_whole = rhs / DIG_SIZE; mp_uint_t n_part = rhs % DIG_SIZE; mpz_dig_t round_up = 0; for (size_t i = 0; i < lhs->len && i < n_whole; i++) { if (lhs->dig[i] != 0) { round_up = 1; break; } } if (n_whole < lhs->len && (lhs->dig[n_whole] & ((1 << n_part) - 1)) != 0) { round_up = 1; } if (round_up) { if (dest->len == 0) { // dest == 0, so need to add 1 by hand (answer will be -1) dest->dig[0] = 1; dest->len = 1; } else { // dest > 0, so can use mpn_add to add 1 dest->len = mpn_add(dest->dig, dest->dig, dest->len, &round_up, 1); } } } } } /* computes dest = lhs + rhs can have dest, lhs, rhs the same */ void mpz_add_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { if (mpn_cmp(lhs->dig, lhs->len, rhs->dig, rhs->len) < 0) { const mpz_t *temp = lhs; lhs = rhs; rhs = temp; } if (lhs->neg == rhs->neg) { mpz_need_dig(dest, lhs->len + 1); dest->len = mpn_add(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } else { mpz_need_dig(dest, lhs->len); dest->len = mpn_sub(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } dest->neg = lhs->neg & !!dest->len; } /* computes dest = lhs - rhs can have dest, lhs, rhs the same */ void mpz_sub_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { bool neg = false; if (mpn_cmp(lhs->dig, lhs->len, rhs->dig, rhs->len) < 0) { const mpz_t *temp = lhs; lhs = rhs; rhs = temp; neg = true; } if (lhs->neg != rhs->neg) { mpz_need_dig(dest, lhs->len + 1); dest->len = mpn_add(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } else { mpz_need_dig(dest, lhs->len); dest->len = mpn_sub(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } if (dest->len == 0) { dest->neg = 0; } else if (neg) { dest->neg = 1 - lhs->neg; } else { dest->neg = lhs->neg; } } /* computes dest = lhs & rhs can have dest, lhs, rhs the same */ void mpz_and_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { // make sure lhs has the most digits if (lhs->len < rhs->len) { const mpz_t *temp = lhs; lhs = rhs; rhs = temp; } #if MICROPY_OPT_MPZ_BITWISE if ((0 == lhs->neg) && (0 == rhs->neg)) { mpz_need_dig(dest, lhs->len); dest->len = mpn_and(dest->dig, lhs->dig, rhs->dig, rhs->len); dest->neg = 0; } else { mpz_need_dig(dest, lhs->len + 1); dest->len = mpn_and_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, lhs->neg == rhs->neg, 0 != lhs->neg, 0 != rhs->neg); dest->neg = lhs->neg & rhs->neg; } #else mpz_need_dig(dest, lhs->len + (lhs->neg || rhs->neg)); dest->len = mpn_and_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, (lhs->neg == rhs->neg) ? lhs->neg : 0, lhs->neg, rhs->neg); dest->neg = lhs->neg & rhs->neg; #endif } /* computes dest = lhs | rhs can have dest, lhs, rhs the same */ void mpz_or_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { // make sure lhs has the most digits if (lhs->len < rhs->len) { const mpz_t *temp = lhs; lhs = rhs; rhs = temp; } #if MICROPY_OPT_MPZ_BITWISE if ((0 == lhs->neg) && (0 == rhs->neg)) { mpz_need_dig(dest, lhs->len); dest->len = mpn_or(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); dest->neg = 0; } else { mpz_need_dig(dest, lhs->len + 1); dest->len = mpn_or_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, 0 != lhs->neg, 0 != rhs->neg); dest->neg = 1; } #else mpz_need_dig(dest, lhs->len + (lhs->neg || rhs->neg)); dest->len = mpn_or_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, (lhs->neg || rhs->neg), lhs->neg, rhs->neg); dest->neg = lhs->neg | rhs->neg; #endif } /* computes dest = lhs ^ rhs can have dest, lhs, rhs the same */ void mpz_xor_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { // make sure lhs has the most digits if (lhs->len < rhs->len) { const mpz_t *temp = lhs; lhs = rhs; rhs = temp; } #if MICROPY_OPT_MPZ_BITWISE if (lhs->neg == rhs->neg) { mpz_need_dig(dest, lhs->len); if (lhs->neg == 0) { dest->len = mpn_xor(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } else { dest->len = mpn_xor_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, 0, 0, 0); } dest->neg = 0; } else { mpz_need_dig(dest, lhs->len + 1); dest->len = mpn_xor_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, 1, 0 == lhs->neg, 0 == rhs->neg); dest->neg = 1; } #else mpz_need_dig(dest, lhs->len + (lhs->neg || rhs->neg)); dest->len = mpn_xor_neg(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len, (lhs->neg != rhs->neg), 0 == lhs->neg, 0 == rhs->neg); dest->neg = lhs->neg ^ rhs->neg; #endif } /* computes dest = lhs * rhs can have dest, lhs, rhs the same */ void mpz_mul_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { if (lhs->len == 0 || rhs->len == 0) { mpz_set_from_int(dest, 0); return; } mpz_t *temp = NULL; if (lhs == dest) { lhs = temp = mpz_clone(lhs); if (rhs == dest) { rhs = lhs; } } else if (rhs == dest) { rhs = temp = mpz_clone(rhs); } mpz_need_dig(dest, lhs->len + rhs->len); // min mem l+r-1, max mem l+r memset(dest->dig, 0, dest->alloc * sizeof(mpz_dig_t)); dest->len = mpn_mul(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); if (lhs->neg == rhs->neg) { dest->neg = 0; } else { dest->neg = 1; } mpz_free(temp); } /* computes dest = lhs ** rhs can have dest, lhs, rhs the same */ void mpz_pow_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { if (lhs->len == 0 || rhs->neg != 0) { mpz_set_from_int(dest, 0); return; } if (rhs->len == 0) { mpz_set_from_int(dest, 1); return; } mpz_t *x = mpz_clone(lhs); mpz_t *n = mpz_clone(rhs); mpz_set_from_int(dest, 1); while (n->len > 0) { if ((n->dig[0] & 1) != 0) { mpz_mul_inpl(dest, dest, x); } n->len = mpn_shr(n->dig, n->dig, n->len, 1); if (n->len == 0) { break; } mpz_mul_inpl(x, x, x); } mpz_free(x); mpz_free(n); } /* computes dest = (lhs ** rhs) % mod can have dest, lhs, rhs the same; mod can't be the same as dest */ void mpz_pow3_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs, const mpz_t *mod) { if (lhs->len == 0 || rhs->neg != 0 || (mod->len == 1 && mod->dig[0] == 1)) { mpz_set_from_int(dest, 0); return; } mpz_set_from_int(dest, 1); if (rhs->len == 0) { return; } mpz_t *x = mpz_clone(lhs); mpz_t *n = mpz_clone(rhs); mpz_t quo; mpz_init_zero(&quo); while (n->len > 0) { if ((n->dig[0] & 1) != 0) { mpz_mul_inpl(dest, dest, x); mpz_divmod_inpl(&quo, dest, dest, mod); } n->len = mpn_shr(n->dig, n->dig, n->len, 1); if (n->len == 0) { break; } mpz_mul_inpl(x, x, x); mpz_divmod_inpl(&quo, x, x, mod); } mpz_deinit(&quo); mpz_free(x); mpz_free(n); } #if 0 these functions are unused /* computes gcd(z1, z2) based on Knuth's modified gcd algorithm (I think?) gcd(z1, z2) >= 0 gcd(0, 0) = 0 gcd(z, 0) = abs(z) */ mpz_t *mpz_gcd(const mpz_t *z1, const mpz_t *z2) { if (z1->len == 0) { // TODO: handle case of z2->alloc=0 mpz_t *a = mpz_clone(z2); a->neg = 0; return a; } else if (z2->len == 0) { mpz_t *a = mpz_clone(z1); a->neg = 0; return a; } mpz_t *a = mpz_clone(z1); mpz_t *b = mpz_clone(z2); mpz_t c; mpz_init_zero(&c); a->neg = 0; b->neg = 0; for (;;) { if (mpz_cmp(a, b) < 0) { if (a->len == 0) { mpz_free(a); mpz_deinit(&c); return b; } mpz_t *t = a; a = b; b = t; } if (!(b->len >= 2 || (b->len == 1 && b->dig[0] > 1))) { // compute b > 0; could be mpz_cmp_small_int(b, 1) > 0 break; } mpz_set(&c, b); do { mpz_add_inpl(&c, &c, &c); } while (mpz_cmp(&c, a) <= 0); c.len = mpn_shr(c.dig, c.dig, c.len, 1); mpz_sub_inpl(a, a, &c); } mpz_deinit(&c); if (b->len == 1 && b->dig[0] == 1) { // compute b == 1; could be mpz_cmp_small_int(b, 1) == 0 mpz_free(a); return b; } else { mpz_free(b); return a; } } /* computes lcm(z1, z2) = abs(z1) / gcd(z1, z2) * abs(z2) lcm(z1, z1) >= 0 lcm(0, 0) = 0 lcm(z, 0) = 0 */ mpz_t *mpz_lcm(const mpz_t *z1, const mpz_t *z2) { if (z1->len == 0 || z2->len == 0) { return mpz_zero(); } mpz_t *gcd = mpz_gcd(z1, z2); mpz_t *quo = mpz_zero(); mpz_t *rem = mpz_zero(); mpz_divmod_inpl(quo, rem, z1, gcd); mpz_mul_inpl(rem, quo, z2); mpz_free(gcd); mpz_free(quo); rem->neg = 0; return rem; } #endif /* computes new integers in quo and rem such that: quo * rhs + rem = lhs 0 <= rem < rhs can have lhs, rhs the same assumes rhs != 0 (undefined behaviour if it is) */ void mpz_divmod_inpl(mpz_t *dest_quo, mpz_t *dest_rem, const mpz_t *lhs, const mpz_t *rhs) { assert(!mpz_is_zero(rhs)); mpz_need_dig(dest_quo, lhs->len + 1); // +1 necessary? memset(dest_quo->dig, 0, (lhs->len + 1) * sizeof(mpz_dig_t)); dest_quo->neg = 0; dest_quo->len = 0; mpz_need_dig(dest_rem, lhs->len + 1); // +1 necessary? mpz_set(dest_rem, lhs); mpn_div(dest_rem->dig, &dest_rem->len, rhs->dig, rhs->len, dest_quo->dig, &dest_quo->len); dest_rem->neg &= !!dest_rem->len; // check signs and do Python style modulo if (lhs->neg != rhs->neg) { dest_quo->neg = !!dest_quo->len; if (!mpz_is_zero(dest_rem)) { mpz_t mpzone; mpz_init_from_int(&mpzone, -1); mpz_add_inpl(dest_quo, dest_quo, &mpzone); mpz_add_inpl(dest_rem, dest_rem, rhs); } } } #if 0 these functions are unused /* computes floor(lhs / rhs) can have lhs, rhs the same */ mpz_t *mpz_div(const mpz_t *lhs, const mpz_t *rhs) { mpz_t *quo = mpz_zero(); mpz_t rem; mpz_init_zero(&rem); mpz_divmod_inpl(quo, &rem, lhs, rhs); mpz_deinit(&rem); return quo; } /* computes lhs % rhs ( >= 0) can have lhs, rhs the same */ mpz_t *mpz_mod(const mpz_t *lhs, const mpz_t *rhs) { mpz_t quo; mpz_init_zero(&quo); mpz_t *rem = mpz_zero(); mpz_divmod_inpl(&quo, rem, lhs, rhs); mpz_deinit(&quo); return rem; } #endif // must return actual int value if it fits in mp_int_t mp_int_t mpz_hash(const mpz_t *z) { mp_uint_t val = 0; mpz_dig_t *d = z->dig + z->len; while (d-- > z->dig) { val = (val << DIG_SIZE) | *d; } if (z->neg != 0) { val = -val; } return val; } bool mpz_as_int_checked(const mpz_t *i, mp_int_t *value) { mp_uint_t val = 0; mpz_dig_t *d = i->dig + i->len; while (d-- > i->dig) { if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> DIG_SIZE)) { // will overflow return false; } val = (val << DIG_SIZE) | *d; } if (i->neg != 0) { val = -val; } *value = val; return true; } bool mpz_as_uint_checked(const mpz_t *i, mp_uint_t *value) { if (i->neg != 0) { // can't represent signed values return false; } mp_uint_t val = 0; mpz_dig_t *d = i->dig + i->len; while (d-- > i->dig) { if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> (DIG_SIZE - 1))) { // will overflow return false; } val = (val << DIG_SIZE) | *d; } *value = val; return true; } void mpz_as_bytes(const mpz_t *z, bool big_endian, size_t len, byte *buf) { byte *b = buf; if (big_endian) { b += len; } mpz_dig_t *zdig = z->dig; int bits = 0; mpz_dbl_dig_t d = 0; mpz_dbl_dig_t carry = 1; for (size_t zlen = z->len; zlen > 0; --zlen) { bits += DIG_SIZE; d = (d << DIG_SIZE) | *zdig++; for (; bits >= 8; bits -= 8, d >>= 8) { mpz_dig_t val = d; if (z->neg) { val = (~val & 0xff) + carry; carry = val >> 8; } if (big_endian) { *--b = val; if (b == buf) { return; } } else { *b++ = val; if (b == buf + len) { return; } } } } // fill remainder of buf with zero/sign extension of the integer if (big_endian) { len = b - buf; } else { len = buf + len - b; buf = b; } memset(buf, z->neg ? 0xff : 0x00, len); } #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mpz_as_float(const mpz_t *i) { mp_float_t val = 0; mpz_dig_t *d = i->dig + i->len; while (d-- > i->dig) { val = val * DIG_BASE + *d; } if (i->neg != 0) { val = -val; } return val; } #endif #if 0 this function is unused char *mpz_as_str(const mpz_t *i, unsigned int base) { char *s = m_new(char, mp_int_format_size(mpz_max_num_bits(i), base, NULL, '\0')); mpz_as_str_inpl(i, base, NULL, 'a', '\0', s); return s; } #endif // assumes enough space in str as calculated by mp_int_format_size // base must be between 2 and 32 inclusive // returns length of string, not including null byte size_t mpz_as_str_inpl(const mpz_t *i, unsigned int base, const char *prefix, char base_char, char comma, char *str) { assert(str != NULL); assert(2 <= base && base <= 32); size_t ilen = i->len; char *s = str; if (ilen == 0) { if (prefix) { while (*prefix) { *s++ = *prefix++; } } *s++ = '0'; *s = '\0'; return s - str; } // make a copy of mpz digits, so we can do the div/mod calculation mpz_dig_t *dig = m_new(mpz_dig_t, ilen); memcpy(dig, i->dig, ilen * sizeof(mpz_dig_t)); // convert char *last_comma = str; bool done; do { mpz_dig_t *d = dig + ilen; mpz_dbl_dig_t a = 0; // compute next remainder while (--d >= dig) { a = (a << DIG_SIZE) | *d; *d = a / base; a %= base; } // convert to character a += '0'; if (a > '9') { a += base_char - '9' - 1; } *s++ = a; // check if number is zero done = true; for (d = dig; d < dig + ilen; ++d) { if (*d != 0) { done = false; break; } } if (comma && (s - last_comma) == 3) { *s++ = comma; last_comma = s; } } while (!done); // free the copy of the digits array m_del(mpz_dig_t, dig, ilen); if (prefix) { const char *p = &prefix[strlen(prefix)]; while (p > prefix) { *s++ = *--p; } } if (i->neg != 0) { *s++ = '-'; } // reverse string for (char *u = str, *v = s - 1; u < v; ++u, --v) { char temp = *u; *u = *v; *v = temp; } *s = '\0'; // null termination return s - str; } #endif // MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ #ifndef __IPU__ #include #endif #include #include "py/runtime.h" #include "py/mperrno.h" #include "py/mpthread.h" #include "py/reader.h" typedef struct _mp_reader_mem_t { size_t free_len; // if >0 mem is freed on close by: m_free(beg, free_len) const byte *beg; const byte *cur; const byte *end; } mp_reader_mem_t; STATIC mp_uint_t mp_reader_mem_readbyte(void *data) { mp_reader_mem_t *reader = (mp_reader_mem_t *)data; if (reader->cur < reader->end) { return *reader->cur++; } else { return MP_READER_EOF; } } STATIC void mp_reader_mem_close(void *data) { mp_reader_mem_t *reader = (mp_reader_mem_t *)data; if (reader->free_len > 0) { m_del(char, (char *)reader->beg, reader->free_len); } m_del_obj(mp_reader_mem_t, reader); } void mp_reader_new_mem(mp_reader_t *reader, const byte *buf, size_t len, size_t free_len) { mp_reader_mem_t *rm = m_new_obj(mp_reader_mem_t); rm->free_len = free_len; rm->beg = buf; rm->cur = buf; rm->end = buf + len; reader->data = rm; reader->readbyte = mp_reader_mem_readbyte; reader->close = mp_reader_mem_close; } #if MICROPY_READER_POSIX #include #include #ifndef __IPU__ #include #else #include #endif typedef struct _mp_reader_posix_t { bool close_fd; int fd; size_t len; size_t pos; byte buf[20]; } mp_reader_posix_t; STATIC mp_uint_t mp_reader_posix_readbyte(void *data) { mp_reader_posix_t *reader = (mp_reader_posix_t *)data; if (reader->pos >= reader->len) { if (reader->len == 0) { return MP_READER_EOF; } else { MP_THREAD_GIL_EXIT(); int n = read(reader->fd, reader->buf, sizeof(reader->buf)); MP_THREAD_GIL_ENTER(); if (n <= 0) { reader->len = 0; return MP_READER_EOF; } reader->len = n; reader->pos = 0; } } return reader->buf[reader->pos++]; } STATIC void mp_reader_posix_close(void *data) { mp_reader_posix_t *reader = (mp_reader_posix_t *)data; if (reader->close_fd) { MP_THREAD_GIL_EXIT(); close(reader->fd); MP_THREAD_GIL_ENTER(); } m_del_obj(mp_reader_posix_t, reader); } void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); rp->close_fd = close_fd; rp->fd = fd; MP_THREAD_GIL_EXIT(); int n = read(rp->fd, rp->buf, sizeof(rp->buf)); if (n == -1) { if (close_fd) { close(fd); } MP_THREAD_GIL_ENTER(); mp_raise_OSError(errno); } MP_THREAD_GIL_ENTER(); rp->len = n; rp->pos = 0; reader->data = rp; reader->readbyte = mp_reader_posix_readbyte; reader->close = mp_reader_posix_close; } #if !MICROPY_VFS_POSIX // If MICROPY_VFS_POSIX is defined then this function is provided by the VFS layer void mp_reader_new_file(mp_reader_t *reader, const char *filename) { MP_THREAD_GIL_EXIT(); int fd = open(filename, O_RDONLY, 0644); MP_THREAD_GIL_ENTER(); if (fd < 0) { mp_raise_OSError(errno); } mp_reader_new_file_from_fd(reader, fd, true); } #endif #endif #ifndef __IPU__ #include #endif #include #include #include "py/reader.h" #include "py/lexer.h" #include "py/runtime.h" #if MICROPY_ENABLE_COMPILER #define TAB_SIZE (8) // TODO seems that CPython allows NULL byte in the input stream // don't know if that's intentional or not, but we don't allow it #define MP_LEXER_EOF ((unichar)MP_READER_EOF) #define CUR_CHAR(lex) ((lex)->chr0) STATIC bool is_end(mp_lexer_t *lex) { return lex->chr0 == MP_LEXER_EOF; } STATIC bool is_physical_newline(mp_lexer_t *lex) { return lex->chr0 == '\n'; } STATIC bool is_char(mp_lexer_t *lex, byte c) { return lex->chr0 == c; } STATIC bool is_char_or(mp_lexer_t *lex, byte c1, byte c2) { return lex->chr0 == c1 || lex->chr0 == c2; } STATIC bool is_char_or3(mp_lexer_t *lex, byte c1, byte c2, byte c3) { return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3; } #if MICROPY_PY_FSTRINGS STATIC bool is_char_or4(mp_lexer_t *lex, byte c1, byte c2, byte c3, byte c4) { return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3 || lex->chr0 == c4; } #endif STATIC bool is_char_following(mp_lexer_t *lex, byte c) { return lex->chr1 == c; } STATIC bool is_char_following_or(mp_lexer_t *lex, byte c1, byte c2) { return lex->chr1 == c1 || lex->chr1 == c2; } STATIC bool is_char_following_following_or(mp_lexer_t *lex, byte c1, byte c2) { return lex->chr2 == c1 || lex->chr2 == c2; } STATIC bool is_char_and(mp_lexer_t *lex, byte c1, byte c2) { return lex->chr0 == c1 && lex->chr1 == c2; } STATIC bool is_whitespace(mp_lexer_t *lex) { return unichar_isspace(lex->chr0); } STATIC bool is_letter(mp_lexer_t *lex) { return unichar_isalpha(lex->chr0); } STATIC bool is_digit(mp_lexer_t *lex) { return unichar_isdigit(lex->chr0); } STATIC bool is_following_digit(mp_lexer_t *lex) { return unichar_isdigit(lex->chr1); } STATIC bool is_following_base_char(mp_lexer_t *lex) { const unichar chr1 = lex->chr1 | 0x20; return chr1 == 'b' || chr1 == 'o' || chr1 == 'x'; } STATIC bool is_following_odigit(mp_lexer_t *lex) { return lex->chr1 >= '0' && lex->chr1 <= '7'; } STATIC bool is_string_or_bytes(mp_lexer_t *lex) { return is_char_or(lex, '\'', '\"') #if MICROPY_PY_FSTRINGS || (is_char_or4(lex, 'r', 'u', 'b', 'f') && is_char_following_or(lex, '\'', '\"')) || (((is_char_and(lex, 'r', 'f') || is_char_and(lex, 'f', 'r')) && is_char_following_following_or(lex, '\'', '\"'))) #else || (is_char_or3(lex, 'r', 'u', 'b') && is_char_following_or(lex, '\'', '\"')) #endif || ((is_char_and(lex, 'r', 'b') || is_char_and(lex, 'b', 'r')) && is_char_following_following_or(lex, '\'', '\"')); } // to easily parse utf-8 identifiers we allow any raw byte with high bit set STATIC bool is_head_of_identifier(mp_lexer_t *lex) { return is_letter(lex) || lex->chr0 == '_' || lex->chr0 >= 0x80; } STATIC bool is_tail_of_identifier(mp_lexer_t *lex) { return is_head_of_identifier(lex) || is_digit(lex); } STATIC void next_char(mp_lexer_t *lex) { if (lex->chr0 == '\n') { // a new line ++lex->line; lex->column = 1; } else if (lex->chr0 == '\t') { // a tab lex->column = (((lex->column - 1 + TAB_SIZE) / TAB_SIZE) * TAB_SIZE) + 1; } else { // a character worth one column ++lex->column; } // shift the input queue forward lex->chr0 = lex->chr1; lex->chr1 = lex->chr2; // and add the next byte from either the fstring args or the reader #if MICROPY_PY_FSTRINGS if (lex->fstring_args_idx) { // if there are saved chars, then we're currently injecting fstring args if (lex->fstring_args_idx < lex->fstring_args.len) { lex->chr2 = lex->fstring_args.buf[lex->fstring_args_idx++]; } else { // no more fstring arg bytes lex->chr2 = '\0'; } if (lex->chr0 == '\0') { // consumed all fstring data, restore saved input queue lex->chr0 = lex->chr0_saved; lex->chr1 = lex->chr1_saved; lex->chr2 = lex->chr2_saved; // stop consuming fstring arg data vstr_reset(&lex->fstring_args); lex->fstring_args_idx = 0; } } else #endif { lex->chr2 = lex->reader.readbyte(lex->reader.data); } if (lex->chr1 == '\r') { // CR is a new line, converted to LF lex->chr1 = '\n'; if (lex->chr2 == '\n') { // CR LF is a single new line, throw out the extra LF lex->chr2 = lex->reader.readbyte(lex->reader.data); } } // check if we need to insert a newline at end of file if (lex->chr2 == MP_LEXER_EOF && lex->chr1 != MP_LEXER_EOF && lex->chr1 != '\n') { lex->chr2 = '\n'; } } STATIC void indent_push(mp_lexer_t *lex, size_t indent) { if (lex->num_indent_level >= lex->alloc_indent_level) { lex->indent_level = m_renew(uint16_t, lex->indent_level, lex->alloc_indent_level, lex->alloc_indent_level + MICROPY_ALLOC_LEXEL_INDENT_INC); lex->alloc_indent_level += MICROPY_ALLOC_LEXEL_INDENT_INC; } lex->indent_level[lex->num_indent_level++] = indent; } STATIC size_t indent_top(mp_lexer_t *lex) { return lex->indent_level[lex->num_indent_level - 1]; } STATIC void indent_pop(mp_lexer_t *lex) { lex->num_indent_level -= 1; } // some tricky operator encoding: // = begin with , if this opchar matches then begin here // e = end with , if this opchar matches then end // c = continue with , if this opchar matches then continue matching // this means if the start of two ops are the same then they are equal til the last char STATIC const char *const tok_enc = "()[]{},;~" // singles ":e=" // : := " >= >> >>= "*e=c*e=" // * *= ** **= "+e=" // + += "-e=e>" // - -= -> "&e=" // & &= "|e=" // | |= "/e=c/e=" // / /= // //= "%e=" // % %= "^e=" // ^ ^= "@e=" // @ @= "=e=" // = == "!."; // start of special cases: != . ... // TODO static assert that number of tokens is less than 256 so we can safely make this table with byte sized entries STATIC const uint8_t tok_enc_kind[] = { MP_TOKEN_DEL_PAREN_OPEN, MP_TOKEN_DEL_PAREN_CLOSE, MP_TOKEN_DEL_BRACKET_OPEN, MP_TOKEN_DEL_BRACKET_CLOSE, MP_TOKEN_DEL_BRACE_OPEN, MP_TOKEN_DEL_BRACE_CLOSE, MP_TOKEN_DEL_COMMA, MP_TOKEN_DEL_SEMICOLON, MP_TOKEN_OP_TILDE, MP_TOKEN_DEL_COLON, MP_TOKEN_OP_ASSIGN, MP_TOKEN_OP_LESS, MP_TOKEN_OP_LESS_EQUAL, MP_TOKEN_OP_DBL_LESS, MP_TOKEN_DEL_DBL_LESS_EQUAL, MP_TOKEN_OP_MORE, MP_TOKEN_OP_MORE_EQUAL, MP_TOKEN_OP_DBL_MORE, MP_TOKEN_DEL_DBL_MORE_EQUAL, MP_TOKEN_OP_STAR, MP_TOKEN_DEL_STAR_EQUAL, MP_TOKEN_OP_DBL_STAR, MP_TOKEN_DEL_DBL_STAR_EQUAL, MP_TOKEN_OP_PLUS, MP_TOKEN_DEL_PLUS_EQUAL, MP_TOKEN_OP_MINUS, MP_TOKEN_DEL_MINUS_EQUAL, MP_TOKEN_DEL_MINUS_MORE, MP_TOKEN_OP_AMPERSAND, MP_TOKEN_DEL_AMPERSAND_EQUAL, MP_TOKEN_OP_PIPE, MP_TOKEN_DEL_PIPE_EQUAL, MP_TOKEN_OP_SLASH, MP_TOKEN_DEL_SLASH_EQUAL, MP_TOKEN_OP_DBL_SLASH, MP_TOKEN_DEL_DBL_SLASH_EQUAL, MP_TOKEN_OP_PERCENT, MP_TOKEN_DEL_PERCENT_EQUAL, MP_TOKEN_OP_CARET, MP_TOKEN_DEL_CARET_EQUAL, MP_TOKEN_OP_AT, MP_TOKEN_DEL_AT_EQUAL, MP_TOKEN_DEL_EQUAL, MP_TOKEN_OP_DBL_EQUAL, }; // must have the same order as enum in lexer.h // must be sorted according to strcmp STATIC const char *const tok_kw[] = { "False", "None", "True", "__debug__", "and", "as", "assert", #if MICROPY_PY_ASYNC_AWAIT "async", "await", #endif "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "ipusyscall", "is", "lambda", "longyield", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield", }; // This is called with CUR_CHAR() before first hex digit, and should return with // it pointing to last hex digit // num_digits must be greater than zero STATIC bool get_hex(mp_lexer_t *lex, size_t num_digits, mp_uint_t *result) { mp_uint_t num = 0; while (num_digits-- != 0) { next_char(lex); unichar c = CUR_CHAR(lex); if (!unichar_isxdigit(c)) { return false; } num = (num << 4) + unichar_xdigit_value(c); } *result = num; return true; } STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) { // get first quoting character char quote_char = '\''; if (is_char(lex, '\"')) { quote_char = '\"'; } next_char(lex); // work out if it's a single or triple quoted literal size_t num_quotes; if (is_char_and(lex, quote_char, quote_char)) { // triple quotes next_char(lex); next_char(lex); num_quotes = 3; } else { // single quotes num_quotes = 1; } size_t n_closing = 0; #if MICROPY_PY_FSTRINGS if (is_fstring) { // assume there's going to be interpolation, so prep the injection data // fstring_args_idx==0 && len(fstring_args)>0 means we're extracting the args. // only when fstring_args_idx>0 will we consume the arg data // note: lex->fstring_args will be empty already (it's reset when finished) vstr_add_str(&lex->fstring_args, ".format("); } #endif while (!is_end(lex) && (num_quotes > 1 || !is_char(lex, '\n')) && n_closing < num_quotes) { if (is_char(lex, quote_char)) { n_closing += 1; vstr_add_char(&lex->vstr, CUR_CHAR(lex)); } else { n_closing = 0; #if MICROPY_PY_FSTRINGS while (is_fstring && is_char(lex, '{')) { next_char(lex); if (is_char(lex, '{')) { // "{{" is passed through unchanged to be handled by str.format vstr_add_byte(&lex->vstr, '{'); next_char(lex); } else { // remember the start of this argument (if we need it for f'{a=}'). size_t i = lex->fstring_args.len; // extract characters inside the { until we reach the // format specifier or closing }. // (MicroPython limitation) note: this is completely unaware of // Python syntax and will not handle any expression containing '}' or ':'. // e.g. f'{"}"}' or f'{foo({})}'. unsigned int nested_bracket_level = 0; while (!is_end(lex) && (nested_bracket_level != 0 || !is_char_or(lex, ':', '}'))) { unichar c = CUR_CHAR(lex); if (c == '[' || c == '{') { nested_bracket_level += 1; } else if (c == ']' || c == '}') { nested_bracket_level -= 1; } // like the default case at the end of this function, stay 8-bit clean vstr_add_byte(&lex->fstring_args, c); next_char(lex); } if (lex->fstring_args.buf[lex->fstring_args.len - 1] == '=') { // if the last character of the arg was '=', then inject "arg=" before the '{'. // f'{a=}' --> 'a={}'.format(a) vstr_add_strn(&lex->vstr, lex->fstring_args.buf + i, lex->fstring_args.len - i); // remove the trailing '=' lex->fstring_args.len--; } // comma-separate args vstr_add_byte(&lex->fstring_args, ','); } vstr_add_byte(&lex->vstr, '{'); } #endif if (is_char(lex, '\\')) { next_char(lex); unichar c = CUR_CHAR(lex); if (is_raw) { // raw strings allow escaping of quotes, but the backslash is also emitted vstr_add_char(&lex->vstr, '\\'); } else { switch (c) { // note: "c" can never be MP_LEXER_EOF because next_char // always inserts a newline at the end of the input stream case '\n': c = MP_LEXER_EOF; break; // backslash escape the newline, just ignore it case '\\': break; case '\'': break; case '"': break; case 'a': c = 0x07; break; case 'b': c = 0x08; break; case 't': c = 0x09; break; case 'n': c = 0x0a; break; case 'v': c = 0x0b; break; case 'f': c = 0x0c; break; case 'r': c = 0x0d; break; case 'u': case 'U': if (lex->tok_kind == MP_TOKEN_BYTES) { // b'\u1234' == b'\\u1234' vstr_add_char(&lex->vstr, '\\'); break; } // Otherwise fall through. MP_FALLTHROUGH case 'x': { mp_uint_t num = 0; if (!get_hex(lex, (c == 'x' ? 2 : c == 'u' ? 4 : 8), &num)) { // not enough hex chars for escape sequence lex->tok_kind = MP_TOKEN_INVALID; } c = num; break; } case 'N': // Supporting '\N{LATIN SMALL LETTER A}' == 'a' would require keeping the // entire Unicode name table in the core. As of Unicode 6.3.0, that's nearly // 3MB of text; even gzip-compressed and with minimal structure, it'll take // roughly half a meg of storage. This form of Unicode escape may be added // later on, but it's definitely not a priority right now. -- CJA 20140607 mp_raise_NotImplementedError(MP_ERROR_TEXT("unicode name escapes")); break; default: if (c >= '0' && c <= '7') { // Octal sequence, 1-3 chars size_t digits = 3; mp_uint_t num = c - '0'; while (is_following_odigit(lex) && --digits != 0) { next_char(lex); num = num * 8 + (CUR_CHAR(lex) - '0'); } c = num; } else { // unrecognised escape character; CPython lets this through verbatim as '\' and then the character vstr_add_char(&lex->vstr, '\\'); } break; } } if (c != MP_LEXER_EOF) { if (MICROPY_PY_BUILTINS_STR_UNICODE_DYNAMIC) { if (c < 0x110000 && lex->tok_kind == MP_TOKEN_STRING) { vstr_add_char(&lex->vstr, c); } else if (c < 0x100 && lex->tok_kind == MP_TOKEN_BYTES) { vstr_add_byte(&lex->vstr, c); } else { // unicode character out of range // this raises a generic SyntaxError; could provide more info lex->tok_kind = MP_TOKEN_INVALID; } } else { // without unicode everything is just added as an 8-bit byte if (c < 0x100) { vstr_add_byte(&lex->vstr, c); } else { // 8-bit character out of range // this raises a generic SyntaxError; could provide more info lex->tok_kind = MP_TOKEN_INVALID; } } } } else { // Add the "character" as a byte so that we remain 8-bit clean. // This way, strings are parsed correctly whether or not they contain utf-8 chars. vstr_add_byte(&lex->vstr, CUR_CHAR(lex)); } } next_char(lex); } // check we got the required end quotes if (n_closing < num_quotes) { lex->tok_kind = MP_TOKEN_LONELY_STRING_OPEN; } // cut off the end quotes from the token text vstr_cut_tail_bytes(&lex->vstr, n_closing); } STATIC bool skip_whitespace(mp_lexer_t *lex, bool stop_at_newline) { bool had_physical_newline = false; while (!is_end(lex)) { if (is_physical_newline(lex)) { if (stop_at_newline && lex->nested_bracket_level == 0) { break; } had_physical_newline = true; next_char(lex); } else if (is_whitespace(lex)) { next_char(lex); } else if (is_char(lex, '#')) { next_char(lex); while (!is_end(lex) && !is_physical_newline(lex)) { next_char(lex); } // had_physical_newline will be set on next loop } else if (is_char_and(lex, '\\', '\n')) { // line-continuation, so don't set had_physical_newline next_char(lex); next_char(lex); } else { break; } } return had_physical_newline; } void mp_lexer_to_next(mp_lexer_t *lex) { #if MICROPY_PY_FSTRINGS if (lex->fstring_args.len && lex->fstring_args_idx == 0) { // moving onto the next token means the literal string is complete. // switch into injecting the format args. vstr_add_byte(&lex->fstring_args, ')'); lex->chr0_saved = lex->chr0; lex->chr1_saved = lex->chr1; lex->chr2_saved = lex->chr2; lex->chr0 = lex->fstring_args.buf[0]; lex->chr1 = lex->fstring_args.buf[1]; lex->chr2 = lex->fstring_args.buf[2]; // we've already extracted 3 chars, but setting this non-zero also // means we'll start consuming the fstring data lex->fstring_args_idx = 3; } #endif // start new token text vstr_reset(&lex->vstr); // skip white space and comments bool had_physical_newline = skip_whitespace(lex, false); // set token source information lex->tok_line = lex->line; lex->tok_column = lex->column; if (lex->emit_dent < 0) { lex->tok_kind = MP_TOKEN_DEDENT; lex->emit_dent += 1; } else if (lex->emit_dent > 0) { lex->tok_kind = MP_TOKEN_INDENT; lex->emit_dent -= 1; } else if (had_physical_newline && lex->nested_bracket_level == 0) { lex->tok_kind = MP_TOKEN_NEWLINE; size_t num_spaces = lex->column - 1; if (num_spaces == indent_top(lex)) { } else if (num_spaces > indent_top(lex)) { indent_push(lex, num_spaces); lex->emit_dent += 1; } else { while (num_spaces < indent_top(lex)) { indent_pop(lex); lex->emit_dent -= 1; } if (num_spaces != indent_top(lex)) { lex->tok_kind = MP_TOKEN_DEDENT_MISMATCH; } } } else if (is_end(lex)) { lex->tok_kind = MP_TOKEN_END; } else if (is_string_or_bytes(lex)) { // a string or bytes literal // Python requires adjacent string/bytes literals to be automatically // concatenated. We do it here in the tokeniser to make efficient use of RAM, // because then the lexer's vstr can be used to accumulate the string literal, // in contrast to creating a parse tree of strings and then joining them later // in the compiler. It's also more compact in code size to do it here. // MP_TOKEN_END is used to indicate that this is the first string token lex->tok_kind = MP_TOKEN_END; // Loop to accumulate string/bytes literals do { // parse type codes bool is_raw = false; bool is_fstring = false; mp_token_kind_t kind = MP_TOKEN_STRING; int n_char = 0; if (is_char(lex, 'u')) { n_char = 1; } else if (is_char(lex, 'b')) { kind = MP_TOKEN_BYTES; n_char = 1; if (is_char_following(lex, 'r')) { is_raw = true; n_char = 2; } } else if (is_char(lex, 'r')) { is_raw = true; n_char = 1; if (is_char_following(lex, 'b')) { kind = MP_TOKEN_BYTES; n_char = 2; } #if MICROPY_PY_FSTRINGS if (is_char_following(lex, 'f')) { // raw-f-strings unsupported, immediately return (invalid) token. lex->tok_kind = MP_TOKEN_FSTRING_RAW; break; } #endif } #if MICROPY_PY_FSTRINGS else if (is_char(lex, 'f')) { if (is_char_following(lex, 'r')) { // raw-f-strings unsupported, immediately return (invalid) token. lex->tok_kind = MP_TOKEN_FSTRING_RAW; break; } n_char = 1; is_fstring = true; } #endif // Set or check token kind if (lex->tok_kind == MP_TOKEN_END) { lex->tok_kind = kind; } else if (lex->tok_kind != kind) { // Can't concatenate string with bytes break; } // Skip any type code characters if (n_char != 0) { next_char(lex); if (n_char == 2) { next_char(lex); } } // Parse the literal parse_string_literal(lex, is_raw, is_fstring); // Skip whitespace so we can check if there's another string following skip_whitespace(lex, true); } while (is_string_or_bytes(lex)); } else if (is_head_of_identifier(lex)) { lex->tok_kind = MP_TOKEN_NAME; // get first char (add as byte to remain 8-bit clean and support utf-8) vstr_add_byte(&lex->vstr, CUR_CHAR(lex)); next_char(lex); // get tail chars while (!is_end(lex) && is_tail_of_identifier(lex)) { vstr_add_byte(&lex->vstr, CUR_CHAR(lex)); next_char(lex); } // Check if the name is a keyword. // We also check for __debug__ here and convert it to its value. This is // so the parser gives a syntax error on, eg, x.__debug__. Otherwise, we // need to check for this special token in many places in the compiler. const char *s = vstr_null_terminated_str(&lex->vstr); for (size_t i = 0; i < MP_ARRAY_SIZE(tok_kw); i++) { int cmp = strcmp(s, tok_kw[i]); if (cmp == 0) { lex->tok_kind = MP_TOKEN_KW_FALSE + i; if (lex->tok_kind == MP_TOKEN_KW___DEBUG__) { lex->tok_kind = (MP_STATE_VM(mp_optimise_value) == 0 ? MP_TOKEN_KW_TRUE : MP_TOKEN_KW_FALSE); } break; } else if (cmp < 0) { // Table is sorted and comparison was less-than, so stop searching break; } } } else if (is_digit(lex) || (is_char(lex, '.') && is_following_digit(lex))) { bool forced_integer = false; if (is_char(lex, '.')) { lex->tok_kind = MP_TOKEN_FLOAT_OR_IMAG; } else { lex->tok_kind = MP_TOKEN_INTEGER; if (is_char(lex, '0') && is_following_base_char(lex)) { forced_integer = true; } } // get first char vstr_add_char(&lex->vstr, CUR_CHAR(lex)); next_char(lex); // get tail chars while (!is_end(lex)) { if (!forced_integer && is_char_or(lex, 'e', 'E')) { lex->tok_kind = MP_TOKEN_FLOAT_OR_IMAG; vstr_add_char(&lex->vstr, 'e'); next_char(lex); if (is_char(lex, '+') || is_char(lex, '-')) { vstr_add_char(&lex->vstr, CUR_CHAR(lex)); next_char(lex); } } else if (is_letter(lex) || is_digit(lex) || is_char(lex, '.')) { if (is_char_or3(lex, '.', 'j', 'J')) { lex->tok_kind = MP_TOKEN_FLOAT_OR_IMAG; } vstr_add_char(&lex->vstr, CUR_CHAR(lex)); next_char(lex); } else if (is_char(lex, '_')) { next_char(lex); } else { break; } } } else { // search for encoded delimiter or operator const char *t = tok_enc; size_t tok_enc_index = 0; for (; *t != 0 && !is_char(lex, *t); t += 1) { if (*t == 'e' || *t == 'c') { t += 1; } tok_enc_index += 1; } next_char(lex); if (*t == 0) { // didn't match any delimiter or operator characters lex->tok_kind = MP_TOKEN_INVALID; } else if (*t == '!') { // "!=" is a special case because "!" is not a valid operator if (is_char(lex, '=')) { next_char(lex); lex->tok_kind = MP_TOKEN_OP_NOT_EQUAL; } else { lex->tok_kind = MP_TOKEN_INVALID; } } else if (*t == '.') { // "." and "..." are special cases because ".." is not a valid operator if (is_char_and(lex, '.', '.')) { next_char(lex); next_char(lex); lex->tok_kind = MP_TOKEN_ELLIPSIS; } else { lex->tok_kind = MP_TOKEN_DEL_PERIOD; } } else { // matched a delimiter or operator character // get the maximum characters for a valid token t += 1; size_t t_index = tok_enc_index; while (*t == 'c' || *t == 'e') { t_index += 1; if (is_char(lex, t[1])) { next_char(lex); tok_enc_index = t_index; if (*t == 'e') { break; } } else if (*t == 'c') { break; } t += 2; } // set token kind lex->tok_kind = tok_enc_kind[tok_enc_index]; // compute bracket level for implicit line joining if (lex->tok_kind == MP_TOKEN_DEL_PAREN_OPEN || lex->tok_kind == MP_TOKEN_DEL_BRACKET_OPEN || lex->tok_kind == MP_TOKEN_DEL_BRACE_OPEN) { lex->nested_bracket_level += 1; } else if (lex->tok_kind == MP_TOKEN_DEL_PAREN_CLOSE || lex->tok_kind == MP_TOKEN_DEL_BRACKET_CLOSE || lex->tok_kind == MP_TOKEN_DEL_BRACE_CLOSE) { lex->nested_bracket_level -= 1; } } } } mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { mp_lexer_t *lex = m_new_obj(mp_lexer_t); lex->source_name = src_name; lex->reader = reader; lex->line = 1; lex->column = (size_t)-2; // account for 3 dummy bytes lex->emit_dent = 0; lex->nested_bracket_level = 0; lex->alloc_indent_level = MICROPY_ALLOC_LEXER_INDENT_INIT; lex->num_indent_level = 1; lex->indent_level = m_new(uint16_t, lex->alloc_indent_level); vstr_init(&lex->vstr, 32); #if MICROPY_PY_FSTRINGS vstr_init(&lex->fstring_args, 0); #endif // store sentinel for first indentation level lex->indent_level[0] = 0; // load lexer with start of file, advancing lex->column to 1 // start with dummy bytes and use next_char() for proper EOL/EOF handling lex->chr0 = lex->chr1 = lex->chr2 = 0; next_char(lex); next_char(lex); next_char(lex); // preload first token mp_lexer_to_next(lex); // Check that the first token is in the first column. If it's not then we // convert the token kind to INDENT so that the parser gives a syntax error. if (lex->tok_column != 1) { lex->tok_kind = MP_TOKEN_INDENT; } return lex; } mp_lexer_t *mp_lexer_new_from_str_len(qstr src_name, const char *str, size_t len, size_t free_len) { mp_reader_t reader; mp_reader_new_mem(&reader, (const byte *)str, len, free_len); return mp_lexer_new(src_name, reader); } #if MICROPY_READER_POSIX || MICROPY_READER_VFS mp_lexer_t *mp_lexer_new_from_file(const char *filename) { mp_reader_t reader; mp_reader_new_file(&reader, filename); return mp_lexer_new(qstr_from_str(filename), reader); } #if MICROPY_HELPER_LEXER_UNIX mp_lexer_t *mp_lexer_new_from_fd(qstr filename, int fd, bool close_fd) { mp_reader_t reader; mp_reader_new_file_from_fd(&reader, fd, close_fd); return mp_lexer_new(filename, reader); } #endif #endif void mp_lexer_free(mp_lexer_t *lex) { if (lex) { lex->reader.close(lex->reader.data); vstr_clear(&lex->vstr); #if MICROPY_PY_FSTRINGS vstr_clear(&lex->fstring_args); #endif m_del(uint16_t, lex->indent_level, lex->alloc_indent_level); m_del_obj(mp_lexer_t, lex); } } #if 0 // This function is used to print the current token and should only be // needed to debug the lexer, so it's not available via a config option. void mp_lexer_show_token(const mp_lexer_t *lex) { printf("(" UINT_FMT ":" UINT_FMT ") kind:%u str:%p len:%zu", lex->tok_line, lex->tok_column, lex->tok_kind, lex->vstr.buf, lex->vstr.len); if (lex->vstr.len > 0) { const byte *i = (const byte *)lex->vstr.buf; const byte *j = (const byte *)i + lex->vstr.len; printf(" "); while (i < j) { unichar c = utf8_get_char(i); i = utf8_next_char(i); if (unichar_isprint(c)) { printf("%c", (int)c); } else { printf("?"); } } } printf("\n"); } #endif #endif // MICROPY_ENABLE_COMPILER #include #include #ifndef __IPU__ #include #endif #ifndef __IPU__ #include #else #include #endif // for ssize_t #include #include #include "py/lexer.h" #include "py/parse.h" #include "py/parsenum.h" #include "py/runtime.h" #include "py/objint.h" #include "py/objstr.h" #include "py/builtin.h" #if MICROPY_ENABLE_COMPILER #define RULE_ACT_ARG_MASK (0x0f) #define RULE_ACT_KIND_MASK (0x30) #define RULE_ACT_ALLOW_IDENT (0x40) #define RULE_ACT_ADD_BLANK (0x80) #define RULE_ACT_OR (0x10) #define RULE_ACT_AND (0x20) #define RULE_ACT_LIST (0x30) #define RULE_ARG_KIND_MASK (0xf000) #define RULE_ARG_ARG_MASK (0x0fff) #define RULE_ARG_TOK (0x1000) #define RULE_ARG_RULE (0x2000) #define RULE_ARG_OPT_RULE (0x3000) // *FORMAT-OFF* enum { // define rules with a compile function #define DEF_RULE(rule, comp, kind, ...) RULE_##rule, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC RULE_const_object, // special node for a constant, generic Python object // define rules without a compile function #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) RULE_##rule, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC }; // Define an array of actions corresponding to each rule STATIC const uint8_t rule_act_table[] = { #define or(n) (RULE_ACT_OR | n) #define and(n) (RULE_ACT_AND | n) #define and_ident(n) (RULE_ACT_AND | n | RULE_ACT_ALLOW_IDENT) #define and_blank(n) (RULE_ACT_AND | n | RULE_ACT_ADD_BLANK) #define one_or_more (RULE_ACT_LIST | 2) #define list (RULE_ACT_LIST | 1) #define list_with_end (RULE_ACT_LIST | 3) #define DEF_RULE(rule, comp, kind, ...) kind, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC 0, // RULE_const_object #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) kind, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC #undef or #undef and #undef and_ident #undef and_blank #undef one_or_more #undef list #undef list_with_end }; // Define the argument data for each rule, as a combined array STATIC const uint16_t rule_arg_combined_table[] = { #define tok(t) (RULE_ARG_TOK | MP_TOKEN_##t) #define rule(r) (RULE_ARG_RULE | RULE_##r) #define opt_rule(r) (RULE_ARG_OPT_RULE | RULE_##r) #define DEF_RULE(rule, comp, kind, ...) __VA_ARGS__, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) __VA_ARGS__, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC #undef tok #undef rule #undef opt_rule }; // Macro to create a list of N identifiers where N is the number of variable arguments to the macro #define RULE_EXPAND(x) x #define RULE_PADDING(rule, ...) RULE_PADDING2(rule, __VA_ARGS__, RULE_PADDING_IDS(rule)) #define RULE_PADDING2(rule, ...) RULE_EXPAND(RULE_PADDING3(rule, __VA_ARGS__)) #define RULE_PADDING3(rule, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) __VA_ARGS__ #define RULE_PADDING_IDS(r) PAD13_##r, PAD12_##r, PAD11_##r, PAD10_##r, PAD9_##r, PAD8_##r, PAD7_##r, PAD6_##r, PAD5_##r, PAD4_##r, PAD3_##r, PAD2_##r, PAD1_##r, // Use an enum to create constants specifying how much room a rule takes in rule_arg_combined_table enum { #define DEF_RULE(rule, comp, kind, ...) RULE_PADDING(rule, __VA_ARGS__) #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) RULE_PADDING(rule, __VA_ARGS__) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC }; // Macro to compute the start of a rule in rule_arg_combined_table #define RULE_ARG_OFFSET(rule, ...) RULE_ARG_OFFSET2(rule, __VA_ARGS__, RULE_ARG_OFFSET_IDS(rule)) #define RULE_ARG_OFFSET2(rule, ...) RULE_EXPAND(RULE_ARG_OFFSET3(rule, __VA_ARGS__)) #define RULE_ARG_OFFSET3(rule, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) _14 #define RULE_ARG_OFFSET_IDS(r) PAD13_##r, PAD12_##r, PAD11_##r, PAD10_##r, PAD9_##r, PAD8_##r, PAD7_##r, PAD6_##r, PAD5_##r, PAD4_##r, PAD3_##r, PAD2_##r, PAD1_##r, PAD0_##r, // Use the above enum values to create a table of offsets for each rule's arg // data, which indexes rule_arg_combined_table. The offsets require 9 bits of // storage but only the lower 8 bits are stored here. The 9th bit is computed // in get_rule_arg using the FIRST_RULE_WITH_OFFSET_ABOVE_255 constant. STATIC const uint8_t rule_arg_offset_table[] = { #define DEF_RULE(rule, comp, kind, ...) RULE_ARG_OFFSET(rule, __VA_ARGS__) & 0xff, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC 0, // RULE_const_object #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) RULE_ARG_OFFSET(rule, __VA_ARGS__) & 0xff, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC }; // Define a constant that's used to determine the 9th bit of the values in rule_arg_offset_table static const size_t FIRST_RULE_WITH_OFFSET_ABOVE_255 = #define DEF_RULE(rule, comp, kind, ...) RULE_ARG_OFFSET(rule, __VA_ARGS__) >= 0x100 ? RULE_##rule : #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) RULE_ARG_OFFSET(rule, __VA_ARGS__) >= 0x100 ? RULE_##rule : #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC 0; #if MICROPY_DEBUG_PARSE_RULE_NAME // Define an array of rule names corresponding to each rule STATIC const char *const rule_name_table[] = { #define DEF_RULE(rule, comp, kind, ...) #rule, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC "", // RULE_const_object #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) #rule, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC }; #endif // *FORMAT-ON* typedef struct _rule_stack_t { size_t src_line : (8 * sizeof(size_t) - 8); // maximum bits storing source line number size_t rule_id : 8; // this must be large enough to fit largest rule number size_t arg_i; // this dictates the maximum nodes in a "list" of things } rule_stack_t; typedef struct _mp_parse_chunk_t { size_t alloc; union { size_t used; struct _mp_parse_chunk_t *next; } union_; byte data[]; } mp_parse_chunk_t; typedef struct _parser_t { size_t rule_stack_alloc; size_t rule_stack_top; rule_stack_t *rule_stack; size_t result_stack_alloc; size_t result_stack_top; mp_parse_node_t *result_stack; mp_lexer_t *lexer; mp_parse_tree_t tree; mp_parse_chunk_t *cur_chunk; #if MICROPY_COMP_CONST mp_map_t consts; #endif } parser_t; STATIC const uint16_t *get_rule_arg(uint8_t r_id) { size_t off = rule_arg_offset_table[r_id]; if (r_id >= FIRST_RULE_WITH_OFFSET_ABOVE_255) { off |= 0x100; } return &rule_arg_combined_table[off]; } STATIC void *parser_alloc(parser_t *parser, size_t num_bytes) { // use a custom memory allocator to store parse nodes sequentially in large chunks mp_parse_chunk_t *chunk = parser->cur_chunk; if (chunk != NULL && chunk->union_.used + num_bytes > chunk->alloc) { // not enough room at end of previously allocated chunk so try to grow mp_parse_chunk_t *new_data = (mp_parse_chunk_t *)m_renew_maybe(byte, chunk, sizeof(mp_parse_chunk_t) + chunk->alloc, sizeof(mp_parse_chunk_t) + chunk->alloc + num_bytes, false); if (new_data == NULL) { // could not grow existing memory; shrink it to fit previous (void)m_renew_maybe(byte, chunk, sizeof(mp_parse_chunk_t) + chunk->alloc, sizeof(mp_parse_chunk_t) + chunk->union_.used, false); chunk->alloc = chunk->union_.used; chunk->union_.next = parser->tree.chunk; parser->tree.chunk = chunk; chunk = NULL; } else { // could grow existing memory chunk->alloc += num_bytes; } } if (chunk == NULL) { // no previous chunk, allocate a new chunk size_t alloc = MICROPY_ALLOC_PARSE_CHUNK_INIT; if (alloc < num_bytes) { alloc = num_bytes; } chunk = (mp_parse_chunk_t *)m_new(byte, sizeof(mp_parse_chunk_t) + alloc); chunk->alloc = alloc; chunk->union_.used = 0; parser->cur_chunk = chunk; } byte *ret = chunk->data + chunk->union_.used; chunk->union_.used += num_bytes; return ret; } STATIC void push_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t arg_i) { if (parser->rule_stack_top >= parser->rule_stack_alloc) { rule_stack_t *rs = m_renew(rule_stack_t, parser->rule_stack, parser->rule_stack_alloc, parser->rule_stack_alloc + MICROPY_ALLOC_PARSE_RULE_INC); parser->rule_stack = rs; parser->rule_stack_alloc += MICROPY_ALLOC_PARSE_RULE_INC; } rule_stack_t *rs = &parser->rule_stack[parser->rule_stack_top++]; rs->src_line = src_line; rs->rule_id = rule_id; rs->arg_i = arg_i; } STATIC void push_rule_from_arg(parser_t *parser, size_t arg) { assert((arg & RULE_ARG_KIND_MASK) == RULE_ARG_RULE || (arg & RULE_ARG_KIND_MASK) == RULE_ARG_OPT_RULE); size_t rule_id = arg & RULE_ARG_ARG_MASK; push_rule(parser, parser->lexer->tok_line, rule_id, 0); } STATIC uint8_t pop_rule(parser_t *parser, size_t *arg_i, size_t *src_line) { parser->rule_stack_top -= 1; uint8_t rule_id = parser->rule_stack[parser->rule_stack_top].rule_id; *arg_i = parser->rule_stack[parser->rule_stack_top].arg_i; *src_line = parser->rule_stack[parser->rule_stack_top].src_line; return rule_id; } bool mp_parse_node_is_const_false(mp_parse_node_t pn) { return MP_PARSE_NODE_IS_TOKEN_KIND(pn, MP_TOKEN_KW_FALSE) || (MP_PARSE_NODE_IS_SMALL_INT(pn) && MP_PARSE_NODE_LEAF_SMALL_INT(pn) == 0); } bool mp_parse_node_is_const_true(mp_parse_node_t pn) { return MP_PARSE_NODE_IS_TOKEN_KIND(pn, MP_TOKEN_KW_TRUE) || (MP_PARSE_NODE_IS_SMALL_INT(pn) && MP_PARSE_NODE_LEAF_SMALL_INT(pn) != 0); } bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) { if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { *o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn)); return true; } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // nodes are 32-bit pointers, but need to extract 64-bit object *o = (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32); #else *o = (mp_obj_t)pns->nodes[0]; #endif return mp_obj_is_int(*o); } else { return false; } } size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes) { if (MP_PARSE_NODE_IS_NULL(*pn)) { *nodes = NULL; return 0; } else if (MP_PARSE_NODE_IS_LEAF(*pn)) { *nodes = pn; return 1; } else { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)(*pn); if (MP_PARSE_NODE_STRUCT_KIND(pns) != pn_kind) { *nodes = pn; return 1; } else { *nodes = pns->nodes; return MP_PARSE_NODE_STRUCT_NUM_NODES(pns); } } } #if MICROPY_DEBUG_PRINTERS void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t indent) { if (MP_PARSE_NODE_IS_STRUCT(pn)) { mp_printf(print, "[% 4d] ", (int)((mp_parse_node_struct_t *)pn)->source_line); } else { mp_printf(print, " "); } for (size_t i = 0; i < indent; i++) { mp_printf(print, " "); } if (MP_PARSE_NODE_IS_NULL(pn)) { mp_printf(print, "NULL\n"); } else if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); mp_printf(print, "int(" INT_FMT ")\n", arg); } else if (MP_PARSE_NODE_IS_LEAF(pn)) { uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: mp_printf(print, "id(%s)\n", qstr_str(arg)); break; case MP_PARSE_NODE_STRING: mp_printf(print, "str(%s)\n", qstr_str(arg)); break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); mp_printf(print, "tok(%u)\n", (uint)arg); break; } } else { // node must be a mp_parse_node_struct_t mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_const_object) { #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D mp_printf(print, "literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); #else mp_printf(print, "literal const(%p)\n", (mp_obj_t)pns->nodes[0]); #endif } else { size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); #if MICROPY_DEBUG_PARSE_RULE_NAME mp_printf(print, "%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #else mp_printf(print, "rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #endif for (size_t i = 0; i < n; i++) { mp_parse_node_print(print, pns->nodes[i], indent + 2); } } } } #endif // MICROPY_DEBUG_PRINTERS /* STATIC void result_stack_show(const mp_print_t *print, parser_t *parser) { mp_printf(print, "result stack, most recent first\n"); for (ssize_t i = parser->result_stack_top - 1; i >= 0; i--) { mp_parse_node_print(print, parser->result_stack[i], 0); } } */ STATIC mp_parse_node_t pop_result(parser_t *parser) { assert(parser->result_stack_top > 0); return parser->result_stack[--parser->result_stack_top]; } STATIC mp_parse_node_t peek_result(parser_t *parser, size_t pos) { assert(parser->result_stack_top > pos); return parser->result_stack[parser->result_stack_top - 1 - pos]; } STATIC void push_result_node(parser_t *parser, mp_parse_node_t pn) { if (parser->result_stack_top >= parser->result_stack_alloc) { mp_parse_node_t *stack = m_renew(mp_parse_node_t, parser->result_stack, parser->result_stack_alloc, parser->result_stack_alloc + MICROPY_ALLOC_PARSE_RESULT_INC); parser->result_stack = stack; parser->result_stack_alloc += MICROPY_ALLOC_PARSE_RESULT_INC; } parser->result_stack[parser->result_stack_top++] = pn; } STATIC mp_parse_node_t make_node_const_object(parser_t *parser, size_t src_line, mp_obj_t obj) { mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_obj_t)); pn->source_line = src_line; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // nodes are 32-bit pointers, but need to store 64-bit object pn->kind_num_nodes = RULE_const_object | (2 << 8); pn->nodes[0] = (uint64_t)obj; pn->nodes[1] = (uint64_t)obj >> 32; #else pn->kind_num_nodes = RULE_const_object | (1 << 8); pn->nodes[0] = (uintptr_t)obj; #endif return (mp_parse_node_t)pn; } // Create a parse node represeting a constant integer value, possibly optimising // it by putting the (small) integer value directly in the parse node itself. STATIC mp_parse_node_t make_node_const_int(parser_t *parser, size_t src_line, mp_obj_t obj) { if (mp_obj_is_small_int(obj)) { mp_int_t val = MP_OBJ_SMALL_INT_VALUE(obj); #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // A parse node is only 32-bits and the small-int value must fit in 31-bits if (((val ^ (val << 1)) & 0xffffffff80000000) != 0) { return make_node_const_object(parser, src_line, obj); } #endif #if MICROPY_DYNAMIC_COMPILER // Check that the integer value fits in target runtime's small-int mp_uint_t sign_mask = -((mp_uint_t)1 << (mp_dynamic_compiler.small_int_bits - 1)); if (!((val & sign_mask) == 0 || (val & sign_mask) == sign_mask)) { return make_node_const_object(parser, src_line, obj); } #endif return mp_parse_node_new_small_int(val); } else { return make_node_const_object(parser, src_line, obj); } } STATIC void push_result_token(parser_t *parser, uint8_t rule_id) { mp_parse_node_t pn; mp_lexer_t *lex = parser->lexer; if (lex->tok_kind == MP_TOKEN_NAME) { qstr id = qstr_from_strn(lex->vstr.buf, lex->vstr.len); #if MICROPY_COMP_CONST // if name is a standalone identifier, look it up in the table of dynamic constants mp_map_elem_t *elem; if (rule_id == RULE_atom && (elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP)) != NULL) { pn = make_node_const_int(parser, lex->tok_line, elem->value); } else { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_ID, id); } #else (void)rule_id; pn = mp_parse_node_new_leaf(MP_PARSE_NODE_ID, id); #endif } else if (lex->tok_kind == MP_TOKEN_INTEGER) { mp_obj_t o = mp_parse_num_integer(lex->vstr.buf, lex->vstr.len, 0, lex); pn = make_node_const_int(parser, lex->tok_line, o); } else if (lex->tok_kind == MP_TOKEN_FLOAT_OR_IMAG) { mp_obj_t o = mp_parse_num_decimal(lex->vstr.buf, lex->vstr.len, true, false, lex); pn = make_node_const_object(parser, lex->tok_line, o); } else if (lex->tok_kind == MP_TOKEN_STRING) { // Don't automatically intern all strings. Doc strings (which are usually large) // will be discarded by the compiler, and so we shouldn't intern them. qstr qst = MP_QSTRnull; if (lex->vstr.len <= MICROPY_ALLOC_PARSE_INTERN_STRING_LEN) { // intern short strings qst = qstr_from_strn(lex->vstr.buf, lex->vstr.len); } else { // check if this string is already interned qst = qstr_find_strn(lex->vstr.buf, lex->vstr.len); } if (qst != MP_QSTRnull) { // qstr exists, make a leaf node pn = mp_parse_node_new_leaf(MP_PARSE_NODE_STRING, qst); } else { // not interned, make a node holding a pointer to the string object mp_obj_t o = mp_obj_new_str_copy(&mp_type_str, (const byte *)lex->vstr.buf, lex->vstr.len); pn = make_node_const_object(parser, lex->tok_line, o); } } else if (lex->tok_kind == MP_TOKEN_BYTES) { // make a node holding a pointer to the bytes object mp_obj_t o = mp_obj_new_bytes((const byte *)lex->vstr.buf, lex->vstr.len); pn = make_node_const_object(parser, lex->tok_line, o); } else { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_TOKEN, lex->tok_kind); } push_result_node(parser, pn); } #if MICROPY_COMP_MODULE_CONST STATIC const mp_rom_map_elem_t mp_constants_table[] = { #if MICROPY_PY_UERRNO { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mp_module_uerrno) }, #endif #if MICROPY_PY_UCTYPES { MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) }, #endif // Extra constants as defined by a port MICROPY_PORT_CONSTANTS }; STATIC MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table); #endif STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t num_args); #if MICROPY_COMP_CONST_FOLDING STATIC bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) { if (rule_id == RULE_or_test || rule_id == RULE_and_test) { // folding for binary logical ops: or and size_t copy_to = *num_args; for (size_t i = copy_to; i > 0;) { mp_parse_node_t pn = peek_result(parser, --i); parser->result_stack[parser->result_stack_top - copy_to] = pn; if (i == 0) { // always need to keep the last value break; } if (rule_id == RULE_or_test) { if (mp_parse_node_is_const_true(pn)) { // break; } else if (!mp_parse_node_is_const_false(pn)) { copy_to -= 1; } } else { // RULE_and_test if (mp_parse_node_is_const_false(pn)) { break; } else if (!mp_parse_node_is_const_true(pn)) { copy_to -= 1; } } } copy_to -= 1; // copy_to now contains number of args to pop // pop and discard all the short-circuited expressions for (size_t i = 0; i < copy_to; ++i) { pop_result(parser); } *num_args -= copy_to; // we did a complete folding if there's only 1 arg left return *num_args == 1; } else if (rule_id == RULE_not_test_2) { // folding for unary logical op: not mp_parse_node_t pn = peek_result(parser, 0); if (mp_parse_node_is_const_false(pn)) { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_TOKEN, MP_TOKEN_KW_TRUE); } else if (mp_parse_node_is_const_true(pn)) { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_TOKEN, MP_TOKEN_KW_FALSE); } else { return false; } pop_result(parser); push_result_node(parser, pn); return true; } return false; } STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { // this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4 // it does not do partial folding, eg 1 + 2 + x -> 3 + x mp_obj_t arg0; if (rule_id == RULE_expr || rule_id == RULE_xor_expr || rule_id == RULE_and_expr || rule_id == RULE_power) { // folding for binary ops: | ^ & ** mp_parse_node_t pn = peek_result(parser, num_args - 1); if (!mp_parse_node_get_int_maybe(pn, &arg0)) { return false; } mp_binary_op_t op; if (rule_id == RULE_expr) { op = MP_BINARY_OP_OR; } else if (rule_id == RULE_xor_expr) { op = MP_BINARY_OP_XOR; } else if (rule_id == RULE_and_expr) { op = MP_BINARY_OP_AND; } else { op = MP_BINARY_OP_POWER; } for (ssize_t i = num_args - 2; i >= 0; --i) { pn = peek_result(parser, i); mp_obj_t arg1; if (!mp_parse_node_get_int_maybe(pn, &arg1)) { return false; } if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) { // ** can't have negative rhs return false; } arg0 = mp_binary_op(op, arg0, arg1); } } else if (rule_id == RULE_shift_expr || rule_id == RULE_arith_expr || rule_id == RULE_term) { // folding for binary ops: << >> + - * @ / % // mp_parse_node_t pn = peek_result(parser, num_args - 1); if (!mp_parse_node_get_int_maybe(pn, &arg0)) { return false; } for (ssize_t i = num_args - 2; i >= 1; i -= 2) { pn = peek_result(parser, i - 1); mp_obj_t arg1; if (!mp_parse_node_get_int_maybe(pn, &arg1)) { return false; } mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, i)); if (tok == MP_TOKEN_OP_AT || tok == MP_TOKEN_OP_SLASH) { // Can't fold @ or / return false; } mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS); int rhs_sign = mp_obj_int_sign(arg1); if (op <= MP_BINARY_OP_RSHIFT) { // << and >> can't have negative rhs if (rhs_sign < 0) { return false; } } else if (op >= MP_BINARY_OP_FLOOR_DIVIDE) { // % and // can't have zero rhs if (rhs_sign == 0) { return false; } } arg0 = mp_binary_op(op, arg0, arg1); } } else if (rule_id == RULE_factor_2) { // folding for unary ops: + - ~ mp_parse_node_t pn = peek_result(parser, 0); if (!mp_parse_node_get_int_maybe(pn, &arg0)) { return false; } mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1)); mp_unary_op_t op; if (tok == MP_TOKEN_OP_TILDE) { op = MP_UNARY_OP_INVERT; } else { assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); // should be op = MP_UNARY_OP_POSITIVE + (tok - MP_TOKEN_OP_PLUS); } arg0 = mp_unary_op(op, arg0); #if MICROPY_COMP_CONST } else if (rule_id == RULE_expr_stmt) { mp_parse_node_t pn1 = peek_result(parser, 0); if (!MP_PARSE_NODE_IS_NULL(pn1) && !(MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_augassign) || MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_assign_list))) { // this node is of the form = mp_parse_node_t pn0 = peek_result(parser, 1); if (MP_PARSE_NODE_IS_ID(pn0) && MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_atom_expr_normal) && MP_PARSE_NODE_IS_ID(((mp_parse_node_struct_t *)pn1)->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn1)->nodes[0]) == MP_QSTR_const && MP_PARSE_NODE_IS_STRUCT_KIND(((mp_parse_node_struct_t *)pn1)->nodes[1], RULE_trailer_paren) ) { // code to assign dynamic constants: id = const(value) // get the id qstr id = MP_PARSE_NODE_LEAF_ARG(pn0); // get the value mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; mp_obj_t value; if (!mp_parse_node_get_int_maybe(pn_value, &value)) { mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, MP_ERROR_TEXT("constant must be an integer")); mp_obj_exception_add_traceback(exc, parser->lexer->source_name, ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); nlr_raise(exc); } // store the value in the table of dynamic constants mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); assert(elem->value == MP_OBJ_NULL); elem->value = value; // If the constant starts with an underscore then treat it as a private // variable and don't emit any code to store the value to the id. if (qstr_str(id)[0] == '_') { pop_result(parser); // pop const(value) pop_result(parser); // pop id push_result_rule(parser, 0, RULE_pass_stmt, 0); // replace with "pass" return true; } // replace const(value) with value pop_result(parser); push_result_node(parser, pn_value); // finished folding this assignment, but we still want it to be part of the tree return false; } } return false; #endif #if MICROPY_COMP_MODULE_CONST } else if (rule_id == RULE_atom_expr_normal) { mp_parse_node_t pn0 = peek_result(parser, 1); mp_parse_node_t pn1 = peek_result(parser, 0); if (!(MP_PARSE_NODE_IS_ID(pn0) && MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_trailer_period))) { return false; } // id1.id2 // look it up in constant table, see if it can be replaced with an integer mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn1; assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0])); qstr q_base = MP_PARSE_NODE_LEAF_ARG(pn0); qstr q_attr = MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]); mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&mp_constants_map, MP_OBJ_NEW_QSTR(q_base), MP_MAP_LOOKUP); if (elem == NULL) { return false; } mp_obj_t dest[2]; mp_load_method_maybe(elem->value, q_attr, dest); if (!(dest[0] != MP_OBJ_NULL && mp_obj_is_int(dest[0]) && dest[1] == MP_OBJ_NULL)) { return false; } arg0 = dest[0]; #endif } else { return false; } // success folding this rule for (size_t i = num_args; i > 0; i--) { pop_result(parser); } push_result_node(parser, make_node_const_int(parser, 0, arg0)); return true; } #endif STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t num_args) { // Simplify and optimise certain rules, to reduce memory usage and simplify the compiler. if (rule_id == RULE_atom_paren) { // Remove parenthesis around a single expression if possible. // This atom_paren rule always has a single argument, and after this // optimisation that argument is either NULL or testlist_comp. mp_parse_node_t pn = peek_result(parser, 0); if (MP_PARSE_NODE_IS_NULL(pn)) { // need to keep parenthesis for () } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_testlist_comp)) { // need to keep parenthesis for (a, b, ...) } else { // parenthesis around a single expression, so it's just the expression return; } } else if (rule_id == RULE_testlist_comp) { // The testlist_comp rule can be the sole argument to either atom_parent // or atom_bracket, for (...) and [...] respectively. assert(num_args == 2); mp_parse_node_t pn = peek_result(parser, 0); if (MP_PARSE_NODE_IS_STRUCT(pn)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_testlist_comp_3b) { // tuple of one item, with trailing comma pop_result(parser); --num_args; } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_testlist_comp_3c) { // tuple of many items, convert testlist_comp_3c to testlist_comp pop_result(parser); assert(pn == peek_result(parser, 0)); pns->kind_num_nodes = rule_id | MP_PARSE_NODE_STRUCT_NUM_NODES(pns) << 8; return; } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_comp_for) { // generator expression } else { // tuple with 2 items } } else { // tuple with 2 items } } else if (rule_id == RULE_testlist_comp_3c) { // steal first arg of outer testlist_comp rule ++num_args; } #if MICROPY_COMP_CONST_FOLDING if (fold_logical_constants(parser, rule_id, &num_args)) { // we folded this rule so return straight away return; } if (fold_constants(parser, rule_id, num_args)) { // we folded this rule so return straight away return; } #endif mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * num_args); pn->source_line = src_line; pn->kind_num_nodes = (rule_id & 0xff) | (num_args << 8); for (size_t i = num_args; i > 0; i--) { pn->nodes[i - 1] = pop_result(parser); } if (rule_id == RULE_testlist_comp_3c) { // need to push something non-null to replace stolen first arg of testlist_comp push_result_node(parser, (mp_parse_node_t)pn); } push_result_node(parser, (mp_parse_node_t)pn); } mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { // initialise parser and allocate memory for its stacks parser_t parser; parser.rule_stack_alloc = MICROPY_ALLOC_PARSE_RULE_INIT; parser.rule_stack_top = 0; parser.rule_stack = m_new(rule_stack_t, parser.rule_stack_alloc); parser.result_stack_alloc = MICROPY_ALLOC_PARSE_RESULT_INIT; parser.result_stack_top = 0; parser.result_stack = m_new(mp_parse_node_t, parser.result_stack_alloc); parser.lexer = lex; parser.tree.chunk = NULL; parser.cur_chunk = NULL; #if MICROPY_COMP_CONST mp_map_init(&parser.consts, 0); #endif // work out the top-level rule to use, and push it on the stack size_t top_level_rule; switch (input_kind) { case MP_PARSE_SINGLE_INPUT: top_level_rule = RULE_single_input; break; case MP_PARSE_EVAL_INPUT: top_level_rule = RULE_eval_input; break; default: top_level_rule = RULE_file_input; } push_rule(&parser, lex->tok_line, top_level_rule, 0); // parse! bool backtrack = false; for (;;) { next_rule: if (parser.rule_stack_top == 0) { break; } // Pop the next rule to process it size_t i; // state for the current rule size_t rule_src_line; // source line for the first token matched by the current rule uint8_t rule_id = pop_rule(&parser, &i, &rule_src_line); uint8_t rule_act = rule_act_table[rule_id]; const uint16_t *rule_arg = get_rule_arg(rule_id); size_t n = rule_act & RULE_ACT_ARG_MASK; #if 0 // debugging printf("depth=" UINT_FMT " ", parser.rule_stack_top); for (int j = 0; j < parser.rule_stack_top; ++j) { printf(" "); } printf("%s n=" UINT_FMT " i=" UINT_FMT " bt=%d\n", rule_name_table[rule_id], n, i, backtrack); #endif switch (rule_act & RULE_ACT_KIND_MASK) { case RULE_ACT_OR: if (i > 0 && !backtrack) { goto next_rule; } else { backtrack = false; } for (; i < n; ++i) { uint16_t kind = rule_arg[i] & RULE_ARG_KIND_MASK; if (kind == RULE_ARG_TOK) { if (lex->tok_kind == (rule_arg[i] & RULE_ARG_ARG_MASK)) { push_result_token(&parser, rule_id); mp_lexer_to_next(lex); goto next_rule; } } else { assert(kind == RULE_ARG_RULE); if (i + 1 < n) { push_rule(&parser, rule_src_line, rule_id, i + 1); // save this or-rule } push_rule_from_arg(&parser, rule_arg[i]); // push child of or-rule goto next_rule; } } backtrack = true; break; case RULE_ACT_AND: { // failed, backtrack if we can, else syntax error if (backtrack) { assert(i > 0); if ((rule_arg[i - 1] & RULE_ARG_KIND_MASK) == RULE_ARG_OPT_RULE) { // an optional rule that failed, so continue with next arg push_result_node(&parser, MP_PARSE_NODE_NULL); backtrack = false; } else { // a mandatory rule that failed, so propagate backtrack if (i > 1) { // already eaten tokens so can't backtrack goto syntax_error; } else { goto next_rule; } } } // progress through the rule for (; i < n; ++i) { if ((rule_arg[i] & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { // need to match a token mp_token_kind_t tok_kind = rule_arg[i] & RULE_ARG_ARG_MASK; if (lex->tok_kind == tok_kind) { // matched token if (tok_kind == MP_TOKEN_NAME) { push_result_token(&parser, rule_id); } mp_lexer_to_next(lex); } else { // failed to match token if (i > 0) { // already eaten tokens so can't backtrack goto syntax_error; } else { // this rule failed, so backtrack backtrack = true; goto next_rule; } } } else { push_rule(&parser, rule_src_line, rule_id, i + 1); // save this and-rule push_rule_from_arg(&parser, rule_arg[i]); // push child of and-rule goto next_rule; } } assert(i == n); // matched the rule, so now build the corresponding parse_node #if !MICROPY_ENABLE_DOC_STRING // this code discards lonely statements, such as doc strings if (input_kind != MP_PARSE_SINGLE_INPUT && rule_id == RULE_expr_stmt && peek_result(&parser, 0) == MP_PARSE_NODE_NULL) { mp_parse_node_t p = peek_result(&parser, 1); if ((MP_PARSE_NODE_IS_LEAF(p) && !MP_PARSE_NODE_IS_ID(p)) || MP_PARSE_NODE_IS_STRUCT_KIND(p, RULE_const_object)) { pop_result(&parser); // MP_PARSE_NODE_NULL pop_result(&parser); // const expression (leaf or RULE_const_object) // Pushing the "pass" rule here will overwrite any RULE_const_object // entry that was on the result stack, allowing the GC to reclaim // the memory from the const object when needed. push_result_rule(&parser, rule_src_line, RULE_pass_stmt, 0); break; } } #endif // count number of arguments for the parse node i = 0; size_t num_not_nil = 0; for (size_t x = n; x > 0;) { --x; if ((rule_arg[x] & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { mp_token_kind_t tok_kind = rule_arg[x] & RULE_ARG_ARG_MASK; if (tok_kind == MP_TOKEN_NAME) { // only tokens which were names are pushed to stack i += 1; num_not_nil += 1; } } else { // rules are always pushed if (peek_result(&parser, i) != MP_PARSE_NODE_NULL) { num_not_nil += 1; } i += 1; } } if (num_not_nil == 1 && (rule_act & RULE_ACT_ALLOW_IDENT)) { // this rule has only 1 argument and should not be emitted mp_parse_node_t pn = MP_PARSE_NODE_NULL; for (size_t x = 0; x < i; ++x) { mp_parse_node_t pn2 = pop_result(&parser); if (pn2 != MP_PARSE_NODE_NULL) { pn = pn2; } } push_result_node(&parser, pn); } else { // this rule must be emitted if (rule_act & RULE_ACT_ADD_BLANK) { // and add an extra blank node at the end (used by the compiler to store data) push_result_node(&parser, MP_PARSE_NODE_NULL); i += 1; } push_result_rule(&parser, rule_src_line, rule_id, i); } break; } default: { assert((rule_act & RULE_ACT_KIND_MASK) == RULE_ACT_LIST); // n=2 is: item item* // n=1 is: item (sep item)* // n=3 is: item (sep item)* [sep] bool had_trailing_sep; if (backtrack) { list_backtrack: had_trailing_sep = false; if (n == 2) { if (i == 1) { // fail on item, first time round; propagate backtrack goto next_rule; } else { // fail on item, in later rounds; finish with this rule backtrack = false; } } else { if (i == 1) { // fail on item, first time round; propagate backtrack goto next_rule; } else if ((i & 1) == 1) { // fail on item, in later rounds; have eaten tokens so can't backtrack if (n == 3) { // list allows trailing separator; finish parsing list had_trailing_sep = true; backtrack = false; } else { // list doesn't allowing trailing separator; fail goto syntax_error; } } else { // fail on separator; finish parsing list backtrack = false; } } } else { for (;;) { size_t arg = rule_arg[i & 1 & n]; if ((arg & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { if (lex->tok_kind == (arg & RULE_ARG_ARG_MASK)) { if (i & 1 & n) { // separators which are tokens are not pushed to result stack } else { push_result_token(&parser, rule_id); } mp_lexer_to_next(lex); // got element of list, so continue parsing list i += 1; } else { // couldn't get element of list i += 1; backtrack = true; goto list_backtrack; } } else { assert((arg & RULE_ARG_KIND_MASK) == RULE_ARG_RULE); push_rule(&parser, rule_src_line, rule_id, i + 1); // save this list-rule push_rule_from_arg(&parser, arg); // push child of list-rule goto next_rule; } } } assert(i >= 1); // compute number of elements in list, result in i i -= 1; if ((n & 1) && (rule_arg[1] & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { // don't count separators when they are tokens i = (i + 1) / 2; } if (i == 1) { // list matched single item if (had_trailing_sep) { // if there was a trailing separator, make a list of a single item push_result_rule(&parser, rule_src_line, rule_id, i); } else { // just leave single item on stack (ie don't wrap in a list) } } else { push_result_rule(&parser, rule_src_line, rule_id, i); } break; } } } #if MICROPY_COMP_CONST mp_map_deinit(&parser.consts); #endif // truncate final chunk and link into chain of chunks if (parser.cur_chunk != NULL) { (void)m_renew_maybe(byte, parser.cur_chunk, sizeof(mp_parse_chunk_t) + parser.cur_chunk->alloc, sizeof(mp_parse_chunk_t) + parser.cur_chunk->union_.used, false); parser.cur_chunk->alloc = parser.cur_chunk->union_.used; parser.cur_chunk->union_.next = parser.tree.chunk; parser.tree.chunk = parser.cur_chunk; } if ( lex->tok_kind != MP_TOKEN_END // check we are at the end of the token stream || parser.result_stack_top == 0 // check that we got a node (can fail on empty input) ) { syntax_error:; mp_obj_t exc; if (lex->tok_kind == MP_TOKEN_INDENT) { exc = mp_obj_new_exception_msg(&mp_type_IndentationError, MP_ERROR_TEXT("unexpected indent")); } else if (lex->tok_kind == MP_TOKEN_DEDENT_MISMATCH) { exc = mp_obj_new_exception_msg(&mp_type_IndentationError, MP_ERROR_TEXT("unindent doesn't match any outer indent level")); #if MICROPY_PY_FSTRINGS } else if (lex->tok_kind == MP_TOKEN_MALFORMED_FSTRING) { exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, MP_ERROR_TEXT("malformed f-string")); } else if (lex->tok_kind == MP_TOKEN_FSTRING_RAW) { exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, MP_ERROR_TEXT("raw f-strings are not supported")); #endif } else { exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, MP_ERROR_TEXT("invalid syntax")); } // add traceback to give info about file name and location // we don't have a 'block' name, so just pass the NULL qstr to indicate this mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); nlr_raise(exc); } // get the root parse node that we created assert(parser.result_stack_top == 1); parser.tree.root = parser.result_stack[0]; // free the memory that we don't need anymore m_del(rule_stack_t, parser.rule_stack, parser.rule_stack_alloc); m_del(mp_parse_node_t, parser.result_stack, parser.result_stack_alloc); // we also free the lexer on behalf of the caller mp_lexer_free(lex); return parser.tree; } void mp_parse_tree_clear(mp_parse_tree_t *tree) { mp_parse_chunk_t *chunk = tree->chunk; while (chunk != NULL) { mp_parse_chunk_t *next = chunk->union_.next; m_del(byte, chunk, sizeof(mp_parse_chunk_t) + chunk->alloc); chunk = next; } } #endif // MICROPY_ENABLE_COMPILER #include #include "py/scope.h" #if MICROPY_ENABLE_COMPILER // These low numbered qstrs should fit in 8 bits. See assertions below. STATIC const uint8_t scope_simple_name_table[] = { [SCOPE_MODULE] = MP_QSTR__lt_module_gt_, [SCOPE_LAMBDA] = MP_QSTR__lt_lambda_gt_, [SCOPE_LIST_COMP] = MP_QSTR__lt_listcomp_gt_, [SCOPE_DICT_COMP] = MP_QSTR__lt_dictcomp_gt_, [SCOPE_SET_COMP] = MP_QSTR__lt_setcomp_gt_, [SCOPE_GEN_EXPR] = MP_QSTR__lt_genexpr_gt_, }; scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, mp_uint_t emit_options) { // Make sure those qstrs indeed fit in an uint8_t. MP_STATIC_ASSERT(MP_QSTR__lt_module_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_lambda_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_listcomp_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_dictcomp_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_setcomp_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_genexpr_gt_ <= UINT8_MAX); scope_t *scope = m_new0(scope_t, 1); scope->kind = kind; scope->pn = pn; if (kind == SCOPE_FUNCTION || kind == SCOPE_CLASS) { assert(MP_PARSE_NODE_IS_STRUCT(pn)); scope->simple_name = MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn)->nodes[0]); } else { scope->simple_name = scope_simple_name_table[kind]; } scope->raw_code = mp_emit_glue_new_raw_code(); scope->emit_options = emit_options; scope->id_info_alloc = MICROPY_ALLOC_SCOPE_ID_INIT; scope->id_info = m_new(id_info_t, scope->id_info_alloc); return scope; } void scope_free(scope_t *scope) { m_del(id_info_t, scope->id_info, scope->id_info_alloc); m_del(scope_t, scope, 1); } id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, id_info_kind_t kind) { id_info_t *id_info = scope_find(scope, qst); if (id_info != NULL) { return id_info; } // make sure we have enough memory if (scope->id_info_len >= scope->id_info_alloc) { scope->id_info = m_renew(id_info_t, scope->id_info, scope->id_info_alloc, scope->id_info_alloc + MICROPY_ALLOC_SCOPE_ID_INC); scope->id_info_alloc += MICROPY_ALLOC_SCOPE_ID_INC; } // add new id to end of array of all ids; this seems to match CPython // important thing is that function arguments are first, but that is // handled by the compiler because it adds arguments before compiling the body id_info = &scope->id_info[scope->id_info_len++]; id_info->kind = kind; id_info->flags = 0; id_info->local_num = 0; id_info->qst = qst; return id_info; } id_info_t *scope_find(scope_t *scope, qstr qst) { for (mp_uint_t i = 0; i < scope->id_info_len; i++) { if (scope->id_info[i].qst == qst) { return &scope->id_info[i]; } } return NULL; } id_info_t *scope_find_global(scope_t *scope, qstr qst) { while (scope->parent != NULL) { scope = scope->parent; } return scope_find(scope, qst); } STATIC void scope_close_over_in_parents(scope_t *scope, qstr qst) { assert(scope->parent != NULL); // we should have at least 1 parent for (scope_t *s = scope->parent;; s = s->parent) { assert(s->parent != NULL); // we should not get to the outer scope id_info_t *id = scope_find_or_add_id(s, qst, ID_INFO_KIND_UNDECIDED); if (id->kind == ID_INFO_KIND_UNDECIDED) { // variable not previously declared in this scope, so declare it as free and keep searching parents id->kind = ID_INFO_KIND_FREE; } else { // variable is declared in this scope, so finish if (id->kind == ID_INFO_KIND_LOCAL) { // variable local to this scope, close it over id->kind = ID_INFO_KIND_CELL; } else { // ID_INFO_KIND_FREE: variable already closed over in a parent scope // ID_INFO_KIND_CELL: variable already closed over in this scope assert(id->kind == ID_INFO_KIND_FREE || id->kind == ID_INFO_KIND_CELL); } return; } } } void scope_check_to_close_over(scope_t *scope, id_info_t *id) { if (scope->parent != NULL) { for (scope_t *s = scope->parent; s->parent != NULL; s = s->parent) { id_info_t *id2 = scope_find(s, id->qst); if (id2 != NULL) { if (id2->kind == ID_INFO_KIND_LOCAL || id2->kind == ID_INFO_KIND_CELL || id2->kind == ID_INFO_KIND_FREE) { id->kind = ID_INFO_KIND_FREE; scope_close_over_in_parents(scope, id->qst); } break; } } } } #endif // MICROPY_ENABLE_COMPILER #include #include #ifndef __IPU__ #include #endif #include #include #include "py/scope.h" #include "py/emit.h" #include "py/compile.h" #include "py/runtime.h" #include "py/asmbase.h" #include "py/nativeglue.h" #include "py/persistentcode.h" #if MICROPY_ENABLE_COMPILER // TODO need to mangle __attr names #define INVALID_LABEL (0xffff) typedef enum { // define rules with a compile function #define DEF_RULE(rule, comp, kind, ...) PN_##rule, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC PN_const_object, // special node for a constant, generic Python object // define rules without a compile function #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) PN_##rule, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC } pn_kind_t; // Whether a mp_parse_node_struct_t that has pns->kind == PN_testlist_comp // corresponds to a list comprehension or generator. #define MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns) \ (MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 2 && \ MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_comp_for)) #define NEED_METHOD_TABLE MICROPY_EMIT_NATIVE #if NEED_METHOD_TABLE // we need a method table to do the lookup for the emitter functions #define EMIT(fun) (comp->emit_method_table->fun(comp->emit)) #define EMIT_ARG(fun, ...) (comp->emit_method_table->fun(comp->emit, __VA_ARGS__)) #define EMIT_LOAD_FAST(qst, local_num) (comp->emit_method_table->load_id.local(comp->emit, qst, local_num, MP_EMIT_IDOP_LOCAL_FAST)) #define EMIT_LOAD_GLOBAL(qst) (comp->emit_method_table->load_id.global(comp->emit, qst, MP_EMIT_IDOP_GLOBAL_GLOBAL)) #else // if we only have the bytecode emitter enabled then we can do a direct call to the functions #define EMIT(fun) (mp_emit_bc_##fun(comp->emit)) #define EMIT_ARG(fun, ...) (mp_emit_bc_##fun(comp->emit, __VA_ARGS__)) #define EMIT_LOAD_FAST(qst, local_num) (mp_emit_bc_load_local(comp->emit, qst, local_num, MP_EMIT_IDOP_LOCAL_FAST)) #define EMIT_LOAD_GLOBAL(qst) (mp_emit_bc_load_global(comp->emit, qst, MP_EMIT_IDOP_GLOBAL_GLOBAL)) #endif #if MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER #define NATIVE_EMITTER(f) emit_native_table[mp_dynamic_compiler.native_arch]->emit_##f #define NATIVE_EMITTER_TABLE (emit_native_table[mp_dynamic_compiler.native_arch]) STATIC const emit_method_table_t *emit_native_table[] = { NULL, &emit_native_x86_method_table, &emit_native_x64_method_table, &emit_native_arm_method_table, &emit_native_thumb_method_table, &emit_native_thumb_method_table, &emit_native_thumb_method_table, &emit_native_thumb_method_table, &emit_native_thumb_method_table, &emit_native_xtensa_method_table, &emit_native_xtensawin_method_table, }; #elif MICROPY_EMIT_NATIVE // define a macro to access external native emitter #if MICROPY_EMIT_X64 #define NATIVE_EMITTER(f) emit_native_x64_##f #elif MICROPY_EMIT_X86 #define NATIVE_EMITTER(f) emit_native_x86_##f #elif MICROPY_EMIT_THUMB #define NATIVE_EMITTER(f) emit_native_thumb_##f #elif MICROPY_EMIT_ARM #define NATIVE_EMITTER(f) emit_native_arm_##f #elif MICROPY_EMIT_XTENSA #define NATIVE_EMITTER(f) emit_native_xtensa_##f #elif MICROPY_EMIT_XTENSAWIN #define NATIVE_EMITTER(f) emit_native_xtensawin_##f #else #error "unknown native emitter" #endif #define NATIVE_EMITTER_TABLE (&NATIVE_EMITTER(method_table)) #endif #if MICROPY_EMIT_INLINE_ASM && MICROPY_DYNAMIC_COMPILER #define ASM_EMITTER(f) emit_asm_table[mp_dynamic_compiler.native_arch]->asm_##f #define ASM_EMITTER_TABLE emit_asm_table[mp_dynamic_compiler.native_arch] STATIC const emit_inline_asm_method_table_t *emit_asm_table[] = { NULL, NULL, NULL, &emit_inline_thumb_method_table, &emit_inline_thumb_method_table, &emit_inline_thumb_method_table, &emit_inline_thumb_method_table, &emit_inline_thumb_method_table, &emit_inline_thumb_method_table, &emit_inline_xtensa_method_table, NULL, }; #elif MICROPY_EMIT_INLINE_ASM // define macros for inline assembler #if MICROPY_EMIT_INLINE_THUMB #define ASM_DECORATOR_QSTR MP_QSTR_asm_thumb #define ASM_EMITTER(f) emit_inline_thumb_##f #elif MICROPY_EMIT_INLINE_XTENSA #define ASM_DECORATOR_QSTR MP_QSTR_asm_xtensa #define ASM_EMITTER(f) emit_inline_xtensa_##f #else #error "unknown asm emitter" #endif #define ASM_EMITTER_TABLE &ASM_EMITTER(method_table) #endif #define EMIT_INLINE_ASM(fun) (comp->emit_inline_asm_method_table->fun(comp->emit_inline_asm)) #define EMIT_INLINE_ASM_ARG(fun, ...) (comp->emit_inline_asm_method_table->fun(comp->emit_inline_asm, __VA_ARGS__)) // elements in this struct are ordered to make it compact typedef struct _compiler_t { uint8_t is_repl; uint8_t pass; // holds enum type pass_kind_t uint8_t have_star; // try to keep compiler clean from nlr mp_obj_t compile_error; // set to an exception object if there's an error size_t compile_error_line; // set to best guess of line of error uint next_label; uint16_t num_dict_params; uint16_t num_default_params; uint16_t break_label; // highest bit set indicates we are breaking out of a for loop uint16_t continue_label; uint16_t cur_except_level; // increased for SETUP_EXCEPT, SETUP_FINALLY; decreased for POP_BLOCK, POP_EXCEPT uint16_t break_continue_except_level; scope_t *scope_head; scope_t *scope_cur; mp_emit_common_t emit_common; emit_t *emit; // current emitter emit_t *emit_bc; #if NEED_METHOD_TABLE const emit_method_table_t *emit_method_table; // current emit method table #endif #if MICROPY_EMIT_INLINE_ASM emit_inline_asm_t *emit_inline_asm; // current emitter for inline asm const emit_inline_asm_method_table_t *emit_inline_asm_method_table; // current emit method table for inline asm #endif } compiler_t; /******************************************************************************/ // mp_emit_common_t helper functions // These are defined here so they can be inlined, to reduce code size. STATIC void mp_emit_common_init(mp_emit_common_t *emit, qstr source_file) { #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE mp_map_init(&emit->qstr_map, 1); // add the source file as the first entry in the qstr table mp_map_elem_t *elem = mp_map_lookup(&emit->qstr_map, MP_OBJ_NEW_QSTR(source_file), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); elem->value = MP_OBJ_NEW_SMALL_INT(0); #endif } STATIC void mp_emit_common_start_pass(mp_emit_common_t *emit, pass_kind_t pass) { emit->pass = pass; if (pass == MP_PASS_STACK_SIZE) { emit->ct_cur_obj_base = emit->ct_cur_obj; } else if (pass > MP_PASS_STACK_SIZE) { emit->ct_cur_obj = emit->ct_cur_obj_base; } if (pass == MP_PASS_EMIT) { if (emit->ct_cur_child == 0) { emit->children = NULL; } else { emit->children = m_new0(mp_raw_code_t *, emit->ct_cur_child); } } emit->ct_cur_child = 0; } STATIC void mp_emit_common_finalise(mp_emit_common_t *emit, bool has_native_code) { emit->ct_cur_obj += has_native_code; // allocate an additional slot for &mp_fun_table emit->const_table = m_new0(mp_uint_t, emit->ct_cur_obj); emit->ct_cur_obj = has_native_code; // reserve slot 0 for &mp_fun_table #if MICROPY_EMIT_NATIVE if (has_native_code) { // store mp_fun_table pointer at the start of the constant table emit->const_table[0] = (mp_uint_t)(uintptr_t)&mp_fun_table; } #endif } STATIC void mp_emit_common_populate_module_context(mp_emit_common_t *emit, qstr source_file, mp_module_context_t *context) { #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE size_t qstr_map_used = emit->qstr_map.used; mp_module_context_alloc_tables(context, qstr_map_used, emit->ct_cur_obj); for (size_t i = 0; i < emit->qstr_map.alloc; ++i) { if (mp_map_slot_is_filled(&emit->qstr_map, i)) { size_t idx = MP_OBJ_SMALL_INT_VALUE(emit->qstr_map.table[i].value); qstr qst = MP_OBJ_QSTR_VALUE(emit->qstr_map.table[i].key); context->constants.qstr_table[idx] = qst; } } #else mp_module_context_alloc_tables(context, 0, emit->ct_cur_obj); context->constants.source_file = source_file; #endif if (emit->ct_cur_obj > 0) { memcpy(context->constants.obj_table, emit->const_table, emit->ct_cur_obj * sizeof(mp_uint_t)); } } /******************************************************************************/ STATIC void compile_error_set_line(compiler_t *comp, mp_parse_node_t pn) { // if the line of the error is unknown then try to update it from the pn if (comp->compile_error_line == 0 && MP_PARSE_NODE_IS_STRUCT(pn)) { comp->compile_error_line = ((mp_parse_node_struct_t *)pn)->source_line; } } STATIC void compile_syntax_error(compiler_t *comp, mp_parse_node_t pn, mp_rom_error_text_t msg) { // only register the error if there has been no other error if (comp->compile_error == MP_OBJ_NULL) { comp->compile_error = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg); compile_error_set_line(comp, pn); } } STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_arglist, bool is_method_call, int n_positional_extra); STATIC void compile_comprehension(compiler_t *comp, mp_parse_node_struct_t *pns, scope_kind_t kind); STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t *pns, bool create_map); STATIC void compile_node(compiler_t *comp, mp_parse_node_t pn); STATIC uint comp_next_label(compiler_t *comp) { return comp->next_label++; } #if MICROPY_EMIT_NATIVE STATIC void reserve_labels_for_native(compiler_t *comp, int n) { if (comp->scope_cur->emit_options != MP_EMIT_OPT_BYTECODE) { comp->next_label += n; } } #else #define reserve_labels_for_native(comp, n) #endif STATIC void compile_increase_except_level(compiler_t *comp, uint label, int kind) { EMIT_ARG(setup_block, label, kind); comp->cur_except_level += 1; if (comp->cur_except_level > comp->scope_cur->exc_stack_size) { comp->scope_cur->exc_stack_size = comp->cur_except_level; } } STATIC void compile_decrease_except_level(compiler_t *comp) { assert(comp->cur_except_level > 0); comp->cur_except_level -= 1; EMIT(end_finally); reserve_labels_for_native(comp, 1); } STATIC scope_t *scope_new_and_link(compiler_t *comp, scope_kind_t kind, mp_parse_node_t pn, uint emit_options) { scope_t *scope = scope_new(kind, pn, emit_options); scope->parent = comp->scope_cur; scope->next = NULL; if (comp->scope_head == NULL) { comp->scope_head = scope; } else { scope_t *s = comp->scope_head; while (s->next != NULL) { s = s->next; } s->next = scope; } return scope; } typedef void (*apply_list_fun_t)(compiler_t *comp, mp_parse_node_t pn); STATIC void apply_to_single_or_list(compiler_t *comp, mp_parse_node_t pn, pn_kind_t pn_list_kind, apply_list_fun_t f) { if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, pn_list_kind)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (int i = 0; i < num_nodes; i++) { f(comp, pns->nodes[i]); } } else if (!MP_PARSE_NODE_IS_NULL(pn)) { f(comp, pn); } } STATIC void compile_generic_all_nodes(compiler_t *comp, mp_parse_node_struct_t *pns) { int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (int i = 0; i < num_nodes; i++) { compile_node(comp, pns->nodes[i]); if (comp->compile_error != MP_OBJ_NULL) { // add line info for the error in case it didn't have a line number compile_error_set_line(comp, pns->nodes[i]); return; } } } STATIC void compile_load_id(compiler_t *comp, qstr qst) { if (comp->pass == MP_PASS_SCOPE) { mp_emit_common_get_id_for_load(comp->scope_cur, qst); } { #if NEED_METHOD_TABLE mp_emit_common_id_op(comp->emit, &comp->emit_method_table->load_id, comp->scope_cur, qst); #else mp_emit_common_id_op(comp->emit, &mp_emit_bc_method_table_load_id_ops, comp->scope_cur, qst); #endif } } STATIC void compile_store_id(compiler_t *comp, qstr qst) { if (comp->pass == MP_PASS_SCOPE) { mp_emit_common_get_id_for_modification(comp->scope_cur, qst); } { #if NEED_METHOD_TABLE mp_emit_common_id_op(comp->emit, &comp->emit_method_table->store_id, comp->scope_cur, qst); #else mp_emit_common_id_op(comp->emit, &mp_emit_bc_method_table_store_id_ops, comp->scope_cur, qst); #endif } } STATIC void compile_delete_id(compiler_t *comp, qstr qst) { if (comp->pass == MP_PASS_SCOPE) { mp_emit_common_get_id_for_modification(comp->scope_cur, qst); } { #if NEED_METHOD_TABLE mp_emit_common_id_op(comp->emit, &comp->emit_method_table->delete_id, comp->scope_cur, qst); #else mp_emit_common_id_op(comp->emit, &mp_emit_bc_method_table_delete_id_ops, comp->scope_cur, qst); #endif } } STATIC void compile_generic_tuple(compiler_t *comp, mp_parse_node_struct_t *pns) { // a simple tuple expression size_t num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (size_t i = 0; i < num_nodes; i++) { compile_node(comp, pns->nodes[i]); } EMIT_ARG(build, num_nodes, MP_EMIT_BUILD_TUPLE); } STATIC void c_if_cond(compiler_t *comp, mp_parse_node_t pn, bool jump_if, int label) { if (mp_parse_node_is_const_false(pn)) { if (jump_if == false) { EMIT_ARG(jump, label); } return; } else if (mp_parse_node_is_const_true(pn)) { if (jump_if == true) { EMIT_ARG(jump, label); } return; } else if (MP_PARSE_NODE_IS_STRUCT(pn)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_or_test) { if (jump_if == false) { and_or_logic1:; uint label2 = comp_next_label(comp); for (int i = 0; i < n - 1; i++) { c_if_cond(comp, pns->nodes[i], !jump_if, label2); } c_if_cond(comp, pns->nodes[n - 1], jump_if, label); EMIT_ARG(label_assign, label2); } else { and_or_logic2: for (int i = 0; i < n; i++) { c_if_cond(comp, pns->nodes[i], jump_if, label); } } return; } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_and_test) { if (jump_if == false) { goto and_or_logic2; } else { goto and_or_logic1; } } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_not_test_2) { c_if_cond(comp, pns->nodes[0], !jump_if, label); return; } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_atom_paren) { // cond is something in parenthesis if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // empty tuple, acts as false for the condition if (jump_if == false) { EMIT_ARG(jump, label); } } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)); // non-empty tuple, acts as true for the condition if (jump_if == true) { EMIT_ARG(jump, label); } } return; } } // nothing special, fall back to default compiling for node and jump compile_node(comp, pn); EMIT_ARG(pop_jump_if, jump_if, label); } typedef enum { ASSIGN_STORE, ASSIGN_AUG_LOAD, ASSIGN_AUG_STORE } assign_kind_t; STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t kind); STATIC void c_assign_atom_expr(compiler_t *comp, mp_parse_node_struct_t *pns, assign_kind_t assign_kind) { if (assign_kind != ASSIGN_AUG_STORE) { compile_node(comp, pns->nodes[0]); } if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_atom_expr_trailers) { int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1); if (assign_kind != ASSIGN_AUG_STORE) { for (int i = 0; i < n - 1; i++) { compile_node(comp, pns1->nodes[i]); } } assert(MP_PARSE_NODE_IS_STRUCT(pns1->nodes[n - 1])); pns1 = (mp_parse_node_struct_t *)pns1->nodes[n - 1]; } if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_trailer_bracket) { if (assign_kind == ASSIGN_AUG_STORE) { EMIT(rot_three); EMIT_ARG(subscr, MP_EMIT_SUBSCR_STORE); } else { compile_node(comp, pns1->nodes[0]); if (assign_kind == ASSIGN_AUG_LOAD) { EMIT(dup_top_two); EMIT_ARG(subscr, MP_EMIT_SUBSCR_LOAD); } else { EMIT_ARG(subscr, MP_EMIT_SUBSCR_STORE); } } return; } else if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_trailer_period) { assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0])); if (assign_kind == ASSIGN_AUG_LOAD) { EMIT(dup_top); EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]), MP_EMIT_ATTR_LOAD); } else { if (assign_kind == ASSIGN_AUG_STORE) { EMIT(rot_two); } EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]), MP_EMIT_ATTR_STORE); } return; } } compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("can't assign to expression")); } STATIC void c_assign_tuple(compiler_t *comp, uint num_tail, mp_parse_node_t *nodes_tail) { // look for star expression uint have_star_index = -1; for (uint i = 0; i < num_tail; i++) { if (MP_PARSE_NODE_IS_STRUCT_KIND(nodes_tail[i], PN_star_expr)) { if (have_star_index == (uint)-1) { EMIT_ARG(unpack_ex, i, num_tail - i - 1); have_star_index = i; } else { compile_syntax_error(comp, nodes_tail[i], MP_ERROR_TEXT("multiple *x in assignment")); return; } } } if (have_star_index == (uint)-1) { EMIT_ARG(unpack_sequence, num_tail); } for (uint i = 0; i < num_tail; i++) { if (i == have_star_index) { c_assign(comp, ((mp_parse_node_struct_t *)nodes_tail[i])->nodes[0], ASSIGN_STORE); } else { c_assign(comp, nodes_tail[i], ASSIGN_STORE); } } } // assigns top of stack to pn STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t assign_kind) { assert(!MP_PARSE_NODE_IS_NULL(pn)); if (MP_PARSE_NODE_IS_LEAF(pn)) { if (MP_PARSE_NODE_IS_ID(pn)) { qstr arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (assign_kind) { case ASSIGN_STORE: case ASSIGN_AUG_STORE: compile_store_id(comp, arg); break; case ASSIGN_AUG_LOAD: default: compile_load_id(comp, arg); break; } } else { goto cannot_assign; } } else { // pn must be a struct mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; switch (MP_PARSE_NODE_STRUCT_KIND(pns)) { case PN_atom_expr_normal: // lhs is an index or attribute c_assign_atom_expr(comp, pns, assign_kind); break; case PN_testlist_star_expr: case PN_exprlist: // lhs is a tuple if (assign_kind != ASSIGN_STORE) { goto cannot_assign; } c_assign_tuple(comp, MP_PARSE_NODE_STRUCT_NUM_NODES(pns), pns->nodes); break; case PN_atom_paren: // lhs is something in parenthesis if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // empty tuple goto cannot_assign; } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)); if (assign_kind != ASSIGN_STORE) { goto cannot_assign; } pns = (mp_parse_node_struct_t *)pns->nodes[0]; goto testlist_comp; } break; case PN_atom_bracket: // lhs is something in brackets if (assign_kind != ASSIGN_STORE) { goto cannot_assign; } if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // empty list, assignment allowed c_assign_tuple(comp, 0, NULL); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)) { pns = (mp_parse_node_struct_t *)pns->nodes[0]; goto testlist_comp; } else { // brackets around 1 item c_assign_tuple(comp, 1, pns->nodes); } break; default: goto cannot_assign; } return; testlist_comp: // lhs is a sequence if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { goto cannot_assign; } c_assign_tuple(comp, MP_PARSE_NODE_STRUCT_NUM_NODES(pns), pns->nodes); return; } return; cannot_assign: compile_syntax_error(comp, pn, MP_ERROR_TEXT("can't assign to expression")); } // stuff for lambda and comprehensions and generators: // if n_pos_defaults > 0 then there is a tuple on the stack with the positional defaults // if n_kw_defaults > 0 then there is a dictionary on the stack with the keyword defaults // if both exist, the tuple is above the dictionary (ie the first pop gets the tuple) STATIC void close_over_variables_etc(compiler_t *comp, scope_t *this_scope, int n_pos_defaults, int n_kw_defaults) { assert(n_pos_defaults >= 0); assert(n_kw_defaults >= 0); // set flags if (n_kw_defaults > 0) { this_scope->scope_flags |= MP_SCOPE_FLAG_DEFKWARGS; } this_scope->num_def_pos_args = n_pos_defaults; #if MICROPY_EMIT_NATIVE // When creating a function/closure it will take a reference to the current globals comp->scope_cur->scope_flags |= MP_SCOPE_FLAG_REFGLOBALS | MP_SCOPE_FLAG_HASCONSTS; #endif // make closed over variables, if any // ensure they are closed over in the order defined in the outer scope (mainly to agree with CPython) int nfree = 0; if (comp->scope_cur->kind != SCOPE_MODULE) { for (int i = 0; i < comp->scope_cur->id_info_len; i++) { id_info_t *id = &comp->scope_cur->id_info[i]; if (id->kind == ID_INFO_KIND_CELL || id->kind == ID_INFO_KIND_FREE) { for (int j = 0; j < this_scope->id_info_len; j++) { id_info_t *id2 = &this_scope->id_info[j]; if (id2->kind == ID_INFO_KIND_FREE && id->qst == id2->qst) { // in MicroPython we load closures using LOAD_FAST EMIT_LOAD_FAST(id->qst, id->local_num); nfree += 1; } } } } } // make the function/closure if (nfree == 0) { EMIT_ARG(make_function, this_scope, n_pos_defaults, n_kw_defaults); } else { EMIT_ARG(make_closure, this_scope, nfree, n_pos_defaults, n_kw_defaults); } } STATIC void compile_funcdef_lambdef_param(compiler_t *comp, mp_parse_node_t pn) { // For efficiency of the code below we extract the parse-node kind here int pn_kind; if (MP_PARSE_NODE_IS_ID(pn)) { pn_kind = -1; } else { assert(MP_PARSE_NODE_IS_STRUCT(pn)); pn_kind = MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pn); } if (pn_kind == PN_typedargslist_star || pn_kind == PN_varargslist_star) { comp->have_star = true; /* don't need to distinguish bare from named star mp_parse_node_struct_t *pns = (mp_parse_node_struct_t*)pn; if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // bare star } else { // named star } */ } else if (pn_kind == PN_typedargslist_dbl_star || pn_kind == PN_varargslist_dbl_star) { // named double star // TODO do we need to do anything with this? } else { mp_parse_node_t pn_id; mp_parse_node_t pn_equal; if (pn_kind == -1) { // this parameter is just an id pn_id = pn; pn_equal = MP_PARSE_NODE_NULL; } else if (pn_kind == PN_typedargslist_name) { // this parameter has a colon and/or equal specifier mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; pn_id = pns->nodes[0]; // pn_colon = pns->nodes[1]; // unused pn_equal = pns->nodes[2]; } else { assert(pn_kind == PN_varargslist_name); // should be // this parameter has an equal specifier mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; pn_id = pns->nodes[0]; pn_equal = pns->nodes[1]; } if (MP_PARSE_NODE_IS_NULL(pn_equal)) { // this parameter does not have a default value // check for non-default parameters given after default parameters (allowed by parser, but not syntactically valid) if (!comp->have_star && comp->num_default_params != 0) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("non-default argument follows default argument")); return; } } else { // this parameter has a default value // in CPython, None (and True, False?) as default parameters are loaded with LOAD_NAME; don't understandy why if (comp->have_star) { comp->num_dict_params += 1; // in MicroPython we put the default dict parameters into a dictionary using the bytecode if (comp->num_dict_params == 1) { // in MicroPython we put the default positional parameters into a tuple using the bytecode // we need to do this here before we start building the map for the default keywords if (comp->num_default_params > 0) { EMIT_ARG(build, comp->num_default_params, MP_EMIT_BUILD_TUPLE); } else { EMIT(load_null); // sentinel indicating empty default positional args } // first default dict param, so make the map EMIT_ARG(build, 0, MP_EMIT_BUILD_MAP); } // compile value then key, then store it to the dict compile_node(comp, pn_equal); EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pn_id)); EMIT(store_map); } else { comp->num_default_params += 1; compile_node(comp, pn_equal); } } } } STATIC void compile_funcdef_lambdef(compiler_t *comp, scope_t *scope, mp_parse_node_t pn_params, pn_kind_t pn_list_kind) { // When we call compile_funcdef_lambdef_param below it can compile an arbitrary // expression for default arguments, which may contain a lambda. The lambda will // call here in a nested way, so we must save and restore the relevant state. bool orig_have_star = comp->have_star; uint16_t orig_num_dict_params = comp->num_dict_params; uint16_t orig_num_default_params = comp->num_default_params; // compile default parameters comp->have_star = false; comp->num_dict_params = 0; comp->num_default_params = 0; apply_to_single_or_list(comp, pn_params, pn_list_kind, compile_funcdef_lambdef_param); if (comp->compile_error != MP_OBJ_NULL) { return; } // in MicroPython we put the default positional parameters into a tuple using the bytecode // the default keywords args may have already made the tuple; if not, do it now if (comp->num_default_params > 0 && comp->num_dict_params == 0) { EMIT_ARG(build, comp->num_default_params, MP_EMIT_BUILD_TUPLE); EMIT(load_null); // sentinel indicating empty default keyword args } // make the function close_over_variables_etc(comp, scope, comp->num_default_params, comp->num_dict_params); // restore state comp->have_star = orig_have_star; comp->num_dict_params = orig_num_dict_params; comp->num_default_params = orig_num_default_params; } // leaves function object on stack // returns function name STATIC qstr compile_funcdef_helper(compiler_t *comp, mp_parse_node_struct_t *pns, uint emit_options) { if (comp->pass == MP_PASS_SCOPE) { // create a new scope for this function scope_t *s = scope_new_and_link(comp, SCOPE_FUNCTION, (mp_parse_node_t)pns, emit_options); // store the function scope so the compiling function can use it at each pass pns->nodes[4] = (mp_parse_node_t)s; } // get the scope for this function scope_t *fscope = (scope_t *)pns->nodes[4]; // compile the function definition compile_funcdef_lambdef(comp, fscope, pns->nodes[1], PN_typedargslist); // return its name (the 'f' in "def f(...):") return fscope->simple_name; } // leaves class object on stack // returns class name STATIC qstr compile_classdef_helper(compiler_t *comp, mp_parse_node_struct_t *pns, uint emit_options) { if (comp->pass == MP_PASS_SCOPE) { // create a new scope for this class scope_t *s = scope_new_and_link(comp, SCOPE_CLASS, (mp_parse_node_t)pns, emit_options); // store the class scope so the compiling function can use it at each pass pns->nodes[3] = (mp_parse_node_t)s; } EMIT(load_build_class); // scope for this class scope_t *cscope = (scope_t *)pns->nodes[3]; // compile the class close_over_variables_etc(comp, cscope, 0, 0); // get its name EMIT_ARG(load_const_str, cscope->simple_name); // nodes[1] has parent classes, if any // empty parenthesis (eg class C():) gets here as an empty PN_classdef_2 and needs special handling mp_parse_node_t parents = pns->nodes[1]; if (MP_PARSE_NODE_IS_STRUCT_KIND(parents, PN_classdef_2)) { parents = MP_PARSE_NODE_NULL; } compile_trailer_paren_helper(comp, parents, false, 2); // return its name (the 'C' in class C(...):") return cscope->simple_name; } // returns true if it was a built-in decorator (even if the built-in had an error) STATIC bool compile_built_in_decorator(compiler_t *comp, size_t name_len, mp_parse_node_t *name_nodes, uint *emit_options) { if (MP_PARSE_NODE_LEAF_ARG(name_nodes[0]) != MP_QSTR_micropython) { return false; } if (name_len != 2) { compile_syntax_error(comp, name_nodes[0], MP_ERROR_TEXT("invalid micropython decorator")); return true; } qstr attr = MP_PARSE_NODE_LEAF_ARG(name_nodes[1]); if (attr == MP_QSTR_bytecode) { *emit_options = MP_EMIT_OPT_BYTECODE; #if MICROPY_EMIT_NATIVE } else if (attr == MP_QSTR_native) { *emit_options = MP_EMIT_OPT_NATIVE_PYTHON; } else if (attr == MP_QSTR_viper) { *emit_options = MP_EMIT_OPT_VIPER; #endif #if MICROPY_EMIT_INLINE_ASM #if MICROPY_DYNAMIC_COMPILER } else if (attr == MP_QSTR_asm_thumb) { *emit_options = MP_EMIT_OPT_ASM; } else if (attr == MP_QSTR_asm_xtensa) { *emit_options = MP_EMIT_OPT_ASM; #else } else if (attr == ASM_DECORATOR_QSTR) { *emit_options = MP_EMIT_OPT_ASM; #endif #endif } else { compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid micropython decorator")); } #if MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER if (*emit_options == MP_EMIT_OPT_NATIVE_PYTHON || *emit_options == MP_EMIT_OPT_VIPER) { if (emit_native_table[mp_dynamic_compiler.native_arch] == NULL) { compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid arch")); } } else if (*emit_options == MP_EMIT_OPT_ASM) { if (emit_asm_table[mp_dynamic_compiler.native_arch] == NULL) { compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid arch")); } } #endif return true; } STATIC void compile_decorated(compiler_t *comp, mp_parse_node_struct_t *pns) { // get the list of decorators mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[0], PN_decorators, &nodes); // inherit emit options for this function/class definition uint emit_options = comp->scope_cur->emit_options; // compile each decorator size_t num_built_in_decorators = 0; for (size_t i = 0; i < n; i++) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(nodes[i], PN_decorator)); // should be mp_parse_node_struct_t *pns_decorator = (mp_parse_node_struct_t *)nodes[i]; // nodes[0] contains the decorator function, which is a dotted name mp_parse_node_t *name_nodes; size_t name_len = mp_parse_node_extract_list(&pns_decorator->nodes[0], PN_dotted_name, &name_nodes); // check for built-in decorators if (compile_built_in_decorator(comp, name_len, name_nodes, &emit_options)) { // this was a built-in num_built_in_decorators += 1; } else { // not a built-in, compile normally // compile the decorator function compile_node(comp, name_nodes[0]); for (size_t j = 1; j < name_len; j++) { assert(MP_PARSE_NODE_IS_ID(name_nodes[j])); // should be EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(name_nodes[j]), MP_EMIT_ATTR_LOAD); } // nodes[1] contains arguments to the decorator function, if any if (!MP_PARSE_NODE_IS_NULL(pns_decorator->nodes[1])) { // call the decorator function with the arguments in nodes[1] compile_node(comp, pns_decorator->nodes[1]); } } } // compile the body (funcdef, async funcdef or classdef) and get its name mp_parse_node_struct_t *pns_body = (mp_parse_node_struct_t *)pns->nodes[1]; qstr body_name = 0; if (MP_PARSE_NODE_STRUCT_KIND(pns_body) == PN_funcdef) { body_name = compile_funcdef_helper(comp, pns_body, emit_options); #if MICROPY_PY_ASYNC_AWAIT } else if (MP_PARSE_NODE_STRUCT_KIND(pns_body) == PN_async_funcdef) { assert(MP_PARSE_NODE_IS_STRUCT(pns_body->nodes[0])); mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns_body->nodes[0]; body_name = compile_funcdef_helper(comp, pns0, emit_options); scope_t *fscope = (scope_t *)pns0->nodes[4]; fscope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; #endif } else { assert(MP_PARSE_NODE_STRUCT_KIND(pns_body) == PN_classdef); // should be body_name = compile_classdef_helper(comp, pns_body, emit_options); } // call each decorator for (size_t i = 0; i < n - num_built_in_decorators; i++) { EMIT_ARG(call_function, 1, 0, 0); } // store func/class object into name compile_store_id(comp, body_name); } STATIC void compile_funcdef(compiler_t *comp, mp_parse_node_struct_t *pns) { qstr fname = compile_funcdef_helper(comp, pns, comp->scope_cur->emit_options); // store function object into function name compile_store_id(comp, fname); } STATIC void c_del_stmt(compiler_t *comp, mp_parse_node_t pn) { if (MP_PARSE_NODE_IS_ID(pn)) { compile_delete_id(comp, MP_PARSE_NODE_LEAF_ARG(pn)); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_atom_expr_normal)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; compile_node(comp, pns->nodes[0]); // base of the atom_expr_normal node if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_atom_expr_trailers) { int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1); for (int i = 0; i < n - 1; i++) { compile_node(comp, pns1->nodes[i]); } assert(MP_PARSE_NODE_IS_STRUCT(pns1->nodes[n - 1])); pns1 = (mp_parse_node_struct_t *)pns1->nodes[n - 1]; } if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_trailer_bracket) { compile_node(comp, pns1->nodes[0]); EMIT_ARG(subscr, MP_EMIT_SUBSCR_DELETE); } else if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_trailer_period) { assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0])); EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]), MP_EMIT_ATTR_DELETE); } else { goto cannot_delete; } } else { goto cannot_delete; } } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_atom_paren)) { pn = ((mp_parse_node_struct_t *)pn)->nodes[0]; if (MP_PARSE_NODE_IS_NULL(pn)) { goto cannot_delete; } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_testlist_comp)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { goto cannot_delete; } for (size_t i = 0; i < MP_PARSE_NODE_STRUCT_NUM_NODES(pns); ++i) { c_del_stmt(comp, pns->nodes[i]); } } } else { // some arbitrary statement that we can't delete (eg del 1) goto cannot_delete; } return; cannot_delete: compile_syntax_error(comp, (mp_parse_node_t)pn, MP_ERROR_TEXT("can't delete expression")); } STATIC void compile_del_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { apply_to_single_or_list(comp, pns->nodes[0], PN_exprlist, c_del_stmt); } STATIC void compile_break_cont_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { uint16_t label; if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_break_stmt) { label = comp->break_label; } else { label = comp->continue_label; } if (label == INVALID_LABEL) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'break'/'continue' outside loop")); } assert(comp->cur_except_level >= comp->break_continue_except_level); EMIT_ARG(unwind_jump, label, comp->cur_except_level - comp->break_continue_except_level); } STATIC void compile_longyield_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT(longyield); } STATIC void compile_ipusyscall_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT(ipusyscall); } STATIC void compile_return_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { #if MICROPY_CPYTHON_COMPAT if (comp->scope_cur->kind != SCOPE_FUNCTION) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'return' outside function")); return; } #endif if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // no argument to 'return', so return None EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } else if (MICROPY_COMP_RETURN_IF_EXPR && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_test_if_expr)) { // special case when returning an if-expression; to match CPython optimisation mp_parse_node_struct_t *pns_test_if_expr = (mp_parse_node_struct_t *)pns->nodes[0]; mp_parse_node_struct_t *pns_test_if_else = (mp_parse_node_struct_t *)pns_test_if_expr->nodes[1]; uint l_fail = comp_next_label(comp); c_if_cond(comp, pns_test_if_else->nodes[0], false, l_fail); // condition compile_node(comp, pns_test_if_expr->nodes[0]); // success value EMIT(return_value); EMIT_ARG(label_assign, l_fail); compile_node(comp, pns_test_if_else->nodes[1]); // failure value } else { compile_node(comp, pns->nodes[0]); } EMIT(return_value); } STATIC void compile_yield_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_node(comp, pns->nodes[0]); EMIT(pop_top); } STATIC void compile_raise_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // raise EMIT_ARG(raise_varargs, 0); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_raise_stmt_arg)) { // raise x from y pns = (mp_parse_node_struct_t *)pns->nodes[0]; compile_node(comp, pns->nodes[0]); compile_node(comp, pns->nodes[1]); EMIT_ARG(raise_varargs, 2); } else { // raise x compile_node(comp, pns->nodes[0]); EMIT_ARG(raise_varargs, 1); } } // q_base holds the base of the name // eg a -> q_base=a // a.b.c -> q_base=a STATIC void do_import_name(compiler_t *comp, mp_parse_node_t pn, qstr *q_base) { bool is_as = false; if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_dotted_as_name)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; // a name of the form x as y; unwrap it *q_base = MP_PARSE_NODE_LEAF_ARG(pns->nodes[1]); pn = pns->nodes[0]; is_as = true; } if (MP_PARSE_NODE_IS_NULL(pn)) { // empty name (eg, from . import x) *q_base = MP_QSTR_; EMIT_ARG(import, MP_QSTR_, MP_EMIT_IMPORT_NAME); // import the empty string } else if (MP_PARSE_NODE_IS_ID(pn)) { // just a simple name qstr q_full = MP_PARSE_NODE_LEAF_ARG(pn); if (!is_as) { *q_base = q_full; } EMIT_ARG(import, q_full, MP_EMIT_IMPORT_NAME); } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_dotted_name)); // should be mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; { // a name of the form a.b.c if (!is_as) { *q_base = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); } int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); int len = n - 1; for (int i = 0; i < n; i++) { len += qstr_len(MP_PARSE_NODE_LEAF_ARG(pns->nodes[i])); } char *q_ptr = mp_local_alloc(len); char *str_dest = q_ptr; for (int i = 0; i < n; i++) { if (i > 0) { *str_dest++ = '.'; } size_t str_src_len; const byte *str_src = qstr_data(MP_PARSE_NODE_LEAF_ARG(pns->nodes[i]), &str_src_len); memcpy(str_dest, str_src, str_src_len); str_dest += str_src_len; } qstr q_full = qstr_from_strn(q_ptr, len); mp_local_free(q_ptr); EMIT_ARG(import, q_full, MP_EMIT_IMPORT_NAME); if (is_as) { for (int i = 1; i < n; i++) { EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns->nodes[i]), MP_EMIT_ATTR_LOAD); } } } } } STATIC void compile_dotted_as_name(compiler_t *comp, mp_parse_node_t pn) { EMIT_ARG(load_const_small_int, 0); // level 0 import EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // not importing from anything qstr q_base; do_import_name(comp, pn, &q_base); compile_store_id(comp, q_base); } STATIC void compile_import_name(compiler_t *comp, mp_parse_node_struct_t *pns) { apply_to_single_or_list(comp, pns->nodes[0], PN_dotted_as_names, compile_dotted_as_name); } STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { mp_parse_node_t pn_import_source = pns->nodes[0]; // extract the preceding .'s (if any) for a relative import, to compute the import level uint import_level = 0; do { mp_parse_node_t pn_rel; if (MP_PARSE_NODE_IS_TOKEN(pn_import_source) || MP_PARSE_NODE_IS_STRUCT_KIND(pn_import_source, PN_one_or_more_period_or_ellipsis)) { // This covers relative imports with dots only like "from .. import" pn_rel = pn_import_source; pn_import_source = MP_PARSE_NODE_NULL; } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn_import_source, PN_import_from_2b)) { // This covers relative imports starting with dot(s) like "from .foo import" mp_parse_node_struct_t *pns_2b = (mp_parse_node_struct_t *)pn_import_source; pn_rel = pns_2b->nodes[0]; pn_import_source = pns_2b->nodes[1]; assert(!MP_PARSE_NODE_IS_NULL(pn_import_source)); // should not be } else { // Not a relative import break; } // get the list of . and/or ...'s mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pn_rel, PN_one_or_more_period_or_ellipsis, &nodes); // count the total number of .'s for (size_t i = 0; i < n; i++) { if (MP_PARSE_NODE_IS_TOKEN_KIND(nodes[i], MP_TOKEN_DEL_PERIOD)) { import_level++; } else { // should be an MP_TOKEN_ELLIPSIS import_level += 3; } } } while (0); if (MP_PARSE_NODE_IS_TOKEN_KIND(pns->nodes[1], MP_TOKEN_OP_STAR)) { #if MICROPY_CPYTHON_COMPAT if (comp->scope_cur->kind != SCOPE_MODULE) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("import * not at module level")); return; } #endif EMIT_ARG(load_const_small_int, import_level); // build the "fromlist" tuple EMIT_ARG(load_const_str, MP_QSTR__star_); EMIT_ARG(build, 1, MP_EMIT_BUILD_TUPLE); // do the import qstr dummy_q; do_import_name(comp, pn_import_source, &dummy_q); EMIT_ARG(import, MP_QSTRnull, MP_EMIT_IMPORT_STAR); } else { EMIT_ARG(load_const_small_int, import_level); // build the "fromlist" tuple mp_parse_node_t *pn_nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[1], PN_import_as_names, &pn_nodes); for (size_t i = 0; i < n; i++) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn_nodes[i], PN_import_as_name)); mp_parse_node_struct_t *pns3 = (mp_parse_node_struct_t *)pn_nodes[i]; qstr id2 = MP_PARSE_NODE_LEAF_ARG(pns3->nodes[0]); // should be id EMIT_ARG(load_const_str, id2); } EMIT_ARG(build, n, MP_EMIT_BUILD_TUPLE); // do the import qstr dummy_q; do_import_name(comp, pn_import_source, &dummy_q); for (size_t i = 0; i < n; i++) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn_nodes[i], PN_import_as_name)); mp_parse_node_struct_t *pns3 = (mp_parse_node_struct_t *)pn_nodes[i]; qstr id2 = MP_PARSE_NODE_LEAF_ARG(pns3->nodes[0]); // should be id EMIT_ARG(import, id2, MP_EMIT_IMPORT_FROM); if (MP_PARSE_NODE_IS_NULL(pns3->nodes[1])) { compile_store_id(comp, id2); } else { compile_store_id(comp, MP_PARSE_NODE_LEAF_ARG(pns3->nodes[1])); } } EMIT(pop_top); } } STATIC void compile_declare_global(compiler_t *comp, mp_parse_node_t pn, id_info_t *id_info) { if (id_info->kind != ID_INFO_KIND_UNDECIDED && id_info->kind != ID_INFO_KIND_GLOBAL_EXPLICIT) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("identifier redefined as global")); return; } id_info->kind = ID_INFO_KIND_GLOBAL_EXPLICIT; // if the id exists in the global scope, set its kind to EXPLICIT_GLOBAL id_info = scope_find_global(comp->scope_cur, id_info->qst); if (id_info != NULL) { id_info->kind = ID_INFO_KIND_GLOBAL_EXPLICIT; } } STATIC void compile_declare_nonlocal(compiler_t *comp, mp_parse_node_t pn, id_info_t *id_info) { if (id_info->kind == ID_INFO_KIND_UNDECIDED) { id_info->kind = ID_INFO_KIND_GLOBAL_IMPLICIT; scope_check_to_close_over(comp->scope_cur, id_info); if (id_info->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("no binding for nonlocal found")); } } else if (id_info->kind != ID_INFO_KIND_FREE) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("identifier redefined as nonlocal")); } } STATIC void compile_global_nonlocal_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { if (comp->pass == MP_PASS_SCOPE) { bool is_global = MP_PARSE_NODE_STRUCT_KIND(pns) == PN_global_stmt; if (!is_global && comp->scope_cur->kind == SCOPE_MODULE) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("can't declare nonlocal in outer code")); return; } mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[0], PN_name_list, &nodes); for (size_t i = 0; i < n; i++) { qstr qst = MP_PARSE_NODE_LEAF_ARG(nodes[i]); id_info_t *id_info = scope_find_or_add_id(comp->scope_cur, qst, ID_INFO_KIND_UNDECIDED); if (is_global) { compile_declare_global(comp, (mp_parse_node_t)pns, id_info); } else { compile_declare_nonlocal(comp, (mp_parse_node_t)pns, id_info); } } } } STATIC void compile_assert_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // with optimisations enabled we don't compile assertions if (MP_STATE_VM(mp_optimise_value) != 0) { return; } uint l_end = comp_next_label(comp); c_if_cond(comp, pns->nodes[0], true, l_end); EMIT_LOAD_GLOBAL(MP_QSTR_AssertionError); // we load_global instead of load_id, to be consistent with CPython if (!MP_PARSE_NODE_IS_NULL(pns->nodes[1])) { // assertion message compile_node(comp, pns->nodes[1]); EMIT_ARG(call_function, 1, 0, 0); } EMIT_ARG(raise_varargs, 1); EMIT_ARG(label_assign, l_end); } STATIC void compile_if_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { uint l_end = comp_next_label(comp); // optimisation: don't emit anything when "if False" if (!mp_parse_node_is_const_false(pns->nodes[0])) { uint l_fail = comp_next_label(comp); c_if_cond(comp, pns->nodes[0], false, l_fail); // if condition compile_node(comp, pns->nodes[1]); // if block // optimisation: skip everything else when "if True" if (mp_parse_node_is_const_true(pns->nodes[0])) { goto done; } if ( // optimisation: don't jump over non-existent elif/else blocks !(MP_PARSE_NODE_IS_NULL(pns->nodes[2]) && MP_PARSE_NODE_IS_NULL(pns->nodes[3])) // optimisation: don't jump if last instruction was return && !EMIT(last_emit_was_return_value) ) { // jump over elif/else blocks EMIT_ARG(jump, l_end); } EMIT_ARG(label_assign, l_fail); } // compile elif blocks (if any) mp_parse_node_t *pn_elif; size_t n_elif = mp_parse_node_extract_list(&pns->nodes[2], PN_if_stmt_elif_list, &pn_elif); for (size_t i = 0; i < n_elif; i++) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn_elif[i], PN_if_stmt_elif)); // should be mp_parse_node_struct_t *pns_elif = (mp_parse_node_struct_t *)pn_elif[i]; // optimisation: don't emit anything when "if False" if (!mp_parse_node_is_const_false(pns_elif->nodes[0])) { uint l_fail = comp_next_label(comp); c_if_cond(comp, pns_elif->nodes[0], false, l_fail); // elif condition compile_node(comp, pns_elif->nodes[1]); // elif block // optimisation: skip everything else when "elif True" if (mp_parse_node_is_const_true(pns_elif->nodes[0])) { goto done; } // optimisation: don't jump if last instruction was return if (!EMIT(last_emit_was_return_value)) { EMIT_ARG(jump, l_end); } EMIT_ARG(label_assign, l_fail); } } // compile else block compile_node(comp, pns->nodes[3]); // can be null done: EMIT_ARG(label_assign, l_end); } #define START_BREAK_CONTINUE_BLOCK \ uint16_t old_break_label = comp->break_label; \ uint16_t old_continue_label = comp->continue_label; \ uint16_t old_break_continue_except_level = comp->break_continue_except_level; \ uint break_label = comp_next_label(comp); \ uint continue_label = comp_next_label(comp); \ comp->break_label = break_label; \ comp->continue_label = continue_label; \ comp->break_continue_except_level = comp->cur_except_level; #define END_BREAK_CONTINUE_BLOCK \ comp->break_label = old_break_label; \ comp->continue_label = old_continue_label; \ comp->break_continue_except_level = old_break_continue_except_level; STATIC void compile_while_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { START_BREAK_CONTINUE_BLOCK if (!mp_parse_node_is_const_false(pns->nodes[0])) { // optimisation: don't emit anything for "while False" uint top_label = comp_next_label(comp); if (!mp_parse_node_is_const_true(pns->nodes[0])) { // optimisation: don't jump to cond for "while True" EMIT_ARG(jump, continue_label); } EMIT_ARG(label_assign, top_label); compile_node(comp, pns->nodes[1]); // body EMIT_ARG(label_assign, continue_label); c_if_cond(comp, pns->nodes[0], true, top_label); // condition } // break/continue apply to outer loop (if any) in the else block END_BREAK_CONTINUE_BLOCK compile_node(comp, pns->nodes[2]); // else EMIT_ARG(label_assign, break_label); } // This function compiles an optimised for-loop of the form: // for in range(, , ): // // else: // // must be an identifier and must be a small-int. // // Semantics of for-loop require: // - final failing value should not be stored in the loop variable // - if the loop never runs, the loop variable should never be assigned // - assignments to , or in the body do not alter the loop // ( is a constant for us, so no need to worry about it changing) // // If is a small-int, then the stack during the for-loop contains just // the current value of . Otherwise, the stack contains then the // current value of . STATIC void compile_for_stmt_optimised_range(compiler_t *comp, mp_parse_node_t pn_var, mp_parse_node_t pn_start, mp_parse_node_t pn_end, mp_parse_node_t pn_step, mp_parse_node_t pn_body, mp_parse_node_t pn_else) { START_BREAK_CONTINUE_BLOCK uint top_label = comp_next_label(comp); uint entry_label = comp_next_label(comp); // put the end value on the stack if it's not a small-int constant bool end_on_stack = !MP_PARSE_NODE_IS_SMALL_INT(pn_end); if (end_on_stack) { compile_node(comp, pn_end); } // compile: start compile_node(comp, pn_start); EMIT_ARG(jump, entry_label); EMIT_ARG(label_assign, top_label); // duplicate next value and store it to var EMIT(dup_top); c_assign(comp, pn_var, ASSIGN_STORE); // compile body compile_node(comp, pn_body); EMIT_ARG(label_assign, continue_label); // compile: var + step compile_node(comp, pn_step); EMIT_ARG(binary_op, MP_BINARY_OP_INPLACE_ADD); EMIT_ARG(label_assign, entry_label); // compile: if var end: goto top if (end_on_stack) { EMIT(dup_top_two); EMIT(rot_two); } else { EMIT(dup_top); compile_node(comp, pn_end); } assert(MP_PARSE_NODE_IS_SMALL_INT(pn_step)); if (MP_PARSE_NODE_LEAF_SMALL_INT(pn_step) >= 0) { EMIT_ARG(binary_op, MP_BINARY_OP_LESS); } else { EMIT_ARG(binary_op, MP_BINARY_OP_MORE); } EMIT_ARG(pop_jump_if, true, top_label); // break/continue apply to outer loop (if any) in the else block END_BREAK_CONTINUE_BLOCK // Compile the else block. We must pop the iterator variables before // executing the else code because it may contain break/continue statements. uint end_label = 0; if (!MP_PARSE_NODE_IS_NULL(pn_else)) { // discard final value of "var", and possible "end" value EMIT(pop_top); if (end_on_stack) { EMIT(pop_top); } compile_node(comp, pn_else); end_label = comp_next_label(comp); EMIT_ARG(jump, end_label); EMIT_ARG(adjust_stack_size, 1 + end_on_stack); } EMIT_ARG(label_assign, break_label); // discard final value of var that failed the loop condition EMIT(pop_top); // discard value if it's on the stack if (end_on_stack) { EMIT(pop_top); } if (!MP_PARSE_NODE_IS_NULL(pn_else)) { EMIT_ARG(label_assign, end_label); } } STATIC void compile_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // this bit optimises: for in range(...), turning it into an explicitly incremented variable // this is actually slower, but uses no heap memory // for viper it will be much, much faster if (/*comp->scope_cur->emit_options == MP_EMIT_OPT_VIPER &&*/ MP_PARSE_NODE_IS_ID(pns->nodes[0]) && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_atom_expr_normal)) { mp_parse_node_struct_t *pns_it = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_IS_ID(pns_it->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(pns_it->nodes[0]) == MP_QSTR_range && MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pns_it->nodes[1]) == PN_trailer_paren) { mp_parse_node_t pn_range_args = ((mp_parse_node_struct_t *)pns_it->nodes[1])->nodes[0]; mp_parse_node_t *args; size_t n_args = mp_parse_node_extract_list(&pn_range_args, PN_arglist, &args); mp_parse_node_t pn_range_start; mp_parse_node_t pn_range_end; mp_parse_node_t pn_range_step; bool optimize = false; if (1 <= n_args && n_args <= 3) { optimize = true; if (n_args == 1) { pn_range_start = mp_parse_node_new_small_int(0); pn_range_end = args[0]; pn_range_step = mp_parse_node_new_small_int(1); } else if (n_args == 2) { pn_range_start = args[0]; pn_range_end = args[1]; pn_range_step = mp_parse_node_new_small_int(1); } else { pn_range_start = args[0]; pn_range_end = args[1]; pn_range_step = args[2]; // the step must be a non-zero constant integer to do the optimisation if (!MP_PARSE_NODE_IS_SMALL_INT(pn_range_step) || MP_PARSE_NODE_LEAF_SMALL_INT(pn_range_step) == 0) { optimize = false; } } // arguments must be able to be compiled as standard expressions if (optimize && MP_PARSE_NODE_IS_STRUCT(pn_range_start)) { int k = MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pn_range_start); if (k == PN_arglist_star || k == PN_arglist_dbl_star || k == PN_argument) { optimize = false; } } if (optimize && MP_PARSE_NODE_IS_STRUCT(pn_range_end)) { int k = MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pn_range_end); if (k == PN_arglist_star || k == PN_arglist_dbl_star || k == PN_argument) { optimize = false; } } } if (optimize) { compile_for_stmt_optimised_range(comp, pns->nodes[0], pn_range_start, pn_range_end, pn_range_step, pns->nodes[2], pns->nodes[3]); return; } } } START_BREAK_CONTINUE_BLOCK comp->break_label |= MP_EMIT_BREAK_FROM_FOR; uint pop_label = comp_next_label(comp); compile_node(comp, pns->nodes[1]); // iterator EMIT_ARG(get_iter, true); EMIT_ARG(label_assign, continue_label); EMIT_ARG(for_iter, pop_label); c_assign(comp, pns->nodes[0], ASSIGN_STORE); // variable compile_node(comp, pns->nodes[2]); // body if (!EMIT(last_emit_was_return_value)) { EMIT_ARG(jump, continue_label); } EMIT_ARG(label_assign, pop_label); EMIT(for_iter_end); // break/continue apply to outer loop (if any) in the else block END_BREAK_CONTINUE_BLOCK compile_node(comp, pns->nodes[3]); // else (may be empty) EMIT_ARG(label_assign, break_label); } STATIC void compile_try_except(compiler_t *comp, mp_parse_node_t pn_body, int n_except, mp_parse_node_t *pn_excepts, mp_parse_node_t pn_else) { // setup code uint l1 = comp_next_label(comp); uint success_label = comp_next_label(comp); compile_increase_except_level(comp, l1, MP_EMIT_SETUP_BLOCK_EXCEPT); compile_node(comp, pn_body); // body EMIT_ARG(pop_except_jump, success_label, false); // jump over exception handler EMIT_ARG(label_assign, l1); // start of exception handler EMIT(start_except_handler); // at this point the top of the stack contains the exception instance that was raised uint l2 = comp_next_label(comp); for (int i = 0; i < n_except; i++) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn_excepts[i], PN_try_stmt_except)); // should be mp_parse_node_struct_t *pns_except = (mp_parse_node_struct_t *)pn_excepts[i]; qstr qstr_exception_local = 0; uint end_finally_label = comp_next_label(comp); #if MICROPY_PY_SYS_SETTRACE EMIT_ARG(set_source_line, pns_except->source_line); #endif if (MP_PARSE_NODE_IS_NULL(pns_except->nodes[0])) { // this is a catch all exception handler if (i + 1 != n_except) { compile_syntax_error(comp, pn_excepts[i], MP_ERROR_TEXT("default 'except' must be last")); compile_decrease_except_level(comp); return; } } else { // this exception handler requires a match to a certain type of exception mp_parse_node_t pns_exception_expr = pns_except->nodes[0]; if (MP_PARSE_NODE_IS_STRUCT(pns_exception_expr)) { mp_parse_node_struct_t *pns3 = (mp_parse_node_struct_t *)pns_exception_expr; if (MP_PARSE_NODE_STRUCT_KIND(pns3) == PN_try_stmt_as_name) { // handler binds the exception to a local pns_exception_expr = pns3->nodes[0]; qstr_exception_local = MP_PARSE_NODE_LEAF_ARG(pns3->nodes[1]); } } EMIT(dup_top); compile_node(comp, pns_exception_expr); EMIT_ARG(binary_op, MP_BINARY_OP_EXCEPTION_MATCH); EMIT_ARG(pop_jump_if, false, end_finally_label); } // either discard or store the exception instance if (qstr_exception_local == 0) { EMIT(pop_top); } else { compile_store_id(comp, qstr_exception_local); } // If the exception is bound to a variable then the of the // exception handler is wrapped in a try-finally so that the name can // be deleted (per Python semantics) even if the has an exception. // In such a case the generated code for the exception handler is: // try: // // finally: // = None // del uint l3 = 0; if (qstr_exception_local != 0) { l3 = comp_next_label(comp); compile_increase_except_level(comp, l3, MP_EMIT_SETUP_BLOCK_FINALLY); } compile_node(comp, pns_except->nodes[1]); // the if (qstr_exception_local != 0) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(label_assign, l3); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); compile_store_id(comp, qstr_exception_local); compile_delete_id(comp, qstr_exception_local); compile_decrease_except_level(comp); } EMIT_ARG(pop_except_jump, l2, true); EMIT_ARG(label_assign, end_finally_label); EMIT_ARG(adjust_stack_size, 1); // stack adjust for the exception instance } compile_decrease_except_level(comp); EMIT(end_except_handler); EMIT_ARG(label_assign, success_label); compile_node(comp, pn_else); // else block, can be null EMIT_ARG(label_assign, l2); } STATIC void compile_try_finally(compiler_t *comp, mp_parse_node_t pn_body, int n_except, mp_parse_node_t *pn_except, mp_parse_node_t pn_else, mp_parse_node_t pn_finally) { uint l_finally_block = comp_next_label(comp); compile_increase_except_level(comp, l_finally_block, MP_EMIT_SETUP_BLOCK_FINALLY); if (n_except == 0) { assert(MP_PARSE_NODE_IS_NULL(pn_else)); EMIT_ARG(adjust_stack_size, 3); // stack adjust for possible UNWIND_JUMP state compile_node(comp, pn_body); EMIT_ARG(adjust_stack_size, -3); } else { compile_try_except(comp, pn_body, n_except, pn_except, pn_else); } EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(label_assign, l_finally_block); compile_node(comp, pn_finally); compile_decrease_except_level(comp); } STATIC void compile_try_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])); // should be { mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_try_stmt_finally) { // just try-finally compile_try_finally(comp, pns->nodes[0], 0, NULL, MP_PARSE_NODE_NULL, pns2->nodes[0]); } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_try_stmt_except_and_more) { // try-except and possibly else and/or finally mp_parse_node_t *pn_excepts; size_t n_except = mp_parse_node_extract_list(&pns2->nodes[0], PN_try_stmt_except_list, &pn_excepts); if (MP_PARSE_NODE_IS_NULL(pns2->nodes[2])) { // no finally compile_try_except(comp, pns->nodes[0], n_except, pn_excepts, pns2->nodes[1]); } else { // have finally compile_try_finally(comp, pns->nodes[0], n_except, pn_excepts, pns2->nodes[1], ((mp_parse_node_struct_t *)pns2->nodes[2])->nodes[0]); } } else { // just try-except mp_parse_node_t *pn_excepts; size_t n_except = mp_parse_node_extract_list(&pns->nodes[1], PN_try_stmt_except_list, &pn_excepts); compile_try_except(comp, pns->nodes[0], n_except, pn_excepts, MP_PARSE_NODE_NULL); } } } STATIC void compile_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_node_t *nodes, mp_parse_node_t body) { if (n == 0) { // no more pre-bits, compile the body of the with compile_node(comp, body); } else { uint l_end = comp_next_label(comp); if (MP_PARSE_NODE_IS_STRUCT_KIND(nodes[0], PN_with_item)) { // this pre-bit is of the form "a as b" mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)nodes[0]; compile_node(comp, pns->nodes[0]); compile_increase_except_level(comp, l_end, MP_EMIT_SETUP_BLOCK_WITH); c_assign(comp, pns->nodes[1], ASSIGN_STORE); } else { // this pre-bit is just an expression compile_node(comp, nodes[0]); compile_increase_except_level(comp, l_end, MP_EMIT_SETUP_BLOCK_WITH); EMIT(pop_top); } // compile additional pre-bits and the body compile_with_stmt_helper(comp, n - 1, nodes + 1, body); // finish this with block EMIT_ARG(with_cleanup, l_end); reserve_labels_for_native(comp, 3); // used by native's with_cleanup compile_decrease_except_level(comp); } } STATIC void compile_with_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // get the nodes for the pre-bit of the with (the a as b, c as d, ... bit) mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[0], PN_with_stmt_list, &nodes); assert(n > 0); // compile in a nested fashion compile_with_stmt_helper(comp, n, nodes, pns->nodes[1]); } STATIC void compile_yield_from(compiler_t *comp) { EMIT_ARG(get_iter, false); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(yield, MP_EMIT_YIELD_FROM); reserve_labels_for_native(comp, 3); } #if MICROPY_PY_ASYNC_AWAIT STATIC void compile_await_object_method(compiler_t *comp, qstr method) { EMIT_ARG(load_method, method, false); EMIT_ARG(call_method, 0, 0, 0); compile_yield_from(comp); } STATIC void compile_async_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // comp->break_label |= MP_EMIT_BREAK_FROM_FOR; qstr context = MP_PARSE_NODE_LEAF_ARG(pns->nodes[1]); uint while_else_label = comp_next_label(comp); uint try_exception_label = comp_next_label(comp); uint try_else_label = comp_next_label(comp); uint try_finally_label = comp_next_label(comp); compile_node(comp, pns->nodes[1]); // iterator EMIT_ARG(load_method, MP_QSTR___aiter__, false); EMIT_ARG(call_method, 0, 0, 0); compile_store_id(comp, context); START_BREAK_CONTINUE_BLOCK EMIT_ARG(label_assign, continue_label); compile_increase_except_level(comp, try_exception_label, MP_EMIT_SETUP_BLOCK_EXCEPT); compile_load_id(comp, context); compile_await_object_method(comp, MP_QSTR___anext__); c_assign(comp, pns->nodes[0], ASSIGN_STORE); // variable EMIT_ARG(pop_except_jump, try_else_label, false); EMIT_ARG(label_assign, try_exception_label); EMIT(start_except_handler); EMIT(dup_top); EMIT_LOAD_GLOBAL(MP_QSTR_StopAsyncIteration); EMIT_ARG(binary_op, MP_BINARY_OP_EXCEPTION_MATCH); EMIT_ARG(pop_jump_if, false, try_finally_label); EMIT(pop_top); // pop exception instance EMIT_ARG(pop_except_jump, while_else_label, true); EMIT_ARG(label_assign, try_finally_label); EMIT_ARG(adjust_stack_size, 1); // if we jump here, the exc is on the stack compile_decrease_except_level(comp); EMIT(end_except_handler); EMIT_ARG(label_assign, try_else_label); compile_node(comp, pns->nodes[2]); // body EMIT_ARG(jump, continue_label); // break/continue apply to outer loop (if any) in the else block END_BREAK_CONTINUE_BLOCK EMIT_ARG(label_assign, while_else_label); compile_node(comp, pns->nodes[3]); // else EMIT_ARG(label_assign, break_label); } STATIC void compile_async_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_node_t *nodes, mp_parse_node_t body) { if (n == 0) { // no more pre-bits, compile the body of the with compile_node(comp, body); } else { uint l_finally_block = comp_next_label(comp); uint l_aexit_no_exc = comp_next_label(comp); uint l_ret_unwind_jump = comp_next_label(comp); uint l_end = comp_next_label(comp); if (MP_PARSE_NODE_IS_STRUCT_KIND(nodes[0], PN_with_item)) { // this pre-bit is of the form "a as b" mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)nodes[0]; compile_node(comp, pns->nodes[0]); EMIT(dup_top); compile_await_object_method(comp, MP_QSTR___aenter__); c_assign(comp, pns->nodes[1], ASSIGN_STORE); } else { // this pre-bit is just an expression compile_node(comp, nodes[0]); EMIT(dup_top); compile_await_object_method(comp, MP_QSTR___aenter__); EMIT(pop_top); } // To keep the Python stack size down, and because we can't access values on // this stack further down than 3 elements (via rot_three), we don't preload // __aexit__ (as per normal with) but rather wait until we need it below. // Start the try-finally statement compile_increase_except_level(comp, l_finally_block, MP_EMIT_SETUP_BLOCK_FINALLY); // Compile any additional pre-bits of the "async with", and also the body EMIT_ARG(adjust_stack_size, 3); // stack adjust for possible UNWIND_JUMP state compile_async_with_stmt_helper(comp, n - 1, nodes + 1, body); EMIT_ARG(adjust_stack_size, -3); // We have now finished the "try" block and fall through to the "finally" // At this point, after the with body has executed, we have 3 cases: // 1. no exception, we just fall through to this point; stack: (..., ctx_mgr) // 2. exception propagating out, we get to the finally block; stack: (..., ctx_mgr, exc) // 3. return or unwind jump, we get to the finally block; stack: (..., ctx_mgr, X, INT) // Handle case 1: call __aexit__ // Stack: (..., ctx_mgr) EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // to tell end_finally there's no exception EMIT(rot_two); EMIT_ARG(jump, l_aexit_no_exc); // jump to code below to call __aexit__ // Start of "finally" block // At this point we have case 2 or 3, we detect which one by the TOS being an exception or not EMIT_ARG(label_assign, l_finally_block); // Detect if TOS an exception or not EMIT(dup_top); EMIT_LOAD_GLOBAL(MP_QSTR_BaseException); EMIT_ARG(binary_op, MP_BINARY_OP_EXCEPTION_MATCH); EMIT_ARG(pop_jump_if, false, l_ret_unwind_jump); // if not an exception then we have case 3 // Handle case 2: call __aexit__ and either swallow or re-raise the exception // Stack: (..., ctx_mgr, exc) EMIT(dup_top); EMIT(rot_three); EMIT(rot_two); EMIT_ARG(load_method, MP_QSTR___aexit__, false); EMIT(rot_three); EMIT(rot_three); EMIT(dup_top); #if MICROPY_CPYTHON_COMPAT EMIT_ARG(attr, MP_QSTR___class__, MP_EMIT_ATTR_LOAD); // get type(exc) #else compile_load_id(comp, MP_QSTR_type); EMIT(rot_two); EMIT_ARG(call_function, 1, 0, 0); // get type(exc) #endif EMIT(rot_two); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // dummy traceback value // Stack: (..., exc, __aexit__, ctx_mgr, type(exc), exc, None) EMIT_ARG(call_method, 3, 0, 0); compile_yield_from(comp); EMIT_ARG(pop_jump_if, false, l_end); EMIT(pop_top); // pop exception EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // replace with None to swallow exception EMIT_ARG(jump, l_end); EMIT_ARG(adjust_stack_size, 2); // Handle case 3: call __aexit__ // Stack: (..., ctx_mgr, X, INT) EMIT_ARG(label_assign, l_ret_unwind_jump); EMIT(rot_three); EMIT(rot_three); EMIT_ARG(label_assign, l_aexit_no_exc); EMIT_ARG(load_method, MP_QSTR___aexit__, false); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT(dup_top); EMIT(dup_top); EMIT_ARG(call_method, 3, 0, 0); compile_yield_from(comp); EMIT(pop_top); EMIT_ARG(adjust_stack_size, -1); // End of "finally" block // Stack can have one of three configurations: // a. (..., None) - from either case 1, or case 2 with swallowed exception // b. (..., exc) - from case 2 with re-raised exception // c. (..., X, INT) - from case 3 EMIT_ARG(label_assign, l_end); compile_decrease_except_level(comp); } } STATIC void compile_async_with_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // get the nodes for the pre-bit of the with (the a as b, c as d, ... bit) mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[0], PN_with_stmt_list, &nodes); assert(n > 0); // compile in a nested fashion compile_async_with_stmt_helper(comp, n, nodes, pns->nodes[1]); } STATIC void compile_async_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[0])); mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns->nodes[0]; if (MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_funcdef) { // async def compile_funcdef(comp, pns0); scope_t *fscope = (scope_t *)pns0->nodes[4]; fscope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; } else { // async for/with; first verify the scope is a generator int scope_flags = comp->scope_cur->scope_flags; if (!(scope_flags & MP_SCOPE_FLAG_GENERATOR)) { compile_syntax_error(comp, (mp_parse_node_t)pns0, MP_ERROR_TEXT("async for/with outside async function")); return; } if (MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_for_stmt) { // async for compile_async_for_stmt(comp, pns0); } else { // async with assert(MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_with_stmt); compile_async_with_stmt(comp, pns0); } } } #endif STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { mp_parse_node_t pn_rhs = pns->nodes[1]; if (MP_PARSE_NODE_IS_NULL(pn_rhs)) { if (comp->is_repl && comp->scope_cur->kind == SCOPE_MODULE) { // for REPL, evaluate then print the expression compile_load_id(comp, MP_QSTR___repl_print__); compile_node(comp, pns->nodes[0]); EMIT_ARG(call_function, 1, 0, 0); EMIT(pop_top); } else { // for non-REPL, evaluate then discard the expression if ((MP_PARSE_NODE_IS_LEAF(pns->nodes[0]) && !MP_PARSE_NODE_IS_ID(pns->nodes[0])) || MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_const_object)) { // do nothing with a lonely constant } else { compile_node(comp, pns->nodes[0]); // just an expression EMIT(pop_top); // discard last result since this is a statement and leaves nothing on the stack } } } else if (MP_PARSE_NODE_IS_STRUCT(pn_rhs)) { mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn_rhs; int kind = MP_PARSE_NODE_STRUCT_KIND(pns1); if (kind == PN_annassign) { // the annotation is in pns1->nodes[0] and is ignored if (MP_PARSE_NODE_IS_NULL(pns1->nodes[1])) { // an annotation of the form "x: y" // inside a function this declares "x" as a local if (comp->scope_cur->kind == SCOPE_FUNCTION) { if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { qstr lhs = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); scope_find_or_add_id(comp->scope_cur, lhs, ID_INFO_KIND_LOCAL); } } } else { // an assigned annotation of the form "x: y = z" pn_rhs = pns1->nodes[1]; goto plain_assign; } } else if (kind == PN_expr_stmt_augassign) { c_assign(comp, pns->nodes[0], ASSIGN_AUG_LOAD); // lhs load for aug assign compile_node(comp, pns1->nodes[1]); // rhs assert(MP_PARSE_NODE_IS_TOKEN(pns1->nodes[0])); mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]); mp_binary_op_t op = MP_BINARY_OP_INPLACE_OR + (tok - MP_TOKEN_DEL_PIPE_EQUAL); EMIT_ARG(binary_op, op); c_assign(comp, pns->nodes[0], ASSIGN_AUG_STORE); // lhs store for aug assign } else if (kind == PN_expr_stmt_assign_list) { int rhs = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) - 1; compile_node(comp, pns1->nodes[rhs]); // rhs // following CPython, we store left-most first if (rhs > 0) { EMIT(dup_top); } c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store for (int i = 0; i < rhs; i++) { if (i + 1 < rhs) { EMIT(dup_top); } c_assign(comp, pns1->nodes[i], ASSIGN_STORE); // middle store } } else { plain_assign: #if MICROPY_COMP_DOUBLE_TUPLE_ASSIGN if (MP_PARSE_NODE_IS_STRUCT_KIND(pn_rhs, PN_testlist_star_expr) && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_star_expr)) { mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns->nodes[0]; pns1 = (mp_parse_node_struct_t *)pn_rhs; uint32_t n_pns0 = MP_PARSE_NODE_STRUCT_NUM_NODES(pns0); // Can only optimise a tuple-to-tuple assignment when all of the following hold: // - equal number of items in LHS and RHS tuples // - 2 or 3 items in the tuples // - there are no star expressions in the LHS tuple if (n_pns0 == MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) && (n_pns0 == 2 #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN || n_pns0 == 3 #endif ) && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[0], PN_star_expr) && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[1], PN_star_expr) #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN && (n_pns0 == 2 || !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[2], PN_star_expr)) #endif ) { // Optimisation for a, b = c, d or a, b, c = d, e, f compile_node(comp, pns1->nodes[0]); // rhs compile_node(comp, pns1->nodes[1]); // rhs #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN if (n_pns0 == 3) { compile_node(comp, pns1->nodes[2]); // rhs EMIT(rot_three); } #endif EMIT(rot_two); c_assign(comp, pns0->nodes[0], ASSIGN_STORE); // lhs store c_assign(comp, pns0->nodes[1], ASSIGN_STORE); // lhs store #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN if (n_pns0 == 3) { c_assign(comp, pns0->nodes[2], ASSIGN_STORE); // lhs store } #endif return; } } #endif compile_node(comp, pn_rhs); // rhs c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store } } else { goto plain_assign; } } STATIC void compile_test_if_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_test_if_else)); mp_parse_node_struct_t *pns_test_if_else = (mp_parse_node_struct_t *)pns->nodes[1]; uint l_fail = comp_next_label(comp); uint l_end = comp_next_label(comp); c_if_cond(comp, pns_test_if_else->nodes[0], false, l_fail); // condition compile_node(comp, pns->nodes[0]); // success value EMIT_ARG(jump, l_end); EMIT_ARG(label_assign, l_fail); EMIT_ARG(adjust_stack_size, -1); // adjust stack size compile_node(comp, pns_test_if_else->nodes[1]); // failure value EMIT_ARG(label_assign, l_end); } STATIC void compile_lambdef(compiler_t *comp, mp_parse_node_struct_t *pns) { if (comp->pass == MP_PASS_SCOPE) { // create a new scope for this lambda scope_t *s = scope_new_and_link(comp, SCOPE_LAMBDA, (mp_parse_node_t)pns, comp->scope_cur->emit_options); // store the lambda scope so the compiling function (this one) can use it at each pass pns->nodes[2] = (mp_parse_node_t)s; } // get the scope for this lambda scope_t *this_scope = (scope_t *)pns->nodes[2]; // compile the lambda definition compile_funcdef_lambdef(comp, this_scope, pns->nodes[0], PN_varargslist); } #if MICROPY_PY_ASSIGN_EXPR STATIC void compile_namedexpr_helper(compiler_t *comp, mp_parse_node_t pn_name, mp_parse_node_t pn_expr) { if (!MP_PARSE_NODE_IS_ID(pn_name)) { compile_syntax_error(comp, (mp_parse_node_t)pn_name, MP_ERROR_TEXT("can't assign to expression")); return; // because pn_name is not a valid qstr (in compile_store_id below) } compile_node(comp, pn_expr); EMIT(dup_top); scope_t *old_scope = comp->scope_cur; if (SCOPE_IS_COMP_LIKE(comp->scope_cur->kind)) { // Use parent's scope for assigned value so it can "escape" comp->scope_cur = comp->scope_cur->parent; } compile_store_id(comp, MP_PARSE_NODE_LEAF_ARG(pn_name)); comp->scope_cur = old_scope; } STATIC void compile_namedexpr(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_namedexpr_helper(comp, pns->nodes[0], pns->nodes[1]); } #endif STATIC void compile_or_and_test(compiler_t *comp, mp_parse_node_struct_t *pns) { bool cond = MP_PARSE_NODE_STRUCT_KIND(pns) == PN_or_test; uint l_end = comp_next_label(comp); int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (int i = 0; i < n; i += 1) { compile_node(comp, pns->nodes[i]); if (i + 1 < n) { EMIT_ARG(jump_if_or_pop, cond, l_end); } } EMIT_ARG(label_assign, l_end); } STATIC void compile_not_test_2(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_node(comp, pns->nodes[0]); EMIT_ARG(unary_op, MP_UNARY_OP_NOT); } STATIC void compile_comparison(compiler_t *comp, mp_parse_node_struct_t *pns) { int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); compile_node(comp, pns->nodes[0]); bool multi = (num_nodes > 3); uint l_fail = 0; if (multi) { l_fail = comp_next_label(comp); } for (int i = 1; i + 1 < num_nodes; i += 2) { compile_node(comp, pns->nodes[i + 1]); if (i + 2 < num_nodes) { EMIT(dup_top); EMIT(rot_three); } if (MP_PARSE_NODE_IS_TOKEN(pns->nodes[i])) { mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(pns->nodes[i]); mp_binary_op_t op; if (tok == MP_TOKEN_KW_IN) { op = MP_BINARY_OP_IN; } else { op = MP_BINARY_OP_LESS + (tok - MP_TOKEN_OP_LESS); } EMIT_ARG(binary_op, op); } else { assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[i])); // should be mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[i]; int kind = MP_PARSE_NODE_STRUCT_KIND(pns2); if (kind == PN_comp_op_not_in) { EMIT_ARG(binary_op, MP_BINARY_OP_NOT_IN); } else { assert(kind == PN_comp_op_is); // should be if (MP_PARSE_NODE_IS_NULL(pns2->nodes[0])) { EMIT_ARG(binary_op, MP_BINARY_OP_IS); } else { EMIT_ARG(binary_op, MP_BINARY_OP_IS_NOT); } } } if (i + 2 < num_nodes) { EMIT_ARG(jump_if_or_pop, false, l_fail); } } if (multi) { uint l_end = comp_next_label(comp); EMIT_ARG(jump, l_end); EMIT_ARG(label_assign, l_fail); EMIT_ARG(adjust_stack_size, 1); EMIT(rot_two); EMIT(pop_top); EMIT_ARG(label_assign, l_end); } } STATIC void compile_star_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("*x must be assignment target")); } STATIC void compile_binary_op(compiler_t *comp, mp_parse_node_struct_t *pns) { MP_STATIC_ASSERT(MP_BINARY_OP_OR + PN_xor_expr - PN_expr == MP_BINARY_OP_XOR); MP_STATIC_ASSERT(MP_BINARY_OP_OR + PN_and_expr - PN_expr == MP_BINARY_OP_AND); mp_binary_op_t binary_op = MP_BINARY_OP_OR + MP_PARSE_NODE_STRUCT_KIND(pns) - PN_expr; int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); compile_node(comp, pns->nodes[0]); for (int i = 1; i < num_nodes; ++i) { compile_node(comp, pns->nodes[i]); EMIT_ARG(binary_op, binary_op); } } STATIC void compile_term(compiler_t *comp, mp_parse_node_struct_t *pns) { int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); compile_node(comp, pns->nodes[0]); for (int i = 1; i + 1 < num_nodes; i += 2) { compile_node(comp, pns->nodes[i + 1]); mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(pns->nodes[i]); mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS); EMIT_ARG(binary_op, op); } } STATIC void compile_factor_2(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_node(comp, pns->nodes[1]); mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); mp_unary_op_t op; if (tok == MP_TOKEN_OP_TILDE) { op = MP_UNARY_OP_INVERT; } else { assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); op = MP_UNARY_OP_POSITIVE + (tok - MP_TOKEN_OP_PLUS); } EMIT_ARG(unary_op, op); } STATIC void compile_atom_expr_normal(compiler_t *comp, mp_parse_node_struct_t *pns) { // compile the subject of the expression compile_node(comp, pns->nodes[0]); // compile_atom_expr_await may call us with a NULL node if (MP_PARSE_NODE_IS_NULL(pns->nodes[1])) { return; } // get the array of trailers (known to be an array of PARSE_NODE_STRUCT) size_t num_trail = 1; mp_parse_node_struct_t **pns_trail = (mp_parse_node_struct_t **)&pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns_trail[0]) == PN_atom_expr_trailers) { num_trail = MP_PARSE_NODE_STRUCT_NUM_NODES(pns_trail[0]); pns_trail = (mp_parse_node_struct_t **)&pns_trail[0]->nodes[0]; } // the current index into the array of trailers size_t i = 0; // handle special super() call if (comp->scope_cur->kind == SCOPE_FUNCTION && MP_PARSE_NODE_IS_ID(pns->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]) == MP_QSTR_super && MP_PARSE_NODE_STRUCT_KIND(pns_trail[0]) == PN_trailer_paren && MP_PARSE_NODE_IS_NULL(pns_trail[0]->nodes[0])) { // at this point we have matched "super()" within a function // load the class for super to search for a parent compile_load_id(comp, MP_QSTR___class__); // look for first argument to function (assumes it's "self") bool found = false; id_info_t *id = &comp->scope_cur->id_info[0]; for (size_t n = comp->scope_cur->id_info_len; n > 0; --n, ++id) { if (id->flags & ID_FLAG_IS_PARAM) { // first argument found; load it compile_load_id(comp, id->qst); found = true; break; } } if (!found) { compile_syntax_error(comp, (mp_parse_node_t)pns_trail[0], MP_ERROR_TEXT("super() can't find self")); // really a TypeError return; } if (num_trail >= 3 && MP_PARSE_NODE_STRUCT_KIND(pns_trail[1]) == PN_trailer_period && MP_PARSE_NODE_STRUCT_KIND(pns_trail[2]) == PN_trailer_paren) { // optimisation for method calls super().f(...), to eliminate heap allocation mp_parse_node_struct_t *pns_period = pns_trail[1]; mp_parse_node_struct_t *pns_paren = pns_trail[2]; EMIT_ARG(load_method, MP_PARSE_NODE_LEAF_ARG(pns_period->nodes[0]), true); compile_trailer_paren_helper(comp, pns_paren->nodes[0], true, 0); i = 3; } else { // a super() call EMIT_ARG(call_function, 2, 0, 0); i = 1; } #if MICROPY_COMP_CONST_LITERAL && MICROPY_PY_COLLECTIONS_ORDEREDDICT // handle special OrderedDict constructor } else if (MP_PARSE_NODE_IS_ID(pns->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]) == MP_QSTR_OrderedDict && MP_PARSE_NODE_STRUCT_KIND(pns_trail[0]) == PN_trailer_paren && MP_PARSE_NODE_IS_STRUCT_KIND(pns_trail[0]->nodes[0], PN_atom_brace)) { // at this point we have matched "OrderedDict({...})" EMIT_ARG(call_function, 0, 0, 0); mp_parse_node_struct_t *pns_dict = (mp_parse_node_struct_t *)pns_trail[0]->nodes[0]; compile_atom_brace_helper(comp, pns_dict, false); i = 1; #endif } // compile the remaining trailers for (; i < num_trail; i++) { if (i + 1 < num_trail && MP_PARSE_NODE_STRUCT_KIND(pns_trail[i]) == PN_trailer_period && MP_PARSE_NODE_STRUCT_KIND(pns_trail[i + 1]) == PN_trailer_paren) { // optimisation for method calls a.f(...), following PyPy mp_parse_node_struct_t *pns_period = pns_trail[i]; mp_parse_node_struct_t *pns_paren = pns_trail[i + 1]; EMIT_ARG(load_method, MP_PARSE_NODE_LEAF_ARG(pns_period->nodes[0]), false); compile_trailer_paren_helper(comp, pns_paren->nodes[0], true, 0); i += 1; } else { // node is one of: trailer_paren, trailer_bracket, trailer_period compile_node(comp, (mp_parse_node_t)pns_trail[i]); } } } STATIC void compile_power(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_generic_all_nodes(comp, pns); // 2 nodes, arguments of power EMIT_ARG(binary_op, MP_BINARY_OP_POWER); } STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_arglist, bool is_method_call, int n_positional_extra) { // function to call is on top of stack // get the list of arguments mp_parse_node_t *args; size_t n_args = mp_parse_node_extract_list(&pn_arglist, PN_arglist, &args); // compile the arguments // Rather than calling compile_node on the list, we go through the list of args // explicitly here so that we can count the number of arguments and give sensible // error messages. int n_positional = n_positional_extra; uint n_keyword = 0; uint star_flags = 0; mp_parse_node_struct_t *star_args_node = NULL, *dblstar_args_node = NULL; for (size_t i = 0; i < n_args; i++) { if (MP_PARSE_NODE_IS_STRUCT(args[i])) { mp_parse_node_struct_t *pns_arg = (mp_parse_node_struct_t *)args[i]; if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_star) { if (star_flags & MP_EMIT_STAR_FLAG_SINGLE) { compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("can't have multiple *x")); return; } star_flags |= MP_EMIT_STAR_FLAG_SINGLE; star_args_node = pns_arg; } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_dbl_star) { if (star_flags & MP_EMIT_STAR_FLAG_DOUBLE) { compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("can't have multiple **x")); return; } star_flags |= MP_EMIT_STAR_FLAG_DOUBLE; dblstar_args_node = pns_arg; } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_argument) { #if MICROPY_PY_ASSIGN_EXPR if (MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_argument_3)) { compile_namedexpr_helper(comp, pns_arg->nodes[0], ((mp_parse_node_struct_t *)pns_arg->nodes[1])->nodes[0]); n_positional++; } else #endif if (!MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_comp_for)) { if (!MP_PARSE_NODE_IS_ID(pns_arg->nodes[0])) { compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("LHS of keyword arg must be an id")); return; } EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pns_arg->nodes[0])); compile_node(comp, pns_arg->nodes[1]); n_keyword += 1; } else { compile_comprehension(comp, pns_arg, SCOPE_GEN_EXPR); n_positional++; } } else { goto normal_argument; } } else { normal_argument: if (star_flags) { compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after */**")); return; } if (n_keyword > 0) { compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after keyword arg")); return; } compile_node(comp, args[i]); n_positional++; } } // compile the star/double-star arguments if we had them // if we had one but not the other then we load "null" as a place holder if (star_flags != 0) { if (star_args_node == NULL) { EMIT(load_null); } else { compile_node(comp, star_args_node->nodes[0]); } if (dblstar_args_node == NULL) { EMIT(load_null); } else { compile_node(comp, dblstar_args_node->nodes[0]); } } // emit the function/method call if (is_method_call) { EMIT_ARG(call_method, n_positional, n_keyword, star_flags); } else { EMIT_ARG(call_function, n_positional, n_keyword, star_flags); } } // pns needs to have 2 nodes, first is lhs of comprehension, second is PN_comp_for node STATIC void compile_comprehension(compiler_t *comp, mp_parse_node_struct_t *pns, scope_kind_t kind) { assert(MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 2); assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_comp_for)); mp_parse_node_struct_t *pns_comp_for = (mp_parse_node_struct_t *)pns->nodes[1]; if (comp->pass == MP_PASS_SCOPE) { // create a new scope for this comprehension scope_t *s = scope_new_and_link(comp, kind, (mp_parse_node_t)pns, comp->scope_cur->emit_options); // store the comprehension scope so the compiling function (this one) can use it at each pass pns_comp_for->nodes[3] = (mp_parse_node_t)s; } // get the scope for this comprehension scope_t *this_scope = (scope_t *)pns_comp_for->nodes[3]; // compile the comprehension close_over_variables_etc(comp, this_scope, 0, 0); compile_node(comp, pns_comp_for->nodes[1]); // source of the iterator if (kind == SCOPE_GEN_EXPR) { EMIT_ARG(get_iter, false); } EMIT_ARG(call_function, 1, 0, 0); } STATIC void compile_atom_paren(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // an empty tuple EMIT_ARG(build, 0, MP_EMIT_BUILD_TUPLE); } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)); pns = (mp_parse_node_struct_t *)pns->nodes[0]; if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { // generator expression compile_comprehension(comp, pns, SCOPE_GEN_EXPR); } else { // tuple with N items compile_generic_tuple(comp, pns); } } } STATIC void compile_atom_bracket(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // empty list EMIT_ARG(build, 0, MP_EMIT_BUILD_LIST); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)) { mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[0]; if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns2)) { // list comprehension compile_comprehension(comp, pns2, SCOPE_LIST_COMP); } else { // list with N items compile_generic_all_nodes(comp, pns2); EMIT_ARG(build, MP_PARSE_NODE_STRUCT_NUM_NODES(pns2), MP_EMIT_BUILD_LIST); } } else { // list with 1 item compile_node(comp, pns->nodes[0]); EMIT_ARG(build, 1, MP_EMIT_BUILD_LIST); } } STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t *pns, bool create_map) { mp_parse_node_t pn = pns->nodes[0]; if (MP_PARSE_NODE_IS_NULL(pn)) { // empty dict if (create_map) { EMIT_ARG(build, 0, MP_EMIT_BUILD_MAP); } } else if (MP_PARSE_NODE_IS_STRUCT(pn)) { pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_dictorsetmaker_item) { // dict with one element if (create_map) { EMIT_ARG(build, 1, MP_EMIT_BUILD_MAP); } compile_node(comp, pn); EMIT(store_map); } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_dictorsetmaker) { assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])); // should succeed mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_dictorsetmaker_list) { // dict/set with multiple elements // get tail elements (2nd, 3rd, ...) mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns1->nodes[0], PN_dictorsetmaker_list2, &nodes); // first element sets whether it's a dict or set bool is_dict; if (!MICROPY_PY_BUILTINS_SET || MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_dictorsetmaker_item)) { // a dictionary if (create_map) { EMIT_ARG(build, 1 + n, MP_EMIT_BUILD_MAP); } compile_node(comp, pns->nodes[0]); EMIT(store_map); is_dict = true; } else { // a set compile_node(comp, pns->nodes[0]); // 1st value of set is_dict = false; } // process rest of elements for (size_t i = 0; i < n; i++) { mp_parse_node_t pn_i = nodes[i]; bool is_key_value = MP_PARSE_NODE_IS_STRUCT_KIND(pn_i, PN_dictorsetmaker_item); compile_node(comp, pn_i); if (is_dict) { if (!is_key_value) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting key:value for dict")); #endif return; } EMIT(store_map); } else { if (is_key_value) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting just a value for set")); #endif return; } } } #if MICROPY_PY_BUILTINS_SET // if it's a set, build it if (!is_dict) { EMIT_ARG(build, 1 + n, MP_EMIT_BUILD_SET); } #endif } else { assert(MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_comp_for); // should be // dict/set comprehension if (!MICROPY_PY_BUILTINS_SET || MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_dictorsetmaker_item)) { // a dictionary comprehension compile_comprehension(comp, pns, SCOPE_DICT_COMP); } else { // a set comprehension compile_comprehension(comp, pns, SCOPE_SET_COMP); } } } else { // set with one element goto set_with_one_element; } } else { // set with one element set_with_one_element: #if MICROPY_PY_BUILTINS_SET compile_node(comp, pn); EMIT_ARG(build, 1, MP_EMIT_BUILD_SET); #else assert(0); #endif } } STATIC void compile_atom_brace(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_atom_brace_helper(comp, pns, true); } STATIC void compile_trailer_paren(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_trailer_paren_helper(comp, pns->nodes[0], false, 0); } STATIC void compile_trailer_bracket(compiler_t *comp, mp_parse_node_struct_t *pns) { // object who's index we want is on top of stack compile_node(comp, pns->nodes[0]); // the index EMIT_ARG(subscr, MP_EMIT_SUBSCR_LOAD); } STATIC void compile_trailer_period(compiler_t *comp, mp_parse_node_struct_t *pns) { // object who's attribute we want is on top of stack EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]), MP_EMIT_ATTR_LOAD); // attribute to get } #if MICROPY_PY_BUILTINS_SLICE STATIC void compile_subscript(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_subscript_2) { compile_node(comp, pns->nodes[0]); // start of slice assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])); // should always be pns = (mp_parse_node_struct_t *)pns->nodes[1]; } else { // pns is a PN_subscript_3, load None for start of slice EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } assert(MP_PARSE_NODE_STRUCT_KIND(pns) == PN_subscript_3); // should always be mp_parse_node_t pn = pns->nodes[0]; if (MP_PARSE_NODE_IS_NULL(pn)) { // [?:] EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(build, 2, MP_EMIT_BUILD_SLICE); } else if (MP_PARSE_NODE_IS_STRUCT(pn)) { pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_subscript_3c) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); pn = pns->nodes[0]; if (MP_PARSE_NODE_IS_NULL(pn)) { // [?::] EMIT_ARG(build, 2, MP_EMIT_BUILD_SLICE); } else { // [?::x] compile_node(comp, pn); EMIT_ARG(build, 3, MP_EMIT_BUILD_SLICE); } } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_subscript_3d) { compile_node(comp, pns->nodes[0]); assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])); // should always be pns = (mp_parse_node_struct_t *)pns->nodes[1]; assert(MP_PARSE_NODE_STRUCT_KIND(pns) == PN_sliceop); // should always be if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // [?:x:] EMIT_ARG(build, 2, MP_EMIT_BUILD_SLICE); } else { // [?:x:x] compile_node(comp, pns->nodes[0]); EMIT_ARG(build, 3, MP_EMIT_BUILD_SLICE); } } else { // [?:x] compile_node(comp, pn); EMIT_ARG(build, 2, MP_EMIT_BUILD_SLICE); } } else { // [?:x] compile_node(comp, pn); EMIT_ARG(build, 2, MP_EMIT_BUILD_SLICE); } } #endif // MICROPY_PY_BUILTINS_SLICE STATIC void compile_dictorsetmaker_item(compiler_t *comp, mp_parse_node_struct_t *pns) { // if this is called then we are compiling a dict key:value pair compile_node(comp, pns->nodes[1]); // value compile_node(comp, pns->nodes[0]); // key } STATIC void compile_classdef(compiler_t *comp, mp_parse_node_struct_t *pns) { qstr cname = compile_classdef_helper(comp, pns, comp->scope_cur->emit_options); // store class object into class name compile_store_id(comp, cname); } STATIC void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { if (comp->scope_cur->kind != SCOPE_FUNCTION && comp->scope_cur->kind != SCOPE_LAMBDA) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'yield' outside function")); return; } if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(yield, MP_EMIT_YIELD_VALUE); reserve_labels_for_native(comp, 1); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_yield_arg_from)) { pns = (mp_parse_node_struct_t *)pns->nodes[0]; compile_node(comp, pns->nodes[0]); compile_yield_from(comp); } else { compile_node(comp, pns->nodes[0]); EMIT_ARG(yield, MP_EMIT_YIELD_VALUE); reserve_labels_for_native(comp, 1); } } #if MICROPY_PY_ASYNC_AWAIT STATIC void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pns) { if (comp->scope_cur->kind != SCOPE_FUNCTION && comp->scope_cur->kind != SCOPE_LAMBDA) { compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'await' outside function")); return; } compile_atom_expr_normal(comp, pns); compile_yield_from(comp); } #endif STATIC mp_obj_t get_const_object(mp_parse_node_struct_t *pns) { #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // nodes are 32-bit pointers, but need to extract 64-bit object return (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32); #else return (mp_obj_t)pns->nodes[0]; #endif } STATIC void compile_const_object(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT_ARG(load_const_obj, get_const_object(pns)); } typedef void (*compile_function_t)(compiler_t *, mp_parse_node_struct_t *); STATIC const compile_function_t compile_function[] = { // only define rules with a compile function #define c(f) compile_##f #define DEF_RULE(rule, comp, kind, ...) comp, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef c #undef DEF_RULE #undef DEF_RULE_NC compile_const_object, }; STATIC void compile_node(compiler_t *comp, mp_parse_node_t pn) { if (MP_PARSE_NODE_IS_NULL(pn)) { // pass } else if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); EMIT_ARG(load_const_small_int, arg); } else if (MP_PARSE_NODE_IS_LEAF(pn)) { uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: compile_load_id(comp, arg); break; case MP_PARSE_NODE_STRING: EMIT_ARG(load_const_str, arg); break; case MP_PARSE_NODE_TOKEN: default: if (arg == MP_TOKEN_NEWLINE) { // this can occur when file_input lets through a NEWLINE (eg if file starts with a newline) // or when single_input lets through a NEWLINE (user enters a blank line) // do nothing } else { EMIT_ARG(load_const_tok, arg); } break; } } else { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; EMIT_ARG(set_source_line, pns->source_line); assert(MP_PARSE_NODE_STRUCT_KIND(pns) <= PN_const_object); compile_function_t f = compile_function[MP_PARSE_NODE_STRUCT_KIND(pns)]; f(comp, pns); } } #if MICROPY_EMIT_NATIVE STATIC int compile_viper_type_annotation(compiler_t *comp, mp_parse_node_t pn_annotation) { int native_type = MP_NATIVE_TYPE_OBJ; if (MP_PARSE_NODE_IS_NULL(pn_annotation)) { // No annotation, type defaults to object } else if (MP_PARSE_NODE_IS_ID(pn_annotation)) { qstr type_name = MP_PARSE_NODE_LEAF_ARG(pn_annotation); native_type = mp_native_type_from_qstr(type_name); if (native_type < 0) { comp->compile_error = mp_obj_new_exception_msg_varg(&mp_type_ViperTypeError, MP_ERROR_TEXT("unknown type '%q'"), type_name); native_type = 0; } } else { compile_syntax_error(comp, pn_annotation, MP_ERROR_TEXT("annotation must be an identifier")); } return native_type; } #endif STATIC void compile_scope_func_lambda_param(compiler_t *comp, mp_parse_node_t pn, pn_kind_t pn_name, pn_kind_t pn_star, pn_kind_t pn_dbl_star) { (void)pn_dbl_star; // check that **kw is last if ((comp->scope_cur->scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("invalid syntax")); return; } qstr param_name = MP_QSTRnull; uint param_flag = ID_FLAG_IS_PARAM; mp_parse_node_struct_t *pns = NULL; if (MP_PARSE_NODE_IS_ID(pn)) { param_name = MP_PARSE_NODE_LEAF_ARG(pn); if (comp->have_star) { // comes after a star, so counts as a keyword-only parameter comp->scope_cur->num_kwonly_args += 1; } else { // comes before a star, so counts as a positional parameter comp->scope_cur->num_pos_args += 1; } mp_emit_common_use_qstr(&comp->emit_common, param_name); } else { assert(MP_PARSE_NODE_IS_STRUCT(pn)); pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == pn_name) { // named parameter with possible annotation param_name = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); if (comp->have_star) { // comes after a star, so counts as a keyword-only parameter comp->scope_cur->num_kwonly_args += 1; } else { // comes before a star, so counts as a positional parameter comp->scope_cur->num_pos_args += 1; } mp_emit_common_use_qstr(&comp->emit_common, param_name); } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == pn_star) { if (comp->have_star) { // more than one star compile_syntax_error(comp, pn, MP_ERROR_TEXT("invalid syntax")); return; } comp->have_star = true; param_flag = ID_FLAG_IS_PARAM | ID_FLAG_IS_STAR_PARAM; if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // bare star // TODO see http://www.python.org/dev/peps/pep-3102/ // assert(comp->scope_cur->num_dict_params == 0); pns = NULL; } else if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { // named star comp->scope_cur->scope_flags |= MP_SCOPE_FLAG_VARARGS; param_name = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); pns = NULL; } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_tfpdef)); // should be // named star with possible annotation comp->scope_cur->scope_flags |= MP_SCOPE_FLAG_VARARGS; pns = (mp_parse_node_struct_t *)pns->nodes[0]; param_name = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); } } else { // double star with possible annotation assert(MP_PARSE_NODE_STRUCT_KIND(pns) == pn_dbl_star); // should be param_name = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); param_flag = ID_FLAG_IS_PARAM | ID_FLAG_IS_DBL_STAR_PARAM; comp->scope_cur->scope_flags |= MP_SCOPE_FLAG_VARKEYWORDS; } } if (param_name != MP_QSTRnull) { id_info_t *id_info = scope_find_or_add_id(comp->scope_cur, param_name, ID_INFO_KIND_UNDECIDED); if (id_info->kind != ID_INFO_KIND_UNDECIDED) { compile_syntax_error(comp, pn, MP_ERROR_TEXT("argument name reused")); return; } id_info->kind = ID_INFO_KIND_LOCAL; id_info->flags = param_flag; #if MICROPY_EMIT_NATIVE if (comp->scope_cur->emit_options == MP_EMIT_OPT_VIPER && pn_name == PN_typedargslist_name && pns != NULL) { id_info->flags |= compile_viper_type_annotation(comp, pns->nodes[1]) << ID_FLAG_VIPER_TYPE_POS; } #else (void)pns; #endif } } STATIC void compile_scope_func_param(compiler_t *comp, mp_parse_node_t pn) { compile_scope_func_lambda_param(comp, pn, PN_typedargslist_name, PN_typedargslist_star, PN_typedargslist_dbl_star); } STATIC void compile_scope_lambda_param(compiler_t *comp, mp_parse_node_t pn) { compile_scope_func_lambda_param(comp, pn, PN_varargslist_name, PN_varargslist_star, PN_varargslist_dbl_star); } STATIC void compile_scope_comp_iter(compiler_t *comp, mp_parse_node_struct_t *pns_comp_for, mp_parse_node_t pn_inner_expr, int for_depth) { uint l_top = comp_next_label(comp); uint l_end = comp_next_label(comp); EMIT_ARG(label_assign, l_top); EMIT_ARG(for_iter, l_end); c_assign(comp, pns_comp_for->nodes[0], ASSIGN_STORE); mp_parse_node_t pn_iter = pns_comp_for->nodes[2]; tail_recursion: if (MP_PARSE_NODE_IS_NULL(pn_iter)) { // no more nested if/for; compile inner expression compile_node(comp, pn_inner_expr); if (comp->scope_cur->kind == SCOPE_GEN_EXPR) { EMIT_ARG(yield, MP_EMIT_YIELD_VALUE); reserve_labels_for_native(comp, 1); EMIT(pop_top); } else { EMIT_ARG(store_comp, comp->scope_cur->kind, 4 * for_depth + 5); } } else if (MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pn_iter) == PN_comp_if) { // if condition mp_parse_node_struct_t *pns_comp_if = (mp_parse_node_struct_t *)pn_iter; c_if_cond(comp, pns_comp_if->nodes[0], false, l_top); pn_iter = pns_comp_if->nodes[1]; goto tail_recursion; } else { assert(MP_PARSE_NODE_STRUCT_KIND((mp_parse_node_struct_t *)pn_iter) == PN_comp_for); // should be // for loop mp_parse_node_struct_t *pns_comp_for2 = (mp_parse_node_struct_t *)pn_iter; compile_node(comp, pns_comp_for2->nodes[1]); EMIT_ARG(get_iter, true); compile_scope_comp_iter(comp, pns_comp_for2, pn_inner_expr, for_depth + 1); } EMIT_ARG(jump, l_top); EMIT_ARG(label_assign, l_end); EMIT(for_iter_end); } STATIC void check_for_doc_string(compiler_t *comp, mp_parse_node_t pn) { #if MICROPY_ENABLE_DOC_STRING // see http://www.python.org/dev/peps/pep-0257/ // look for the first statement if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_expr_stmt)) { // a statement; fall through } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_file_input_2)) { // file input; find the first non-newline node mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; int num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (int i = 0; i < num_nodes; i++) { pn = pns->nodes[i]; if (!(MP_PARSE_NODE_IS_LEAF(pn) && MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN && MP_PARSE_NODE_LEAF_ARG(pn) == MP_TOKEN_NEWLINE)) { // not a newline, so this is the first statement; finish search break; } } // if we didn't find a non-newline then it's okay to fall through; pn will be a newline and so doc-string test below will fail gracefully } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_suite_block_stmts)) { // a list of statements; get the first one pn = ((mp_parse_node_struct_t *)pn)->nodes[0]; } else { return; } // check the first statement for a doc string if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_expr_stmt)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if ((MP_PARSE_NODE_IS_LEAF(pns->nodes[0]) && MP_PARSE_NODE_LEAF_KIND(pns->nodes[0]) == MP_PARSE_NODE_STRING) || (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_const_object) && mp_obj_is_str(get_const_object((mp_parse_node_struct_t *)pns->nodes[0])))) { // compile the doc string compile_node(comp, pns->nodes[0]); // store the doc string compile_store_id(comp, MP_QSTR___doc__); } } #else (void)comp; (void)pn; #endif } STATIC void compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { comp->pass = pass; comp->scope_cur = scope; comp->next_label = 0; mp_emit_common_start_pass(&comp->emit_common, pass); EMIT_ARG(start_pass, pass, scope); reserve_labels_for_native(comp, 6); // used by native's start_pass if (comp->pass == MP_PASS_SCOPE) { // reset maximum stack sizes in scope // they will be computed in this first pass scope->stack_size = 0; scope->exc_stack_size = 0; #if MICROPY_EMIT_NATIVE if (scope->emit_options == MP_EMIT_OPT_NATIVE_PYTHON || scope->emit_options == MP_EMIT_OPT_VIPER) { // allow native code to perfom basic tasks during the pass scope // note: the first argument passed here is mp_emit_common_t, not the native emitter context NATIVE_EMITTER_TABLE->start_pass((void *)&comp->emit_common, comp->pass, scope); } #endif } // compile if (MP_PARSE_NODE_IS_STRUCT_KIND(scope->pn, PN_eval_input)) { assert(scope->kind == SCOPE_MODULE); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; compile_node(comp, pns->nodes[0]); // compile the expression EMIT(return_value); } else if (scope->kind == SCOPE_MODULE) { if (!comp->is_repl) { check_for_doc_string(comp, scope->pn); } compile_node(comp, scope->pn); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT(return_value); } else if (scope->kind == SCOPE_FUNCTION) { assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; assert(MP_PARSE_NODE_STRUCT_KIND(pns) == PN_funcdef); // work out number of parameters, keywords and default parameters, and add them to the id_info array // must be done before compiling the body so that arguments are numbered first (for LOAD_FAST etc) if (comp->pass == MP_PASS_SCOPE) { comp->have_star = false; apply_to_single_or_list(comp, pns->nodes[1], PN_typedargslist, compile_scope_func_param); #if MICROPY_EMIT_NATIVE if (scope->emit_options == MP_EMIT_OPT_VIPER) { // Compile return type; pns->nodes[2] is return/whole function annotation scope->scope_flags |= compile_viper_type_annotation(comp, pns->nodes[2]) << MP_SCOPE_FLAG_VIPERRET_POS; } #endif // MICROPY_EMIT_NATIVE } compile_node(comp, pns->nodes[3]); // 3 is function body // emit return if it wasn't the last opcode if (!EMIT(last_emit_was_return_value)) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT(return_value); } } else if (scope->kind == SCOPE_LAMBDA) { assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; assert(MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 3); // Set the source line number for the start of the lambda EMIT_ARG(set_source_line, pns->source_line); // work out number of parameters, keywords and default parameters, and add them to the id_info array // must be done before compiling the body so that arguments are numbered first (for LOAD_FAST etc) if (comp->pass == MP_PASS_SCOPE) { comp->have_star = false; apply_to_single_or_list(comp, pns->nodes[0], PN_varargslist, compile_scope_lambda_param); } compile_node(comp, pns->nodes[1]); // 1 is lambda body // if the lambda is a generator, then we return None, not the result of the expression of the lambda if (scope->scope_flags & MP_SCOPE_FLAG_GENERATOR) { EMIT(pop_top); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } EMIT(return_value); } else if (SCOPE_IS_COMP_LIKE(scope->kind)) { // a bit of a hack at the moment assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; assert(MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 2); assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_comp_for)); mp_parse_node_struct_t *pns_comp_for = (mp_parse_node_struct_t *)pns->nodes[1]; // We need a unique name for the comprehension argument (the iterator). // CPython uses .0, but we should be able to use anything that won't // clash with a user defined variable. Best to use an existing qstr, // so we use the blank qstr. qstr qstr_arg = MP_QSTR_; if (comp->pass == MP_PASS_SCOPE) { scope_find_or_add_id(comp->scope_cur, qstr_arg, ID_INFO_KIND_LOCAL); scope->num_pos_args = 1; mp_emit_common_use_qstr(&comp->emit_common, MP_QSTR__star_); } // Set the source line number for the start of the comprehension EMIT_ARG(set_source_line, pns->source_line); if (scope->kind == SCOPE_LIST_COMP) { EMIT_ARG(build, 0, MP_EMIT_BUILD_LIST); } else if (scope->kind == SCOPE_DICT_COMP) { EMIT_ARG(build, 0, MP_EMIT_BUILD_MAP); #if MICROPY_PY_BUILTINS_SET } else if (scope->kind == SCOPE_SET_COMP) { EMIT_ARG(build, 0, MP_EMIT_BUILD_SET); #endif } // There are 4 slots on the stack for the iterator, and the first one is // NULL to indicate that the second one points to the iterator object. if (scope->kind == SCOPE_GEN_EXPR) { MP_STATIC_ASSERT(MP_OBJ_ITER_BUF_NSLOTS == 4); EMIT(load_null); compile_load_id(comp, qstr_arg); EMIT(load_null); EMIT(load_null); } else { compile_load_id(comp, qstr_arg); EMIT_ARG(get_iter, true); } compile_scope_comp_iter(comp, pns_comp_for, pns->nodes[0], 0); if (scope->kind == SCOPE_GEN_EXPR) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } EMIT(return_value); } else { assert(scope->kind == SCOPE_CLASS); assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; assert(MP_PARSE_NODE_STRUCT_KIND(pns) == PN_classdef); if (comp->pass == MP_PASS_SCOPE) { scope_find_or_add_id(scope, MP_QSTR___class__, ID_INFO_KIND_LOCAL); } #if MICROPY_PY_SYS_SETTRACE EMIT_ARG(set_source_line, pns->source_line); #endif compile_load_id(comp, MP_QSTR___name__); compile_store_id(comp, MP_QSTR___module__); EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pns->nodes[0])); // 0 is class name compile_store_id(comp, MP_QSTR___qualname__); check_for_doc_string(comp, pns->nodes[2]); compile_node(comp, pns->nodes[2]); // 2 is class body id_info_t *id = scope_find(scope, MP_QSTR___class__); assert(id != NULL); if (id->kind == ID_INFO_KIND_LOCAL) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } else { EMIT_LOAD_FAST(MP_QSTR___class__, id->local_num); } EMIT(return_value); } EMIT(end_pass); // make sure we match all the exception levels assert(comp->cur_except_level == 0); } #if MICROPY_EMIT_INLINE_ASM // requires 3 passes: SCOPE, CODE_SIZE, EMIT STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind_t pass) { comp->pass = pass; comp->scope_cur = scope; comp->next_label = 0; if (scope->kind != SCOPE_FUNCTION) { compile_syntax_error(comp, MP_PARSE_NODE_NULL, MP_ERROR_TEXT("inline assembler must be a function")); return; } if (comp->pass > MP_PASS_SCOPE) { EMIT_INLINE_ASM_ARG(start_pass, comp->pass, &comp->compile_error); } // get the function definition parse node assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)scope->pn; assert(MP_PARSE_NODE_STRUCT_KIND(pns) == PN_funcdef); // qstr f_id = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); // function name // parameters are in pns->nodes[1] if (comp->pass == MP_PASS_CODE_SIZE) { mp_parse_node_t *pn_params; size_t n_params = mp_parse_node_extract_list(&pns->nodes[1], PN_typedargslist, &pn_params); scope->num_pos_args = EMIT_INLINE_ASM_ARG(count_params, n_params, pn_params); if (comp->compile_error != MP_OBJ_NULL) { goto inline_asm_error; } } // pns->nodes[2] is function return annotation mp_uint_t type_sig = MP_NATIVE_TYPE_INT; mp_parse_node_t pn_annotation = pns->nodes[2]; if (!MP_PARSE_NODE_IS_NULL(pn_annotation)) { // nodes[2] can be null or a test-expr if (MP_PARSE_NODE_IS_ID(pn_annotation)) { qstr ret_type = MP_PARSE_NODE_LEAF_ARG(pn_annotation); switch (ret_type) { case MP_QSTR_object: type_sig = MP_NATIVE_TYPE_OBJ; break; case MP_QSTR_bool: type_sig = MP_NATIVE_TYPE_BOOL; break; case MP_QSTR_int: type_sig = MP_NATIVE_TYPE_INT; break; case MP_QSTR_uint: type_sig = MP_NATIVE_TYPE_UINT; break; default: compile_syntax_error(comp, pn_annotation, MP_ERROR_TEXT("unknown type")); return; } } else { compile_syntax_error(comp, pn_annotation, MP_ERROR_TEXT("return annotation must be an identifier")); } } mp_parse_node_t pn_body = pns->nodes[3]; // body mp_parse_node_t *nodes; size_t num = mp_parse_node_extract_list(&pn_body, PN_suite_block_stmts, &nodes); for (size_t i = 0; i < num; i++) { assert(MP_PARSE_NODE_IS_STRUCT(nodes[i])); mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)nodes[i]; if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_pass_stmt) { // no instructions continue; } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) != PN_expr_stmt) { // not an instruction; error not_an_instruction: compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("expecting an assembler instruction")); return; } // check structure of parse node assert(MP_PARSE_NODE_IS_STRUCT(pns2->nodes[0])); if (!MP_PARSE_NODE_IS_NULL(pns2->nodes[1])) { goto not_an_instruction; } pns2 = (mp_parse_node_struct_t *)pns2->nodes[0]; if (MP_PARSE_NODE_STRUCT_KIND(pns2) != PN_atom_expr_normal) { goto not_an_instruction; } if (!MP_PARSE_NODE_IS_ID(pns2->nodes[0])) { goto not_an_instruction; } if (!MP_PARSE_NODE_IS_STRUCT_KIND(pns2->nodes[1], PN_trailer_paren)) { goto not_an_instruction; } // parse node looks like an instruction // get instruction name and args qstr op = MP_PARSE_NODE_LEAF_ARG(pns2->nodes[0]); pns2 = (mp_parse_node_struct_t *)pns2->nodes[1]; // PN_trailer_paren mp_parse_node_t *pn_arg; size_t n_args = mp_parse_node_extract_list(&pns2->nodes[0], PN_arglist, &pn_arg); // emit instructions if (op == MP_QSTR_label) { if (!(n_args == 1 && MP_PARSE_NODE_IS_ID(pn_arg[0]))) { compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("'label' requires 1 argument")); return; } uint lab = comp_next_label(comp); if (pass > MP_PASS_SCOPE) { if (!EMIT_INLINE_ASM_ARG(label, lab, MP_PARSE_NODE_LEAF_ARG(pn_arg[0]))) { compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("label redefined")); return; } } } else if (op == MP_QSTR_align) { if (!(n_args == 1 && MP_PARSE_NODE_IS_SMALL_INT(pn_arg[0]))) { compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("'align' requires 1 argument")); return; } if (pass > MP_PASS_SCOPE) { mp_asm_base_align((mp_asm_base_t *)comp->emit_inline_asm, MP_PARSE_NODE_LEAF_SMALL_INT(pn_arg[0])); } } else if (op == MP_QSTR_data) { if (!(n_args >= 2 && MP_PARSE_NODE_IS_SMALL_INT(pn_arg[0]))) { compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("'data' requires at least 2 arguments")); return; } if (pass > MP_PASS_SCOPE) { mp_int_t bytesize = MP_PARSE_NODE_LEAF_SMALL_INT(pn_arg[0]); for (uint j = 1; j < n_args; j++) { if (!MP_PARSE_NODE_IS_SMALL_INT(pn_arg[j])) { compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("'data' requires integer arguments")); return; } mp_asm_base_data((mp_asm_base_t *)comp->emit_inline_asm, bytesize, MP_PARSE_NODE_LEAF_SMALL_INT(pn_arg[j])); } } } else { if (pass > MP_PASS_SCOPE) { EMIT_INLINE_ASM_ARG(op, op, n_args, pn_arg); } } if (comp->compile_error != MP_OBJ_NULL) { pns = pns2; // this is the parse node that had the error goto inline_asm_error; } } if (comp->pass > MP_PASS_SCOPE) { EMIT_INLINE_ASM_ARG(end_pass, type_sig); if (comp->pass == MP_PASS_EMIT) { void *f = mp_asm_base_get_code((mp_asm_base_t *)comp->emit_inline_asm); mp_emit_glue_assign_native(comp->scope_cur->raw_code, MP_CODE_NATIVE_ASM, f, mp_asm_base_get_code_size((mp_asm_base_t *)comp->emit_inline_asm), NULL, #if MICROPY_PERSISTENT_CODE_SAVE 0, 0, 0, NULL, #endif 0, comp->scope_cur->num_pos_args, type_sig); } } if (comp->compile_error != MP_OBJ_NULL) { // inline assembler had an error; set line for its exception inline_asm_error: comp->compile_error_line = pns->source_line; } } #endif STATIC void scope_compute_things(scope_t *scope, mp_emit_common_t *emit_common) { // in MicroPython we put the *x parameter after all other parameters (except **y) if (scope->scope_flags & MP_SCOPE_FLAG_VARARGS) { id_info_t *id_param = NULL; for (int i = scope->id_info_len - 1; i >= 0; i--) { id_info_t *id = &scope->id_info[i]; if (id->flags & ID_FLAG_IS_STAR_PARAM) { if (id_param != NULL) { // swap star param with last param id_info_t temp = *id_param; *id_param = *id; *id = temp; } break; } else if (id_param == NULL && id->flags == ID_FLAG_IS_PARAM) { id_param = id; } } } // in functions, turn implicit globals into explicit globals // compute the index of each local scope->num_locals = 0; for (int i = 0; i < scope->id_info_len; i++) { id_info_t *id = &scope->id_info[i]; if (scope->kind == SCOPE_CLASS && id->qst == MP_QSTR___class__) { // __class__ is not counted as a local; if it's used then it becomes a ID_INFO_KIND_CELL continue; } if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { id->kind = ID_INFO_KIND_GLOBAL_EXPLICIT; } #if MICROPY_EMIT_NATIVE if (id->kind == ID_INFO_KIND_GLOBAL_EXPLICIT) { // This function makes a reference to a global variable if (scope->emit_options == MP_EMIT_OPT_VIPER && mp_native_type_from_qstr(id->qst) >= MP_NATIVE_TYPE_INT) { // A casting operator in viper mode, not a real global reference } else { scope->scope_flags |= MP_SCOPE_FLAG_REFGLOBALS; } } #endif // params always count for 1 local, even if they are a cell if (id->kind == ID_INFO_KIND_LOCAL || (id->flags & ID_FLAG_IS_PARAM)) { id->local_num = scope->num_locals++; } } // compute the index of cell vars for (int i = 0; i < scope->id_info_len; i++) { id_info_t *id = &scope->id_info[i]; // in MicroPython the cells come right after the fast locals // parameters are not counted here, since they remain at the start // of the locals, even if they are cell vars if (id->kind == ID_INFO_KIND_CELL && !(id->flags & ID_FLAG_IS_PARAM)) { id->local_num = scope->num_locals; scope->num_locals += 1; } } // compute the index of free vars // make sure they are in the order of the parent scope if (scope->parent != NULL) { int num_free = 0; for (int i = 0; i < scope->parent->id_info_len; i++) { id_info_t *id = &scope->parent->id_info[i]; if (id->kind == ID_INFO_KIND_CELL || id->kind == ID_INFO_KIND_FREE) { for (int j = 0; j < scope->id_info_len; j++) { id_info_t *id2 = &scope->id_info[j]; if (id2->kind == ID_INFO_KIND_FREE && id->qst == id2->qst) { assert(!(id2->flags & ID_FLAG_IS_PARAM)); // free vars should not be params // in MicroPython the frees come first, before the params id2->local_num = num_free; num_free += 1; } } } } // in MicroPython shift all other locals after the free locals if (num_free > 0) { for (int i = 0; i < scope->id_info_len; i++) { id_info_t *id = &scope->id_info[i]; if (id->kind != ID_INFO_KIND_FREE || (id->flags & ID_FLAG_IS_PARAM)) { id->local_num += num_free; } } scope->num_pos_args += num_free; // free vars are counted as params for passing them into the function scope->num_locals += num_free; mp_emit_common_use_qstr(emit_common, MP_QSTR__star_); } } } #if !MICROPY_PERSISTENT_CODE_SAVE STATIC #endif mp_compiled_module_t mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl, mp_module_context_t *context) { // put compiler state on the stack, it's relatively small compiler_t comp_state = {0}; compiler_t *comp = &comp_state; comp->is_repl = is_repl; comp->break_label = INVALID_LABEL; comp->continue_label = INVALID_LABEL; mp_emit_common_init(&comp->emit_common, source_file); // create the module scope #if MICROPY_EMIT_NATIVE const uint emit_opt = MP_STATE_VM(default_emit_opt); #else const uint emit_opt = MP_EMIT_OPT_NONE; #endif scope_t *module_scope = scope_new_and_link(comp, SCOPE_MODULE, parse_tree->root, emit_opt); // create standard emitter; it's used at least for MP_PASS_SCOPE emit_t *emit_bc = emit_bc_new(&comp->emit_common); // compile MP_PASS_SCOPE comp->emit = emit_bc; comp->emit_bc = emit_bc; #if MICROPY_EMIT_NATIVE comp->emit_method_table = &emit_bc_method_table; #endif uint max_num_labels = 0; for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { #if MICROPY_EMIT_INLINE_ASM if (s->emit_options == MP_EMIT_OPT_ASM) { compile_scope_inline_asm(comp, s, MP_PASS_SCOPE); } else #endif { compile_scope(comp, s, MP_PASS_SCOPE); // Check if any implicitly declared variables should be closed over for (size_t i = 0; i < s->id_info_len; ++i) { id_info_t *id = &s->id_info[i]; if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { scope_check_to_close_over(s, id); } } } // update maximim number of labels needed if (comp->next_label > max_num_labels) { max_num_labels = comp->next_label; } } // compute some things related to scope and identifiers bool has_native_code = false; for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { #if MICROPY_EMIT_NATIVE if (s->emit_options == MP_EMIT_OPT_NATIVE_PYTHON || s->emit_options == MP_EMIT_OPT_VIPER) { has_native_code = true; } #endif scope_compute_things(s, &comp->emit_common); } // set max number of labels now that it's calculated emit_bc_set_max_num_labels(emit_bc, max_num_labels); // finalise and allocate the constant table mp_emit_common_finalise(&comp->emit_common, has_native_code); // compile MP_PASS_STACK_SIZE, MP_PASS_CODE_SIZE, MP_PASS_EMIT #if MICROPY_EMIT_NATIVE emit_t *emit_native = NULL; #endif for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { #if MICROPY_EMIT_INLINE_ASM if (s->emit_options == MP_EMIT_OPT_ASM) { // inline assembly if (comp->emit_inline_asm == NULL) { comp->emit_inline_asm = ASM_EMITTER(new)(max_num_labels); } comp->emit = NULL; comp->emit_inline_asm_method_table = ASM_EMITTER_TABLE; compile_scope_inline_asm(comp, s, MP_PASS_CODE_SIZE); #if MICROPY_EMIT_INLINE_XTENSA // Xtensa requires an extra pass to compute size of l32r const table // TODO this can be improved by calculating it during SCOPE pass // but that requires some other structural changes to the asm emitters #if MICROPY_DYNAMIC_COMPILER if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_XTENSA) #endif { compile_scope_inline_asm(comp, s, MP_PASS_CODE_SIZE); } #endif if (comp->compile_error == MP_OBJ_NULL) { compile_scope_inline_asm(comp, s, MP_PASS_EMIT); } } else #endif { // choose the emit type switch (s->emit_options) { #if MICROPY_EMIT_NATIVE case MP_EMIT_OPT_NATIVE_PYTHON: case MP_EMIT_OPT_VIPER: if (emit_native == NULL) { emit_native = NATIVE_EMITTER(new)(&comp->emit_common, &comp->compile_error, &comp->next_label, max_num_labels); } comp->emit_method_table = NATIVE_EMITTER_TABLE; comp->emit = emit_native; break; #endif // MICROPY_EMIT_NATIVE default: comp->emit = emit_bc; #if MICROPY_EMIT_NATIVE comp->emit_method_table = &emit_bc_method_table; #endif break; } // need a pass to compute stack size compile_scope(comp, s, MP_PASS_STACK_SIZE); // second last pass: compute code size if (comp->compile_error == MP_OBJ_NULL) { compile_scope(comp, s, MP_PASS_CODE_SIZE); } // final pass: emit code if (comp->compile_error == MP_OBJ_NULL) { compile_scope(comp, s, MP_PASS_EMIT); } } } if (comp->compile_error != MP_OBJ_NULL) { // if there is no line number for the error then use the line // number for the start of this scope compile_error_set_line(comp, comp->scope_cur->pn); // add a traceback to the exception using relevant source info mp_obj_exception_add_traceback(comp->compile_error, source_file, comp->compile_error_line, comp->scope_cur->simple_name); } // construct the global qstr/const table for this module mp_compiled_module_t cm; cm.rc = module_scope->raw_code; cm.context = context; #if MICROPY_PERSISTENT_CODE_SAVE cm.has_native = has_native_code; cm.n_qstr = comp->emit_common.qstr_map.used; cm.n_obj = comp->emit_common.ct_cur_obj; #endif if (comp->compile_error == MP_OBJ_NULL) { mp_emit_common_populate_module_context(&comp->emit_common, source_file, context); #if MICROPY_DEBUG_PRINTERS // now that the module context is valid, the raw codes can be printed if (mp_verbose_flag >= 2) { for (scope_t *s = comp->scope_head; s != NULL; s = s->next) { mp_raw_code_t *rc = s->raw_code; mp_bytecode_print(&mp_plat_print, rc, &cm.context->constants); } } #endif } // free the emitters emit_bc_free(emit_bc); #if MICROPY_EMIT_NATIVE if (emit_native != NULL) { NATIVE_EMITTER(free)(emit_native); } #endif #if MICROPY_EMIT_INLINE_ASM if (comp->emit_inline_asm != NULL) { ASM_EMITTER(free)(comp->emit_inline_asm); } #endif // free the parse tree mp_parse_tree_clear(parse_tree); // free the scopes for (scope_t *s = module_scope; s;) { scope_t *next = s->next; scope_free(s); s = next; } if (comp->compile_error != MP_OBJ_NULL) { nlr_raise(comp->compile_error); } return cm; } mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { mp_module_context_t *context = m_new_obj(mp_module_context_t); context->module.globals = mp_globals_get(); mp_compiled_module_t cm = mp_compile_to_raw_code(parse_tree, source_file, is_repl, context); // return function that executes the outer module return mp_make_function_from_raw_code(cm.rc, cm.context, NULL); } #endif // MICROPY_ENABLE_COMPILER #include #include "py/emit.h" #if MICROPY_ENABLE_COMPILER #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst) { mp_map_elem_t *elem = mp_map_lookup(&emit->qstr_map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); if (elem->value == MP_OBJ_NULL) { assert(emit->pass == MP_PASS_SCOPE); elem->value = MP_OBJ_NEW_SMALL_INT(emit->qstr_map.used - 1); } return MP_OBJ_SMALL_INT_VALUE(elem->value); } #endif void mp_emit_common_get_id_for_modification(scope_t *scope, qstr qst) { // name adding/lookup id_info_t *id = scope_find_or_add_id(scope, qst, ID_INFO_KIND_GLOBAL_IMPLICIT); if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { // rebind as a local variable id->kind = ID_INFO_KIND_LOCAL; } } void mp_emit_common_id_op(emit_t *emit, const mp_emit_method_table_id_ops_t *emit_method_table, scope_t *scope, qstr qst) { // assumes pass is greater than 1, ie that all identifiers are defined in the scope id_info_t *id = scope_find(scope, qst); assert(id != NULL); // call the emit backend with the correct code if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_NAME); } else if (id->kind == ID_INFO_KIND_GLOBAL_EXPLICIT) { emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_GLOBAL); } else if (id->kind == ID_INFO_KIND_LOCAL) { emit_method_table->local(emit, qst, id->local_num, MP_EMIT_IDOP_LOCAL_FAST); } else { assert(id->kind == ID_INFO_KIND_CELL || id->kind == ID_INFO_KIND_FREE); emit_method_table->local(emit, qst, id->local_num, MP_EMIT_IDOP_LOCAL_DEREF); } } #endif // MICROPY_ENABLE_COMPILER #include #include #ifndef __IPU__ #include #endif #include #include #include "py/mpstate.h" #include "py/emit.h" #include "py/bc0.h" #if MICROPY_ENABLE_COMPILER #define DUMMY_DATA_SIZE (MP_ENCODE_UINT_MAX_BYTES) struct _emit_t { // Accessed as mp_obj_t, so must be aligned as such, and we rely on the // memory allocator returning a suitably aligned pointer. // Should work for cases when mp_obj_t is 64-bit on a 32-bit machine. byte dummy_data[DUMMY_DATA_SIZE]; pass_kind_t pass : 8; mp_uint_t last_emit_was_return_value : 8; int stack_size; mp_emit_common_t *emit_common; scope_t *scope; mp_uint_t last_source_line_offset; mp_uint_t last_source_line; mp_uint_t max_num_labels; mp_uint_t *label_offsets; size_t code_info_offset; size_t code_info_size; size_t bytecode_offset; size_t bytecode_size; byte *code_base; // stores both byte code and code info size_t n_info; size_t n_cell; }; emit_t *emit_bc_new(mp_emit_common_t *emit_common) { emit_t *emit = m_new0(emit_t, 1); emit->emit_common = emit_common; return emit; } void emit_bc_set_max_num_labels(emit_t *emit, mp_uint_t max_num_labels) { emit->max_num_labels = max_num_labels; emit->label_offsets = m_new(mp_uint_t, emit->max_num_labels); } void emit_bc_free(emit_t *emit) { m_del(mp_uint_t, emit->label_offsets, emit->max_num_labels); m_del_obj(emit_t, emit); } // all functions must go through this one to emit code info STATIC uint8_t *emit_get_cur_to_write_code_info(void *emit_in, size_t num_bytes_to_write) { emit_t *emit = emit_in; if (emit->pass < MP_PASS_EMIT) { emit->code_info_offset += num_bytes_to_write; return emit->dummy_data; } else { assert(emit->code_info_offset + num_bytes_to_write <= emit->code_info_size); byte *c = emit->code_base + emit->code_info_offset; emit->code_info_offset += num_bytes_to_write; return c; } } STATIC void emit_write_code_info_byte(emit_t *emit, byte val) { *emit_get_cur_to_write_code_info(emit, 1) = val; } STATIC void emit_write_code_info_qstr(emit_t *emit, qstr qst) { mp_encode_uint(emit, emit_get_cur_to_write_code_info, mp_emit_common_use_qstr(emit->emit_common, qst)); } #if MICROPY_ENABLE_SOURCE_LINE STATIC void emit_write_code_info_bytes_lines(emit_t *emit, mp_uint_t bytes_to_skip, mp_uint_t lines_to_skip) { assert(bytes_to_skip > 0 || lines_to_skip > 0); while (bytes_to_skip > 0 || lines_to_skip > 0) { mp_uint_t b, l; if (lines_to_skip <= 6 || bytes_to_skip > 0xf) { // use 0b0LLBBBBB encoding b = MIN(bytes_to_skip, 0x1f); if (b < bytes_to_skip) { // we can't skip any lines until we skip all the bytes l = 0; } else { l = MIN(lines_to_skip, 0x3); } *emit_get_cur_to_write_code_info(emit, 1) = b | (l << 5); } else { // use 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) b = MIN(bytes_to_skip, 0xf); l = MIN(lines_to_skip, 0x7ff); byte *ci = emit_get_cur_to_write_code_info(emit, 2); ci[0] = 0x80 | b | ((l >> 4) & 0x70); ci[1] = l; } bytes_to_skip -= b; lines_to_skip -= l; } } #endif // all functions must go through this one to emit byte code STATIC uint8_t *emit_get_cur_to_write_bytecode(void *emit_in, size_t num_bytes_to_write) { emit_t *emit = emit_in; if (emit->pass < MP_PASS_EMIT) { emit->bytecode_offset += num_bytes_to_write; return emit->dummy_data; } else { assert(emit->bytecode_offset + num_bytes_to_write <= emit->bytecode_size); byte *c = emit->code_base + emit->code_info_size + emit->bytecode_offset; emit->bytecode_offset += num_bytes_to_write; return c; } } STATIC void emit_write_bytecode_raw_byte(emit_t *emit, byte b1) { byte *c = emit_get_cur_to_write_bytecode(emit, 1); c[0] = b1; } STATIC void emit_write_bytecode_byte(emit_t *emit, int stack_adj, byte b1) { mp_emit_bc_adjust_stack_size(emit, stack_adj); byte *c = emit_get_cur_to_write_bytecode(emit, 1); c[0] = b1; } // Similar to mp_encode_uint(), just some extra handling to encode sign STATIC void emit_write_bytecode_byte_int(emit_t *emit, int stack_adj, byte b1, mp_int_t num) { emit_write_bytecode_byte(emit, stack_adj, b1); // We store each 7 bits in a separate byte, and that's how many bytes needed byte buf[MP_ENCODE_UINT_MAX_BYTES]; byte *p = buf + sizeof(buf); // We encode in little-ending order, but store in big-endian, to help decoding do { *--p = num & 0x7f; num >>= 7; } while (num != 0 && num != -1); // Make sure that highest bit we stored (mask 0x40) matches sign // of the number. If not, store extra byte just to encode sign if (num == -1 && (*p & 0x40) == 0) { *--p = 0x7f; } else if (num == 0 && (*p & 0x40) != 0) { *--p = 0; } byte *c = emit_get_cur_to_write_bytecode(emit, buf + sizeof(buf) - p); while (p != buf + sizeof(buf) - 1) { *c++ = *p++ | 0x80; } *c = *p; } STATIC void emit_write_bytecode_byte_uint(emit_t *emit, int stack_adj, byte b, mp_uint_t val) { emit_write_bytecode_byte(emit, stack_adj, b); mp_encode_uint(emit, emit_get_cur_to_write_bytecode, val); } STATIC void emit_write_bytecode_byte_const(emit_t *emit, int stack_adj, byte b, mp_uint_t n) { emit_write_bytecode_byte_uint(emit, stack_adj, b, n); } STATIC void emit_write_bytecode_byte_qstr(emit_t *emit, int stack_adj, byte b, qstr qst) { emit_write_bytecode_byte_uint(emit, stack_adj, b, mp_emit_common_use_qstr(emit->emit_common, qst)); } STATIC void emit_write_bytecode_byte_obj(emit_t *emit, int stack_adj, byte b, mp_obj_t obj) { emit_write_bytecode_byte_const(emit, stack_adj, b, mp_emit_common_alloc_const_obj(emit->emit_common, obj)); } STATIC void emit_write_bytecode_byte_child(emit_t *emit, int stack_adj, byte b, mp_raw_code_t *rc) { emit_write_bytecode_byte_const(emit, stack_adj, b, mp_emit_common_alloc_const_child(emit->emit_common, rc)); #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = emit->last_source_line; #endif } // unsigned labels are relative to ip following this instruction, stored as 16 bits STATIC void emit_write_bytecode_byte_unsigned_label(emit_t *emit, int stack_adj, byte b1, mp_uint_t label) { mp_emit_bc_adjust_stack_size(emit, stack_adj); mp_uint_t bytecode_offset; if (emit->pass < MP_PASS_EMIT) { bytecode_offset = 0; } else { bytecode_offset = emit->label_offsets[label] - emit->bytecode_offset - 3; } byte *c = emit_get_cur_to_write_bytecode(emit, 3); c[0] = b1; c[1] = bytecode_offset; c[2] = bytecode_offset >> 8; } // signed labels are relative to ip following this instruction, stored as 16 bits, in excess STATIC void emit_write_bytecode_byte_signed_label(emit_t *emit, int stack_adj, byte b1, mp_uint_t label) { mp_emit_bc_adjust_stack_size(emit, stack_adj); int bytecode_offset; if (emit->pass < MP_PASS_EMIT) { bytecode_offset = 0; } else { bytecode_offset = emit->label_offsets[label] - emit->bytecode_offset - 3 + 0x8000; } byte *c = emit_get_cur_to_write_bytecode(emit, 3); c[0] = b1; c[1] = bytecode_offset; c[2] = bytecode_offset >> 8; } void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { emit->pass = pass; emit->stack_size = 0; emit->last_emit_was_return_value = false; emit->scope = scope; emit->last_source_line_offset = 0; emit->last_source_line = 1; #ifndef NDEBUG // With debugging enabled labels are checked for unique assignment if (pass < MP_PASS_EMIT && emit->label_offsets != NULL) { memset(emit->label_offsets, -1, emit->max_num_labels * sizeof(mp_uint_t)); } #endif emit->bytecode_offset = 0; emit->code_info_offset = 0; // Write local state size, exception stack size, scope flags and number of arguments { mp_uint_t n_state = scope->num_locals + scope->stack_size; if (n_state == 0) { // Need at least 1 entry in the state, in the case an exception is // propagated through this function, the exception is returned in // the highest slot in the state (fastn[0], see vm.c). n_state = 1; } #if MICROPY_DEBUG_VM_STACK_OVERFLOW // An extra slot in the stack is needed to detect VM stack overflow n_state += 1; #endif size_t n_exc_stack = scope->exc_stack_size; MP_BC_PRELUDE_SIG_ENCODE(n_state, n_exc_stack, scope, emit_write_code_info_byte, emit); } // Write number of cells and size of the source code info if (emit->pass >= MP_PASS_CODE_SIZE) { size_t n_info = emit->n_info; size_t n_cell = emit->n_cell; MP_BC_PRELUDE_SIZE_ENCODE(n_info, n_cell, emit_write_code_info_byte, emit); } emit->n_info = emit->code_info_offset; // Write the name of this function. emit_write_code_info_qstr(emit, scope->simple_name); // Write argument names, needed to resolve positional args passed as keywords. { // For a given argument position (indexed by i) we need to find the // corresponding id_info which is a parameter, as it has the correct // qstr name to use as the argument name. Note that it's not a simple // 1-1 mapping (ie i!=j in general) because of possible closed-over // variables. In the case that the argument i has no corresponding // parameter we use "*" as its name (since no argument can ever be named // "*"). We could use a blank qstr but "*" is better for debugging. // Note: there is some wasted RAM here for the case of storing a qstr // for each closed-over variable, and maybe there is a better way to do // it, but that would require changes to mp_setup_code_state. for (int i = 0; i < scope->num_pos_args + scope->num_kwonly_args; i++) { qstr qst = MP_QSTR__star_; for (int j = 0; j < scope->id_info_len; ++j) { id_info_t *id = &scope->id_info[j]; if ((id->flags & ID_FLAG_IS_PARAM) && id->local_num == i) { qst = id->qst; break; } } emit_write_code_info_qstr(emit, qst); } } } void mp_emit_bc_end_pass(emit_t *emit) { if (emit->pass == MP_PASS_SCOPE) { return; } // check stack is back to zero size assert(emit->stack_size == 0); // Calculate size of source code info section emit->n_info = emit->code_info_offset - emit->n_info; // Emit closure section of prelude emit->n_cell = 0; for (size_t i = 0; i < emit->scope->id_info_len; ++i) { id_info_t *id = &emit->scope->id_info[i]; if (id->kind == ID_INFO_KIND_CELL) { assert(id->local_num <= 255); emit_write_code_info_byte(emit, id->local_num); // write the local which should be converted to a cell ++emit->n_cell; } } if (emit->pass == MP_PASS_CODE_SIZE) { // calculate size of total code-info + bytecode, in bytes emit->code_info_size = emit->code_info_offset; emit->bytecode_size = emit->bytecode_offset; emit->code_base = m_new0(byte, emit->code_info_size + emit->bytecode_size); } else if (emit->pass == MP_PASS_EMIT) { mp_emit_glue_assign_bytecode(emit->scope->raw_code, emit->code_base, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS emit->code_info_size + emit->bytecode_size, #endif emit->emit_common->children, #if MICROPY_PERSISTENT_CODE_SAVE emit->emit_common->ct_cur_child, #endif emit->scope->scope_flags); } } bool mp_emit_bc_last_emit_was_return_value(emit_t *emit) { return emit->last_emit_was_return_value; } void mp_emit_bc_adjust_stack_size(emit_t *emit, mp_int_t delta) { if (emit->pass == MP_PASS_SCOPE) { return; } assert((mp_int_t)emit->stack_size + delta >= 0); emit->stack_size += delta; if (emit->stack_size > emit->scope->stack_size) { emit->scope->stack_size = emit->stack_size; } emit->last_emit_was_return_value = false; } void mp_emit_bc_set_source_line(emit_t *emit, mp_uint_t source_line) { #if MICROPY_ENABLE_SOURCE_LINE if (MP_STATE_VM(mp_optimise_value) >= 3) { // If we compile with -O3, don't store line numbers. return; } if (source_line > emit->last_source_line) { mp_uint_t bytes_to_skip = emit->bytecode_offset - emit->last_source_line_offset; mp_uint_t lines_to_skip = source_line - emit->last_source_line; emit_write_code_info_bytes_lines(emit, bytes_to_skip, lines_to_skip); emit->last_source_line_offset = emit->bytecode_offset; emit->last_source_line = source_line; } #else (void)emit; (void)source_line; #endif } void mp_emit_bc_label_assign(emit_t *emit, mp_uint_t l) { mp_emit_bc_adjust_stack_size(emit, 0); if (emit->pass == MP_PASS_SCOPE) { return; } assert(l < emit->max_num_labels); if (emit->pass < MP_PASS_EMIT) { // assign label offset assert(emit->label_offsets[l] == (mp_uint_t)-1); emit->label_offsets[l] = emit->bytecode_offset; } else { // ensure label offset has not changed from MP_PASS_CODE_SIZE to MP_PASS_EMIT assert(emit->label_offsets[l] == emit->bytecode_offset); } } void mp_emit_bc_import(emit_t *emit, qstr qst, int kind) { MP_STATIC_ASSERT(MP_BC_IMPORT_NAME + MP_EMIT_IMPORT_NAME == MP_BC_IMPORT_NAME); MP_STATIC_ASSERT(MP_BC_IMPORT_NAME + MP_EMIT_IMPORT_FROM == MP_BC_IMPORT_FROM); int stack_adj = kind == MP_EMIT_IMPORT_FROM ? 1 : -1; if (kind == MP_EMIT_IMPORT_STAR) { emit_write_bytecode_byte(emit, stack_adj, MP_BC_IMPORT_STAR); } else { emit_write_bytecode_byte_qstr(emit, stack_adj, MP_BC_IMPORT_NAME + kind, qst); } } void mp_emit_bc_load_const_tok(emit_t *emit, mp_token_kind_t tok) { MP_STATIC_ASSERT(MP_BC_LOAD_CONST_FALSE + (MP_TOKEN_KW_NONE - MP_TOKEN_KW_FALSE) == MP_BC_LOAD_CONST_NONE); MP_STATIC_ASSERT(MP_BC_LOAD_CONST_FALSE + (MP_TOKEN_KW_TRUE - MP_TOKEN_KW_FALSE) == MP_BC_LOAD_CONST_TRUE); if (tok == MP_TOKEN_ELLIPSIS) { emit_write_bytecode_byte_obj(emit, 1, MP_BC_LOAD_CONST_OBJ, MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj)); } else { emit_write_bytecode_byte(emit, 1, MP_BC_LOAD_CONST_FALSE + (tok - MP_TOKEN_KW_FALSE)); } } void mp_emit_bc_load_const_small_int(emit_t *emit, mp_int_t arg) { if (-MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS <= arg && arg < MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM - MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS) { emit_write_bytecode_byte(emit, 1, MP_BC_LOAD_CONST_SMALL_INT_MULTI + MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS + arg); } else { emit_write_bytecode_byte_int(emit, 1, MP_BC_LOAD_CONST_SMALL_INT, arg); } } void mp_emit_bc_load_const_str(emit_t *emit, qstr qst) { emit_write_bytecode_byte_qstr(emit, 1, MP_BC_LOAD_CONST_STRING, qst); } void mp_emit_bc_load_const_obj(emit_t *emit, mp_obj_t obj) { emit_write_bytecode_byte_obj(emit, 1, MP_BC_LOAD_CONST_OBJ, obj); } void mp_emit_bc_load_null(emit_t *emit) { emit_write_bytecode_byte(emit, 1, MP_BC_LOAD_NULL); } void mp_emit_bc_load_local(emit_t *emit, qstr qst, mp_uint_t local_num, int kind) { MP_STATIC_ASSERT(MP_BC_LOAD_FAST_N + MP_EMIT_IDOP_LOCAL_FAST == MP_BC_LOAD_FAST_N); MP_STATIC_ASSERT(MP_BC_LOAD_FAST_N + MP_EMIT_IDOP_LOCAL_DEREF == MP_BC_LOAD_DEREF); (void)qst; if (kind == MP_EMIT_IDOP_LOCAL_FAST && local_num <= 15) { emit_write_bytecode_byte(emit, 1, MP_BC_LOAD_FAST_MULTI + local_num); } else { emit_write_bytecode_byte_uint(emit, 1, MP_BC_LOAD_FAST_N + kind, local_num); } } void mp_emit_bc_load_global(emit_t *emit, qstr qst, int kind) { MP_STATIC_ASSERT(MP_BC_LOAD_NAME + MP_EMIT_IDOP_GLOBAL_NAME == MP_BC_LOAD_NAME); MP_STATIC_ASSERT(MP_BC_LOAD_NAME + MP_EMIT_IDOP_GLOBAL_GLOBAL == MP_BC_LOAD_GLOBAL); (void)qst; emit_write_bytecode_byte_qstr(emit, 1, MP_BC_LOAD_NAME + kind, qst); } void mp_emit_bc_load_method(emit_t *emit, qstr qst, bool is_super) { int stack_adj = 1 - 2 * is_super; emit_write_bytecode_byte_qstr(emit, stack_adj, is_super ? MP_BC_LOAD_SUPER_METHOD : MP_BC_LOAD_METHOD, qst); } void mp_emit_bc_load_build_class(emit_t *emit) { emit_write_bytecode_byte(emit, 1, MP_BC_LOAD_BUILD_CLASS); } void mp_emit_bc_subscr(emit_t *emit, int kind) { if (kind == MP_EMIT_SUBSCR_LOAD) { emit_write_bytecode_byte(emit, -1, MP_BC_LOAD_SUBSCR); } else { if (kind == MP_EMIT_SUBSCR_DELETE) { mp_emit_bc_load_null(emit); mp_emit_bc_rot_three(emit); } emit_write_bytecode_byte(emit, -3, MP_BC_STORE_SUBSCR); } } void mp_emit_bc_attr(emit_t *emit, qstr qst, int kind) { if (kind == MP_EMIT_ATTR_LOAD) { emit_write_bytecode_byte_qstr(emit, 0, MP_BC_LOAD_ATTR, qst); } else { if (kind == MP_EMIT_ATTR_DELETE) { mp_emit_bc_load_null(emit); mp_emit_bc_rot_two(emit); } emit_write_bytecode_byte_qstr(emit, -2, MP_BC_STORE_ATTR, qst); } } void mp_emit_bc_store_local(emit_t *emit, qstr qst, mp_uint_t local_num, int kind) { MP_STATIC_ASSERT(MP_BC_STORE_FAST_N + MP_EMIT_IDOP_LOCAL_FAST == MP_BC_STORE_FAST_N); MP_STATIC_ASSERT(MP_BC_STORE_FAST_N + MP_EMIT_IDOP_LOCAL_DEREF == MP_BC_STORE_DEREF); (void)qst; if (kind == MP_EMIT_IDOP_LOCAL_FAST && local_num <= 15) { emit_write_bytecode_byte(emit, -1, MP_BC_STORE_FAST_MULTI + local_num); } else { emit_write_bytecode_byte_uint(emit, -1, MP_BC_STORE_FAST_N + kind, local_num); } } void mp_emit_bc_store_global(emit_t *emit, qstr qst, int kind) { MP_STATIC_ASSERT(MP_BC_STORE_NAME + MP_EMIT_IDOP_GLOBAL_NAME == MP_BC_STORE_NAME); MP_STATIC_ASSERT(MP_BC_STORE_NAME + MP_EMIT_IDOP_GLOBAL_GLOBAL == MP_BC_STORE_GLOBAL); emit_write_bytecode_byte_qstr(emit, -1, MP_BC_STORE_NAME + kind, qst); } void mp_emit_bc_delete_local(emit_t *emit, qstr qst, mp_uint_t local_num, int kind) { MP_STATIC_ASSERT(MP_BC_DELETE_FAST + MP_EMIT_IDOP_LOCAL_FAST == MP_BC_DELETE_FAST); MP_STATIC_ASSERT(MP_BC_DELETE_FAST + MP_EMIT_IDOP_LOCAL_DEREF == MP_BC_DELETE_DEREF); (void)qst; emit_write_bytecode_byte_uint(emit, 0, MP_BC_DELETE_FAST + kind, local_num); } void mp_emit_bc_delete_global(emit_t *emit, qstr qst, int kind) { MP_STATIC_ASSERT(MP_BC_DELETE_NAME + MP_EMIT_IDOP_GLOBAL_NAME == MP_BC_DELETE_NAME); MP_STATIC_ASSERT(MP_BC_DELETE_NAME + MP_EMIT_IDOP_GLOBAL_GLOBAL == MP_BC_DELETE_GLOBAL); emit_write_bytecode_byte_qstr(emit, 0, MP_BC_DELETE_NAME + kind, qst); } void mp_emit_bc_dup_top(emit_t *emit) { emit_write_bytecode_byte(emit, 1, MP_BC_DUP_TOP); } void mp_emit_bc_dup_top_two(emit_t *emit) { emit_write_bytecode_byte(emit, 2, MP_BC_DUP_TOP_TWO); } void mp_emit_bc_pop_top(emit_t *emit) { emit_write_bytecode_byte(emit, -1, MP_BC_POP_TOP); } void mp_emit_bc_longyield(emit_t *emit) { emit_write_bytecode_byte(emit, 0, MP_BC_LONGYIELD); } void mp_emit_bc_ipusyscall(emit_t *emit) { emit_write_bytecode_byte(emit, 0, MP_BC_IPUSYSCALL); } void mp_emit_bc_rot_two(emit_t *emit) { emit_write_bytecode_byte(emit, 0, MP_BC_ROT_TWO); } void mp_emit_bc_rot_three(emit_t *emit) { emit_write_bytecode_byte(emit, 0, MP_BC_ROT_THREE); } void mp_emit_bc_jump(emit_t *emit, mp_uint_t label) { emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_JUMP, label); } void mp_emit_bc_pop_jump_if(emit_t *emit, bool cond, mp_uint_t label) { if (cond) { emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_POP_JUMP_IF_TRUE, label); } else { emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_POP_JUMP_IF_FALSE, label); } } void mp_emit_bc_jump_if_or_pop(emit_t *emit, bool cond, mp_uint_t label) { if (cond) { emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_JUMP_IF_TRUE_OR_POP, label); } else { emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_JUMP_IF_FALSE_OR_POP, label); } } void mp_emit_bc_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t except_depth) { if (except_depth == 0) { if (label & MP_EMIT_BREAK_FROM_FOR) { // need to pop the iterator if we are breaking out of a for loop emit_write_bytecode_raw_byte(emit, MP_BC_POP_TOP); // also pop the iter_buf for (size_t i = 0; i < MP_OBJ_ITER_BUF_NSLOTS - 1; ++i) { emit_write_bytecode_raw_byte(emit, MP_BC_POP_TOP); } } emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); } else { emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_UNWIND_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); emit_write_bytecode_raw_byte(emit, ((label & MP_EMIT_BREAK_FROM_FOR) ? 0x80 : 0) | except_depth); } } void mp_emit_bc_setup_block(emit_t *emit, mp_uint_t label, int kind) { MP_STATIC_ASSERT(MP_BC_SETUP_WITH + MP_EMIT_SETUP_BLOCK_WITH == MP_BC_SETUP_WITH); MP_STATIC_ASSERT(MP_BC_SETUP_WITH + MP_EMIT_SETUP_BLOCK_EXCEPT == MP_BC_SETUP_EXCEPT); MP_STATIC_ASSERT(MP_BC_SETUP_WITH + MP_EMIT_SETUP_BLOCK_FINALLY == MP_BC_SETUP_FINALLY); // The SETUP_WITH opcode pops ctx_mgr from the top of the stack // and then pushes 3 entries: __exit__, ctx_mgr, as_value. int stack_adj = kind == MP_EMIT_SETUP_BLOCK_WITH ? 2 : 0; emit_write_bytecode_byte_unsigned_label(emit, stack_adj, MP_BC_SETUP_WITH + kind, label); } void mp_emit_bc_with_cleanup(emit_t *emit, mp_uint_t label) { mp_emit_bc_load_const_tok(emit, MP_TOKEN_KW_NONE); mp_emit_bc_label_assign(emit, label); // The +2 is to ensure we have enough stack space to call the __exit__ method emit_write_bytecode_byte(emit, 2, MP_BC_WITH_CLEANUP); // Cancel the +2 above, plus the +2 from mp_emit_bc_setup_block(MP_EMIT_SETUP_BLOCK_WITH) mp_emit_bc_adjust_stack_size(emit, -4); } void mp_emit_bc_end_finally(emit_t *emit) { emit_write_bytecode_byte(emit, -1, MP_BC_END_FINALLY); } void mp_emit_bc_get_iter(emit_t *emit, bool use_stack) { int stack_adj = use_stack ? MP_OBJ_ITER_BUF_NSLOTS - 1 : 0; emit_write_bytecode_byte(emit, stack_adj, use_stack ? MP_BC_GET_ITER_STACK : MP_BC_GET_ITER); } void mp_emit_bc_for_iter(emit_t *emit, mp_uint_t label) { emit_write_bytecode_byte_unsigned_label(emit, 1, MP_BC_FOR_ITER, label); } void mp_emit_bc_for_iter_end(emit_t *emit) { mp_emit_bc_adjust_stack_size(emit, -MP_OBJ_ITER_BUF_NSLOTS); } void mp_emit_bc_pop_except_jump(emit_t *emit, mp_uint_t label, bool within_exc_handler) { (void)within_exc_handler; emit_write_bytecode_byte_unsigned_label(emit, 0, MP_BC_POP_EXCEPT_JUMP, label); } void mp_emit_bc_unary_op(emit_t *emit, mp_unary_op_t op) { emit_write_bytecode_byte(emit, 0, MP_BC_UNARY_OP_MULTI + op); } void mp_emit_bc_binary_op(emit_t *emit, mp_binary_op_t op) { bool invert = false; if (op == MP_BINARY_OP_NOT_IN) { invert = true; op = MP_BINARY_OP_IN; } else if (op == MP_BINARY_OP_IS_NOT) { invert = true; op = MP_BINARY_OP_IS; } emit_write_bytecode_byte(emit, -1, MP_BC_BINARY_OP_MULTI + op); if (invert) { emit_write_bytecode_byte(emit, 0, MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NOT); } } void mp_emit_bc_build(emit_t *emit, mp_uint_t n_args, int kind) { MP_STATIC_ASSERT(MP_BC_BUILD_TUPLE + MP_EMIT_BUILD_TUPLE == MP_BC_BUILD_TUPLE); MP_STATIC_ASSERT(MP_BC_BUILD_TUPLE + MP_EMIT_BUILD_LIST == MP_BC_BUILD_LIST); MP_STATIC_ASSERT(MP_BC_BUILD_TUPLE + MP_EMIT_BUILD_MAP == MP_BC_BUILD_MAP); MP_STATIC_ASSERT(MP_BC_BUILD_TUPLE + MP_EMIT_BUILD_SET == MP_BC_BUILD_SET); MP_STATIC_ASSERT(MP_BC_BUILD_TUPLE + MP_EMIT_BUILD_SLICE == MP_BC_BUILD_SLICE); int stack_adj = kind == MP_EMIT_BUILD_MAP ? 1 : 1 - n_args; emit_write_bytecode_byte_uint(emit, stack_adj, MP_BC_BUILD_TUPLE + kind, n_args); } void mp_emit_bc_store_map(emit_t *emit) { emit_write_bytecode_byte(emit, -2, MP_BC_STORE_MAP); } void mp_emit_bc_store_comp(emit_t *emit, scope_kind_t kind, mp_uint_t collection_stack_index) { int t; int n; if (kind == SCOPE_LIST_COMP) { n = 0; t = 0; } else if (!MICROPY_PY_BUILTINS_SET || kind == SCOPE_DICT_COMP) { n = 1; t = 1; } else if (MICROPY_PY_BUILTINS_SET) { n = 0; t = 2; } // the lower 2 bits of the opcode argument indicate the collection type emit_write_bytecode_byte_uint(emit, -1 - n, MP_BC_STORE_COMP, ((collection_stack_index + n) << 2) | t); } void mp_emit_bc_unpack_sequence(emit_t *emit, mp_uint_t n_args) { emit_write_bytecode_byte_uint(emit, -1 + n_args, MP_BC_UNPACK_SEQUENCE, n_args); } void mp_emit_bc_unpack_ex(emit_t *emit, mp_uint_t n_left, mp_uint_t n_right) { emit_write_bytecode_byte_uint(emit, -1 + n_left + n_right + 1, MP_BC_UNPACK_EX, n_left | (n_right << 8)); } void mp_emit_bc_make_function(emit_t *emit, scope_t *scope, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { if (n_pos_defaults == 0 && n_kw_defaults == 0) { emit_write_bytecode_byte_child(emit, 1, MP_BC_MAKE_FUNCTION, scope->raw_code); } else { emit_write_bytecode_byte_child(emit, -1, MP_BC_MAKE_FUNCTION_DEFARGS, scope->raw_code); } } void mp_emit_bc_make_closure(emit_t *emit, scope_t *scope, mp_uint_t n_closed_over, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { if (n_pos_defaults == 0 && n_kw_defaults == 0) { int stack_adj = -n_closed_over + 1; emit_write_bytecode_byte_child(emit, stack_adj, MP_BC_MAKE_CLOSURE, scope->raw_code); emit_write_bytecode_raw_byte(emit, n_closed_over); } else { assert(n_closed_over <= 255); int stack_adj = -2 - (mp_int_t)n_closed_over + 1; emit_write_bytecode_byte_child(emit, stack_adj, MP_BC_MAKE_CLOSURE_DEFARGS, scope->raw_code); emit_write_bytecode_raw_byte(emit, n_closed_over); } } STATIC void emit_bc_call_function_method_helper(emit_t *emit, int stack_adj, mp_uint_t bytecode_base, mp_uint_t n_positional, mp_uint_t n_keyword, mp_uint_t star_flags) { if (star_flags) { stack_adj -= (int)n_positional + 2 * (int)n_keyword + 2; emit_write_bytecode_byte_uint(emit, stack_adj, bytecode_base + 1, (n_keyword << 8) | n_positional); // TODO make it 2 separate uints? } else { stack_adj -= (int)n_positional + 2 * (int)n_keyword; emit_write_bytecode_byte_uint(emit, stack_adj, bytecode_base, (n_keyword << 8) | n_positional); // TODO make it 2 separate uints? } } void mp_emit_bc_call_function(emit_t *emit, mp_uint_t n_positional, mp_uint_t n_keyword, mp_uint_t star_flags) { emit_bc_call_function_method_helper(emit, 0, MP_BC_CALL_FUNCTION, n_positional, n_keyword, star_flags); } void mp_emit_bc_call_method(emit_t *emit, mp_uint_t n_positional, mp_uint_t n_keyword, mp_uint_t star_flags) { emit_bc_call_function_method_helper(emit, -1, MP_BC_CALL_METHOD, n_positional, n_keyword, star_flags); } void mp_emit_bc_return_value(emit_t *emit) { emit_write_bytecode_byte(emit, -1, MP_BC_RETURN_VALUE); emit->last_emit_was_return_value = true; } void mp_emit_bc_raise_varargs(emit_t *emit, mp_uint_t n_args) { MP_STATIC_ASSERT(MP_BC_RAISE_LAST + 1 == MP_BC_RAISE_OBJ); MP_STATIC_ASSERT(MP_BC_RAISE_LAST + 2 == MP_BC_RAISE_FROM); assert(n_args <= 2); emit_write_bytecode_byte(emit, -n_args, MP_BC_RAISE_LAST + n_args); } void mp_emit_bc_yield(emit_t *emit, int kind) { MP_STATIC_ASSERT(MP_BC_YIELD_VALUE + 1 == MP_BC_YIELD_FROM); emit_write_bytecode_byte(emit, -kind, MP_BC_YIELD_VALUE + kind); emit->scope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; } void mp_emit_bc_start_except_handler(emit_t *emit) { mp_emit_bc_adjust_stack_size(emit, 4); // stack adjust for the exception instance, +3 for possible UNWIND_JUMP state } void mp_emit_bc_end_except_handler(emit_t *emit) { mp_emit_bc_adjust_stack_size(emit, -3); // stack adjust } #if MICROPY_EMIT_NATIVE const emit_method_table_t emit_bc_method_table = { #if MICROPY_DYNAMIC_COMPILER NULL, NULL, #endif mp_emit_bc_start_pass, mp_emit_bc_end_pass, mp_emit_bc_last_emit_was_return_value, mp_emit_bc_adjust_stack_size, mp_emit_bc_set_source_line, { mp_emit_bc_load_local, mp_emit_bc_load_global, }, { mp_emit_bc_store_local, mp_emit_bc_store_global, }, { mp_emit_bc_delete_local, mp_emit_bc_delete_global, }, mp_emit_bc_label_assign, mp_emit_bc_import, mp_emit_bc_load_const_tok, mp_emit_bc_load_const_small_int, mp_emit_bc_load_const_str, mp_emit_bc_load_const_obj, mp_emit_bc_load_null, mp_emit_bc_load_method, mp_emit_bc_load_build_class, mp_emit_bc_subscr, mp_emit_bc_attr, mp_emit_bc_dup_top, mp_emit_bc_dup_top_two, mp_emit_bc_pop_top, mp_emit_bc_rot_two, mp_emit_bc_rot_three, mp_emit_bc_jump, mp_emit_bc_pop_jump_if, mp_emit_bc_jump_if_or_pop, mp_emit_bc_unwind_jump, mp_emit_bc_setup_block, mp_emit_bc_with_cleanup, mp_emit_bc_end_finally, mp_emit_bc_get_iter, mp_emit_bc_for_iter, mp_emit_bc_for_iter_end, mp_emit_bc_pop_except_jump, mp_emit_bc_unary_op, mp_emit_bc_binary_op, mp_emit_bc_build, mp_emit_bc_store_map, mp_emit_bc_store_comp, mp_emit_bc_unpack_sequence, mp_emit_bc_unpack_ex, mp_emit_bc_make_function, mp_emit_bc_make_closure, mp_emit_bc_call_function, mp_emit_bc_call_method, mp_emit_bc_return_value, mp_emit_bc_raise_varargs, mp_emit_bc_yield, mp_emit_bc_start_except_handler, mp_emit_bc_end_except_handler, }; #else const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_load_id_ops = { mp_emit_bc_load_local, mp_emit_bc_load_global, }; const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_store_id_ops = { mp_emit_bc_store_local, mp_emit_bc_store_global, }; const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_delete_id_ops = { mp_emit_bc_delete_local, mp_emit_bc_delete_global, }; #endif #endif // MICROPY_ENABLE_COMPILER #include #include #include "py/obj.h" #include "py/misc.h" #include "py/asmbase.h" #if MICROPY_EMIT_MACHINE_CODE void mp_asm_base_init(mp_asm_base_t *as, size_t max_num_labels) { as->max_num_labels = max_num_labels; as->label_offsets = m_new(size_t, max_num_labels); } void mp_asm_base_deinit(mp_asm_base_t *as, bool free_code) { if (free_code) { MP_PLAT_FREE_EXEC(as->code_base, as->code_size); } m_del(size_t, as->label_offsets, as->max_num_labels); } void mp_asm_base_start_pass(mp_asm_base_t *as, int pass) { if (pass < MP_ASM_PASS_EMIT) { // Reset labels so we can detect backwards jumps (and verify unique assignment) memset(as->label_offsets, -1, as->max_num_labels * sizeof(size_t)); } else { // allocating executable RAM is platform specific MP_PLAT_ALLOC_EXEC(as->code_offset, (void **)&as->code_base, &as->code_size); assert(as->code_base != NULL); } as->pass = pass; as->code_offset = 0; } // all functions must go through this one to emit bytes // if as->pass < MP_ASM_PASS_EMIT, then this function just counts the number // of bytes needed and returns NULL, and callers should not store any data uint8_t *mp_asm_base_get_cur_to_write_bytes(void *as_in, size_t num_bytes_to_write) { mp_asm_base_t *as = as_in; uint8_t *c = NULL; if (as->pass == MP_ASM_PASS_EMIT) { assert(as->code_offset + num_bytes_to_write <= as->code_size); c = as->code_base + as->code_offset; } as->code_offset += num_bytes_to_write; return c; } void mp_asm_base_label_assign(mp_asm_base_t *as, size_t label) { assert(label < as->max_num_labels); if (as->pass < MP_ASM_PASS_EMIT) { // assign label offset assert(as->label_offsets[label] == (size_t)-1); as->label_offsets[label] = as->code_offset; } else { // ensure label offset has not changed from PASS_COMPUTE to PASS_EMIT assert(as->label_offsets[label] == as->code_offset); } } // align must be a multiple of 2 void mp_asm_base_align(mp_asm_base_t *as, unsigned int align) { as->code_offset = (as->code_offset + align - 1) & (~(align - 1)); } // this function assumes a little endian machine void mp_asm_base_data(mp_asm_base_t *as, unsigned int bytesize, uintptr_t val) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(as, bytesize); if (c != NULL) { for (unsigned int i = 0; i < bytesize; i++) { *c++ = val; val >>= 8; } } } #endif // MICROPY_EMIT_MACHINE_CODE #include #ifndef __IPU__ #include #endif #include #include #include "py/mpconfig.h" // wrapper around everything in this file #if MICROPY_EMIT_X64 #include "py/asmx64.h" /* all offsets are measured in multiples of 8 bytes */ #define WORD_SIZE (8) #define OPCODE_NOP (0x90) #define OPCODE_PUSH_R64 (0x50) /* +rq */ #define OPCODE_PUSH_I64 (0x68) #define OPCODE_PUSH_M64 (0xff) /* /6 */ #define OPCODE_POP_R64 (0x58) /* +rq */ #define OPCODE_RET (0xc3) #define OPCODE_MOV_I8_TO_R8 (0xb0) /* +rb */ #define OPCODE_MOV_I64_TO_R64 (0xb8) /* +rq */ #define OPCODE_MOV_I32_TO_RM32 (0xc7) #define OPCODE_MOV_R8_TO_RM8 (0x88) /* /r */ #define OPCODE_MOV_R64_TO_RM64 (0x89) /* /r */ #define OPCODE_MOV_RM64_TO_R64 (0x8b) /* /r */ #define OPCODE_MOVZX_RM8_TO_R64 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R64 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R64 (0x8d) /* /r */ #define OPCODE_AND_R64_TO_RM64 (0x21) /* /r */ #define OPCODE_OR_R64_TO_RM64 (0x09) /* /r */ #define OPCODE_XOR_R64_TO_RM64 (0x31) /* /r */ #define OPCODE_ADD_R64_TO_RM64 (0x01) /* /r */ #define OPCODE_ADD_I32_TO_RM32 (0x81) /* /0 */ #define OPCODE_ADD_I8_TO_RM32 (0x83) /* /0 */ #define OPCODE_SUB_R64_FROM_RM64 (0x29) #define OPCODE_SUB_I32_FROM_RM64 (0x81) /* /5 */ #define OPCODE_SUB_I8_FROM_RM64 (0x83) /* /5 */ // #define OPCODE_SHL_RM32_BY_I8 (0xc1) /* /4 */ // #define OPCODE_SHR_RM32_BY_I8 (0xc1) /* /5 */ // #define OPCODE_SAR_RM32_BY_I8 (0xc1) /* /7 */ #define OPCODE_SHL_RM64_CL (0xd3) /* /4 */ #define OPCODE_SHR_RM64_CL (0xd3) /* /5 */ #define OPCODE_SAR_RM64_CL (0xd3) /* /7 */ // #define OPCODE_CMP_I32_WITH_RM32 (0x81) /* /7 */ // #define OPCODE_CMP_I8_WITH_RM32 (0x83) /* /7 */ #define OPCODE_CMP_R64_WITH_RM64 (0x39) /* /r */ // #define OPCODE_CMP_RM32_WITH_R32 (0x3b) #define OPCODE_TEST_R8_WITH_RM8 (0x84) /* /r */ #define OPCODE_TEST_R64_WITH_RM64 (0x85) /* /r */ #define OPCODE_JMP_REL8 (0xeb) #define OPCODE_JMP_REL32 (0xe9) #define OPCODE_JMP_RM64 (0xff) /* /4 */ #define OPCODE_JCC_REL8 (0x70) /* | jcc type */ #define OPCODE_JCC_REL32_A (0x0f) #define OPCODE_JCC_REL32_B (0x80) /* | jcc type */ #define OPCODE_SETCC_RM8_A (0x0f) #define OPCODE_SETCC_RM8_B (0x90) /* | jcc type, /0 */ #define OPCODE_CALL_REL32 (0xe8) #define OPCODE_CALL_RM32 (0xff) /* /2 */ #define OPCODE_LEAVE (0xc9) #define MODRM_R64(x) (((x) & 0x7) << 3) #define MODRM_RM_DISP0 (0x00) #define MODRM_RM_DISP8 (0x40) #define MODRM_RM_DISP32 (0x80) #define MODRM_RM_REG (0xc0) #define MODRM_RM_R64(x) ((x) & 0x7) #define OP_SIZE_PREFIX (0x66) #define REX_PREFIX (0x40) #define REX_W (0x08) // width #define REX_R (0x04) // register #define REX_X (0x02) // index #define REX_B (0x01) // base #define REX_W_FROM_R64(r64) ((r64) >> 0 & 0x08) #define REX_R_FROM_R64(r64) ((r64) >> 1 & 0x04) #define REX_X_FROM_R64(r64) ((r64) >> 2 & 0x02) #define REX_B_FROM_R64(r64) ((r64) >> 3 & 0x01) #define IMM32_L0(x) ((x) & 0xff) #define IMM32_L1(x) (((x) >> 8) & 0xff) #define IMM32_L2(x) (((x) >> 16) & 0xff) #define IMM32_L3(x) (((x) >> 24) & 0xff) #define IMM64_L4(x) (((x) >> 32) & 0xff) #define IMM64_L5(x) (((x) >> 40) & 0xff) #define IMM64_L6(x) (((x) >> 48) & 0xff) #define IMM64_L7(x) (((x) >> 56) & 0xff) #define UNSIGNED_FIT8(x) (((x) & 0xffffffffffffff00) == 0) #define UNSIGNED_FIT32(x) (((x) & 0xffffffff00000000) == 0) #define SIGNED_FIT8(x) (((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80) static inline byte *asm_x64_get_cur_to_write_bytes(asm_x64_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); } STATIC void asm_x64_write_byte_1(asm_x64_t *as, byte b1) { byte *c = asm_x64_get_cur_to_write_bytes(as, 1); if (c != NULL) { c[0] = b1; } } STATIC void asm_x64_write_byte_2(asm_x64_t *as, byte b1, byte b2) { byte *c = asm_x64_get_cur_to_write_bytes(as, 2); if (c != NULL) { c[0] = b1; c[1] = b2; } } STATIC void asm_x64_write_byte_3(asm_x64_t *as, byte b1, byte b2, byte b3) { byte *c = asm_x64_get_cur_to_write_bytes(as, 3); if (c != NULL) { c[0] = b1; c[1] = b2; c[2] = b3; } } STATIC void asm_x64_write_word32(asm_x64_t *as, int w32) { byte *c = asm_x64_get_cur_to_write_bytes(as, 4); if (c != NULL) { c[0] = IMM32_L0(w32); c[1] = IMM32_L1(w32); c[2] = IMM32_L2(w32); c[3] = IMM32_L3(w32); } } STATIC void asm_x64_write_word64(asm_x64_t *as, int64_t w64) { byte *c = asm_x64_get_cur_to_write_bytes(as, 8); if (c != NULL) { c[0] = IMM32_L0(w64); c[1] = IMM32_L1(w64); c[2] = IMM32_L2(w64); c[3] = IMM32_L3(w64); c[4] = IMM64_L4(w64); c[5] = IMM64_L5(w64); c[6] = IMM64_L6(w64); c[7] = IMM64_L7(w64); } } /* unused STATIC void asm_x64_write_word32_to(asm_x64_t *as, int offset, int w32) { byte* c; assert(offset + 4 <= as->code_size); c = as->code_base + offset; c[0] = IMM32_L0(w32); c[1] = IMM32_L1(w32); c[2] = IMM32_L2(w32); c[3] = IMM32_L3(w32); } */ STATIC void asm_x64_write_r64_disp(asm_x64_t *as, int r64, int disp_r64, int disp_offset) { uint8_t rm_disp; if (disp_offset == 0 && (disp_r64 & 7) != ASM_X64_REG_RBP) { rm_disp = MODRM_RM_DISP0; } else if (SIGNED_FIT8(disp_offset)) { rm_disp = MODRM_RM_DISP8; } else { rm_disp = MODRM_RM_DISP32; } asm_x64_write_byte_1(as, MODRM_R64(r64) | rm_disp | MODRM_RM_R64(disp_r64)); if ((disp_r64 & 7) == ASM_X64_REG_RSP) { // Special case for rsp and r12, they need a SIB byte asm_x64_write_byte_1(as, 0x24); } if (rm_disp == MODRM_RM_DISP8) { asm_x64_write_byte_1(as, IMM32_L0(disp_offset)); } else if (rm_disp == MODRM_RM_DISP32) { asm_x64_write_word32(as, disp_offset); } } STATIC void asm_x64_generic_r64_r64(asm_x64_t *as, int dest_r64, int src_r64, int op) { asm_x64_write_byte_3(as, REX_PREFIX | REX_W | REX_R_FROM_R64(src_r64) | REX_B_FROM_R64(dest_r64), op, MODRM_R64(src_r64) | MODRM_RM_REG | MODRM_RM_R64(dest_r64)); } void asm_x64_nop(asm_x64_t *as) { asm_x64_write_byte_1(as, OPCODE_NOP); } void asm_x64_push_r64(asm_x64_t *as, int src_r64) { if (src_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_PUSH_R64 | src_r64); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_B, OPCODE_PUSH_R64 | (src_r64 & 7)); } } /* void asm_x64_push_i32(asm_x64_t *as, int src_i32) { asm_x64_write_byte_1(as, OPCODE_PUSH_I64); asm_x64_write_word32(as, src_i32); // will be sign extended to 64 bits } */ /* void asm_x64_push_disp(asm_x64_t *as, int src_r64, int src_offset) { assert(src_r64 < 8); asm_x64_write_byte_1(as, OPCODE_PUSH_M64); asm_x64_write_r64_disp(as, 6, src_r64, src_offset); } */ void asm_x64_pop_r64(asm_x64_t *as, int dest_r64) { if (dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_POP_R64 | dest_r64); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_B, OPCODE_POP_R64 | (dest_r64 & 7)); } } STATIC void asm_x64_ret(asm_x64_t *as) { asm_x64_write_byte_1(as, OPCODE_RET); } void asm_x64_mov_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_MOV_R64_TO_RM64); } void asm_x64_mov_r8_to_mem8(asm_x64_t *as, int src_r64, int dest_r64, int dest_disp) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_MOV_R8_TO_RM8); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_R_FROM_R64(src_r64) | REX_B_FROM_R64(dest_r64), OPCODE_MOV_R8_TO_RM8); } asm_x64_write_r64_disp(as, src_r64, dest_r64, dest_disp); } void asm_x64_mov_r16_to_mem16(asm_x64_t *as, int src_r64, int dest_r64, int dest_disp) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_2(as, OP_SIZE_PREFIX, OPCODE_MOV_R64_TO_RM64); } else { asm_x64_write_byte_3(as, OP_SIZE_PREFIX, REX_PREFIX | REX_R_FROM_R64(src_r64) | REX_B_FROM_R64(dest_r64), OPCODE_MOV_R64_TO_RM64); } asm_x64_write_r64_disp(as, src_r64, dest_r64, dest_disp); } void asm_x64_mov_r32_to_mem32(asm_x64_t *as, int src_r64, int dest_r64, int dest_disp) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_MOV_R64_TO_RM64); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_R_FROM_R64(src_r64) | REX_B_FROM_R64(dest_r64), OPCODE_MOV_R64_TO_RM64); } asm_x64_write_r64_disp(as, src_r64, dest_r64, dest_disp); } void asm_x64_mov_r64_to_mem64(asm_x64_t *as, int src_r64, int dest_r64, int dest_disp) { // use REX prefix for 64 bit operation asm_x64_write_byte_2(as, REX_PREFIX | REX_W | REX_R_FROM_R64(src_r64) | REX_B_FROM_R64(dest_r64), OPCODE_MOV_R64_TO_RM64); asm_x64_write_r64_disp(as, src_r64, dest_r64, dest_disp); } void asm_x64_mov_mem8_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM8_TO_R64); } else { asm_x64_write_byte_3(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), 0x0f, OPCODE_MOVZX_RM8_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } void asm_x64_mov_mem16_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM16_TO_R64); } else { asm_x64_write_byte_3(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), 0x0f, OPCODE_MOVZX_RM16_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } void asm_x64_mov_mem32_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_MOV_RM64_TO_R64); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), OPCODE_MOV_RM64_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } void asm_x64_mov_mem64_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { // use REX prefix for 64 bit operation asm_x64_write_byte_2(as, REX_PREFIX | REX_W | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), OPCODE_MOV_RM64_TO_R64); asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } STATIC void asm_x64_lea_disp_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { // use REX prefix for 64 bit operation assert(src_r64 < 8); assert(dest_r64 < 8); asm_x64_write_byte_2(as, REX_PREFIX | REX_W, OPCODE_LEA_MEM_TO_R64); asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } /* void asm_x64_mov_i8_to_r8(asm_x64_t *as, int src_i8, int dest_r64) { assert(dest_r64 < 8); asm_x64_write_byte_2(as, OPCODE_MOV_I8_TO_R8 | dest_r64, src_i8); } */ size_t asm_x64_mov_i32_to_r64(asm_x64_t *as, int src_i32, int dest_r64) { // cpu defaults to i32 to r64, with zero extension if (dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_MOV_I64_TO_R64 | dest_r64); } else { asm_x64_write_byte_2(as, REX_PREFIX | REX_B, OPCODE_MOV_I64_TO_R64 | (dest_r64 & 7)); } size_t loc = mp_asm_base_get_code_pos(&as->base); asm_x64_write_word32(as, src_i32); return loc; } void asm_x64_mov_i64_to_r64(asm_x64_t *as, int64_t src_i64, int dest_r64) { // cpu defaults to i32 to r64 // to mov i64 to r64 need to use REX prefix asm_x64_write_byte_2(as, REX_PREFIX | REX_W | (dest_r64 < 8 ? 0 : REX_B), OPCODE_MOV_I64_TO_R64 | (dest_r64 & 7)); asm_x64_write_word64(as, src_i64); } void asm_x64_mov_i64_to_r64_optimised(asm_x64_t *as, int64_t src_i64, int dest_r64) { // TODO use movzx, movsx if possible if (UNSIGNED_FIT32(src_i64)) { // 5 bytes asm_x64_mov_i32_to_r64(as, src_i64 & 0xffffffff, dest_r64); } else { // 10 bytes asm_x64_mov_i64_to_r64(as, src_i64, dest_r64); } } void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_AND_R64_TO_RM64); } void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_OR_R64_TO_RM64); } void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_XOR_R64_TO_RM64); } void asm_x64_shl_r64_cl(asm_x64_t *as, int dest_r64) { asm_x64_generic_r64_r64(as, dest_r64, 4, OPCODE_SHL_RM64_CL); } void asm_x64_shr_r64_cl(asm_x64_t *as, int dest_r64) { asm_x64_generic_r64_r64(as, dest_r64, 5, OPCODE_SHR_RM64_CL); } void asm_x64_sar_r64_cl(asm_x64_t *as, int dest_r64) { asm_x64_generic_r64_r64(as, dest_r64, 7, OPCODE_SAR_RM64_CL); } void asm_x64_add_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_ADD_R64_TO_RM64); } void asm_x64_sub_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_SUB_R64_FROM_RM64); } void asm_x64_mul_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { // imul reg64, reg/mem64 -- 0x0f 0xaf /r asm_x64_write_byte_1(as, REX_PREFIX | REX_W | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64)); asm_x64_write_byte_3(as, 0x0f, 0xaf, MODRM_R64(dest_r64) | MODRM_RM_REG | MODRM_RM_R64(src_r64)); } /* void asm_x64_sub_i32_from_r32(asm_x64_t *as, int src_i32, int dest_r32) { if (SIGNED_FIT8(src_i32)) { // defaults to 32 bit operation asm_x64_write_byte_2(as, OPCODE_SUB_I8_FROM_RM64, MODRM_R64(5) | MODRM_RM_REG | MODRM_RM_R64(dest_r32)); asm_x64_write_byte_1(as, src_i32 & 0xff); } else { // defaults to 32 bit operation asm_x64_write_byte_2(as, OPCODE_SUB_I32_FROM_RM64, MODRM_R64(5) | MODRM_RM_REG | MODRM_RM_R64(dest_r32)); asm_x64_write_word32(as, src_i32); } } */ STATIC void asm_x64_sub_r64_i32(asm_x64_t *as, int dest_r64, int src_i32) { assert(dest_r64 < 8); if (SIGNED_FIT8(src_i32)) { // use REX prefix for 64 bit operation asm_x64_write_byte_3(as, REX_PREFIX | REX_W, OPCODE_SUB_I8_FROM_RM64, MODRM_R64(5) | MODRM_RM_REG | MODRM_RM_R64(dest_r64)); asm_x64_write_byte_1(as, src_i32 & 0xff); } else { // use REX prefix for 64 bit operation asm_x64_write_byte_3(as, REX_PREFIX | REX_W, OPCODE_SUB_I32_FROM_RM64, MODRM_R64(5) | MODRM_RM_REG | MODRM_RM_R64(dest_r64)); asm_x64_write_word32(as, src_i32); } } /* void asm_x64_shl_r32_by_imm(asm_x64_t *as, int r32, int imm) { asm_x64_write_byte_2(as, OPCODE_SHL_RM32_BY_I8, MODRM_R64(4) | MODRM_RM_REG | MODRM_RM_R64(r32)); asm_x64_write_byte_1(as, imm); } void asm_x64_shr_r32_by_imm(asm_x64_t *as, int r32, int imm) { asm_x64_write_byte_2(as, OPCODE_SHR_RM32_BY_I8, MODRM_R64(5) | MODRM_RM_REG | MODRM_RM_R64(r32)); asm_x64_write_byte_1(as, imm); } void asm_x64_sar_r32_by_imm(asm_x64_t *as, int r32, int imm) { asm_x64_write_byte_2(as, OPCODE_SAR_RM32_BY_I8, MODRM_R64(7) | MODRM_RM_REG | MODRM_RM_R64(r32)); asm_x64_write_byte_1(as, imm); } */ void asm_x64_cmp_r64_with_r64(asm_x64_t *as, int src_r64_a, int src_r64_b) { asm_x64_generic_r64_r64(as, src_r64_b, src_r64_a, OPCODE_CMP_R64_WITH_RM64); } /* void asm_x64_cmp_i32_with_r32(asm_x64_t *as, int src_i32, int src_r32) { if (SIGNED_FIT8(src_i32)) { asm_x64_write_byte_2(as, OPCODE_CMP_I8_WITH_RM32, MODRM_R64(7) | MODRM_RM_REG | MODRM_RM_R64(src_r32)); asm_x64_write_byte_1(as, src_i32 & 0xff); } else { asm_x64_write_byte_2(as, OPCODE_CMP_I32_WITH_RM32, MODRM_R64(7) | MODRM_RM_REG | MODRM_RM_R64(src_r32)); asm_x64_write_word32(as, src_i32); } } */ void asm_x64_test_r8_with_r8(asm_x64_t *as, int src_r64_a, int src_r64_b) { assert(src_r64_a < 8); assert(src_r64_b < 8); asm_x64_write_byte_2(as, OPCODE_TEST_R8_WITH_RM8, MODRM_R64(src_r64_a) | MODRM_RM_REG | MODRM_RM_R64(src_r64_b)); } void asm_x64_test_r64_with_r64(asm_x64_t *as, int src_r64_a, int src_r64_b) { asm_x64_generic_r64_r64(as, src_r64_b, src_r64_a, OPCODE_TEST_R64_WITH_RM64); } void asm_x64_setcc_r8(asm_x64_t *as, int jcc_type, int dest_r8) { assert(dest_r8 < 8); asm_x64_write_byte_3(as, OPCODE_SETCC_RM8_A, OPCODE_SETCC_RM8_B | jcc_type, MODRM_R64(0) | MODRM_RM_REG | MODRM_RM_R64(dest_r8)); } void asm_x64_jmp_reg(asm_x64_t *as, int src_r64) { assert(src_r64 < 8); asm_x64_write_byte_2(as, OPCODE_JMP_RM64, MODRM_R64(4) | MODRM_RM_REG | MODRM_RM_R64(src_r64)); } STATIC mp_uint_t get_label_dest(asm_x64_t *as, mp_uint_t label) { assert(label < as->base.max_num_labels); return as->base.label_offsets[label]; } void asm_x64_jmp_label(asm_x64_t *as, mp_uint_t label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; if (dest != (mp_uint_t)-1 && rel < 0) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 8 bit relative jump rel -= 2; if (SIGNED_FIT8(rel)) { asm_x64_write_byte_2(as, OPCODE_JMP_REL8, rel & 0xff); } else { rel += 2; goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: rel -= 5; asm_x64_write_byte_1(as, OPCODE_JMP_REL32); asm_x64_write_word32(as, rel); } } void asm_x64_jcc_label(asm_x64_t *as, int jcc_type, mp_uint_t label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; if (dest != (mp_uint_t)-1 && rel < 0) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 8 bit relative jump rel -= 2; if (SIGNED_FIT8(rel)) { asm_x64_write_byte_2(as, OPCODE_JCC_REL8 | jcc_type, rel & 0xff); } else { rel += 2; goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: rel -= 6; asm_x64_write_byte_2(as, OPCODE_JCC_REL32_A, OPCODE_JCC_REL32_B | jcc_type); asm_x64_write_word32(as, rel); } } void asm_x64_entry(asm_x64_t *as, int num_locals) { assert(num_locals >= 0); asm_x64_push_r64(as, ASM_X64_REG_RBP); asm_x64_push_r64(as, ASM_X64_REG_RBX); asm_x64_push_r64(as, ASM_X64_REG_R12); asm_x64_push_r64(as, ASM_X64_REG_R13); num_locals |= 1; // make it odd so stack is aligned on 16 byte boundary asm_x64_sub_r64_i32(as, ASM_X64_REG_RSP, num_locals * WORD_SIZE); as->num_locals = num_locals; } void asm_x64_exit(asm_x64_t *as) { asm_x64_sub_r64_i32(as, ASM_X64_REG_RSP, -as->num_locals * WORD_SIZE); asm_x64_pop_r64(as, ASM_X64_REG_R13); asm_x64_pop_r64(as, ASM_X64_REG_R12); asm_x64_pop_r64(as, ASM_X64_REG_RBX); asm_x64_pop_r64(as, ASM_X64_REG_RBP); asm_x64_ret(as); } // locals: // - stored on the stack in ascending order // - numbered 0 through as->num_locals-1 // - RSP points to the first local // // | RSP // v // l0 l1 l2 ... l(n-1) // ^ ^ // | low address | high address in RAM // STATIC int asm_x64_local_offset_from_rsp(asm_x64_t *as, int local_num) { (void)as; // Stack is full descending, RSP points to local0 return local_num * WORD_SIZE; } void asm_x64_mov_local_to_r64(asm_x64_t *as, int src_local_num, int dest_r64) { asm_x64_mov_mem64_to_r64(as, ASM_X64_REG_RSP, asm_x64_local_offset_from_rsp(as, src_local_num), dest_r64); } void asm_x64_mov_r64_to_local(asm_x64_t *as, int src_r64, int dest_local_num) { asm_x64_mov_r64_to_mem64(as, src_r64, ASM_X64_REG_RSP, asm_x64_local_offset_from_rsp(as, dest_local_num)); } void asm_x64_mov_local_addr_to_r64(asm_x64_t *as, int local_num, int dest_r64) { int offset = asm_x64_local_offset_from_rsp(as, local_num); if (offset == 0) { asm_x64_mov_r64_r64(as, dest_r64, ASM_X64_REG_RSP); } else { asm_x64_lea_disp_to_r64(as, ASM_X64_REG_RSP, offset, dest_r64); } } void asm_x64_mov_reg_pcrel(asm_x64_t *as, int dest_r64, mp_uint_t label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - (as->base.code_offset + 7); asm_x64_write_byte_3(as, REX_PREFIX | REX_W | REX_R_FROM_R64(dest_r64), OPCODE_LEA_MEM_TO_R64, MODRM_R64(dest_r64) | MODRM_RM_R64(5)); asm_x64_write_word32(as, rel); } /* void asm_x64_push_local(asm_x64_t *as, int local_num) { asm_x64_push_disp(as, ASM_X64_REG_RSP, asm_x64_local_offset_from_rsp(as, local_num)); } void asm_x64_push_local_addr(asm_x64_t *as, int local_num, int temp_r64) { asm_x64_mov_r64_r64(as, temp_r64, ASM_X64_REG_RSP); asm_x64_add_i32_to_r32(as, asm_x64_local_offset_from_rsp(as, local_num), temp_r64); asm_x64_push_r64(as, temp_r64); } */ /* can't use these because code might be relocated when resized void asm_x64_call(asm_x64_t *as, void* func) { asm_x64_sub_i32_from_r32(as, 8, ASM_X64_REG_RSP); asm_x64_write_byte_1(as, OPCODE_CALL_REL32); asm_x64_write_word32(as, func - (void*)(as->code_cur + 4)); asm_x64_mov_r64_r64(as, ASM_X64_REG_RSP, ASM_X64_REG_RBP); } void asm_x64_call_i1(asm_x64_t *as, void* func, int i1) { asm_x64_sub_i32_from_r32(as, 8, ASM_X64_REG_RSP); asm_x64_sub_i32_from_r32(as, 12, ASM_X64_REG_RSP); asm_x64_push_i32(as, i1); asm_x64_write_byte_1(as, OPCODE_CALL_REL32); asm_x64_write_word32(as, func - (void*)(as->code_cur + 4)); asm_x64_add_i32_to_r32(as, 16, ASM_X64_REG_RSP); asm_x64_mov_r64_r64(as, ASM_X64_REG_RSP, ASM_X64_REG_RBP); } */ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r64) { assert(temp_r64 < 8); asm_x64_mov_mem64_to_r64(as, ASM_X64_REG_FUN_TABLE, fun_id * WORD_SIZE, temp_r64); asm_x64_write_byte_2(as, OPCODE_CALL_RM32, MODRM_R64(2) | MODRM_RM_REG | MODRM_RM_R64(temp_r64)); } #endif // MICROPY_EMIT_X64 // x64 specific stuff #include "py/mpconfig.h" #if MICROPY_EMIT_X64 // This is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #include "py/asmx64.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (5) // rbx #define NLR_BUF_IDX_LOCAL_2 (6) // r12 #define NLR_BUF_IDX_LOCAL_3 (7) // r13 #define N_X64 (1) #define EXPORT_FUN(name) emit_native_x64_##name #include "py/emitnative.c" #endif #include #ifndef __IPU__ #include #endif #include #include #include "py/mpconfig.h" // wrapper around everything in this file #if MICROPY_EMIT_X86 #include "py/asmx86.h" /* all offsets are measured in multiples of 4 bytes */ #define WORD_SIZE (4) #define OPCODE_NOP (0x90) #define OPCODE_PUSH_R32 (0x50) // #define OPCODE_PUSH_I32 (0x68) // #define OPCODE_PUSH_M32 (0xff) /* /6 */ #define OPCODE_POP_R32 (0x58) #define OPCODE_RET (0xc3) // #define OPCODE_MOV_I8_TO_R8 (0xb0) /* +rb */ #define OPCODE_MOV_I32_TO_R32 (0xb8) // #define OPCODE_MOV_I32_TO_RM32 (0xc7) #define OPCODE_MOV_R8_TO_RM8 (0x88) /* /r */ #define OPCODE_MOV_R32_TO_RM32 (0x89) /* /r */ #define OPCODE_MOV_RM32_TO_R32 (0x8b) /* /r */ #define OPCODE_MOVZX_RM8_TO_R32 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R32 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R32 (0x8d) /* /r */ #define OPCODE_AND_R32_TO_RM32 (0x21) /* /r */ #define OPCODE_OR_R32_TO_RM32 (0x09) /* /r */ #define OPCODE_XOR_R32_TO_RM32 (0x31) /* /r */ #define OPCODE_ADD_R32_TO_RM32 (0x01) #define OPCODE_ADD_I32_TO_RM32 (0x81) /* /0 */ #define OPCODE_ADD_I8_TO_RM32 (0x83) /* /0 */ #define OPCODE_SUB_R32_FROM_RM32 (0x29) #define OPCODE_SUB_I32_FROM_RM32 (0x81) /* /5 */ #define OPCODE_SUB_I8_FROM_RM32 (0x83) /* /5 */ // #define OPCODE_SHL_RM32_BY_I8 (0xc1) /* /4 */ // #define OPCODE_SHR_RM32_BY_I8 (0xc1) /* /5 */ // #define OPCODE_SAR_RM32_BY_I8 (0xc1) /* /7 */ #define OPCODE_SHL_RM32_CL (0xd3) /* /4 */ #define OPCODE_SHR_RM32_CL (0xd3) /* /5 */ #define OPCODE_SAR_RM32_CL (0xd3) /* /7 */ // #define OPCODE_CMP_I32_WITH_RM32 (0x81) /* /7 */ // #define OPCODE_CMP_I8_WITH_RM32 (0x83) /* /7 */ #define OPCODE_CMP_R32_WITH_RM32 (0x39) // #define OPCODE_CMP_RM32_WITH_R32 (0x3b) #define OPCODE_TEST_R8_WITH_RM8 (0x84) /* /r */ #define OPCODE_TEST_R32_WITH_RM32 (0x85) /* /r */ #define OPCODE_JMP_REL8 (0xeb) #define OPCODE_JMP_REL32 (0xe9) #define OPCODE_JMP_RM32 (0xff) /* /4 */ #define OPCODE_JCC_REL8 (0x70) /* | jcc type */ #define OPCODE_JCC_REL32_A (0x0f) #define OPCODE_JCC_REL32_B (0x80) /* | jcc type */ #define OPCODE_SETCC_RM8_A (0x0f) #define OPCODE_SETCC_RM8_B (0x90) /* | jcc type, /0 */ #define OPCODE_CALL_REL32 (0xe8) #define OPCODE_CALL_RM32 (0xff) /* /2 */ #define OPCODE_LEAVE (0xc9) #define MODRM_R32(x) ((x) << 3) #define MODRM_RM_DISP0 (0x00) #define MODRM_RM_DISP8 (0x40) #define MODRM_RM_DISP32 (0x80) #define MODRM_RM_REG (0xc0) #define MODRM_RM_R32(x) (x) #define OP_SIZE_PREFIX (0x66) #define IMM32_L0(x) ((x) & 0xff) #define IMM32_L1(x) (((x) >> 8) & 0xff) #define IMM32_L2(x) (((x) >> 16) & 0xff) #define IMM32_L3(x) (((x) >> 24) & 0xff) #define SIGNED_FIT8(x) (((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80) STATIC void asm_x86_write_byte_1(asm_x86_t *as, byte b1) { byte *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 1); if (c != NULL) { c[0] = b1; } } STATIC void asm_x86_write_byte_2(asm_x86_t *as, byte b1, byte b2) { byte *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 2); if (c != NULL) { c[0] = b1; c[1] = b2; } } STATIC void asm_x86_write_byte_3(asm_x86_t *as, byte b1, byte b2, byte b3) { byte *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 3); if (c != NULL) { c[0] = b1; c[1] = b2; c[2] = b3; } } STATIC void asm_x86_write_word32(asm_x86_t *as, int w32) { byte *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 4); if (c != NULL) { c[0] = IMM32_L0(w32); c[1] = IMM32_L1(w32); c[2] = IMM32_L2(w32); c[3] = IMM32_L3(w32); } } STATIC void asm_x86_write_r32_disp(asm_x86_t *as, int r32, int disp_r32, int disp_offset) { uint8_t rm_disp; if (disp_offset == 0 && disp_r32 != ASM_X86_REG_EBP) { rm_disp = MODRM_RM_DISP0; } else if (SIGNED_FIT8(disp_offset)) { rm_disp = MODRM_RM_DISP8; } else { rm_disp = MODRM_RM_DISP32; } asm_x86_write_byte_1(as, MODRM_R32(r32) | rm_disp | MODRM_RM_R32(disp_r32)); if (disp_r32 == ASM_X86_REG_ESP) { // Special case for esp, it needs a SIB byte asm_x86_write_byte_1(as, 0x24); } if (rm_disp == MODRM_RM_DISP8) { asm_x86_write_byte_1(as, IMM32_L0(disp_offset)); } else if (rm_disp == MODRM_RM_DISP32) { asm_x86_write_word32(as, disp_offset); } } STATIC void asm_x86_generic_r32_r32(asm_x86_t *as, int dest_r32, int src_r32, int op) { asm_x86_write_byte_2(as, op, MODRM_R32(src_r32) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); } #if 0 STATIC void asm_x86_nop(asm_x86_t *as) { asm_x86_write_byte_1(as, OPCODE_NOP); } #endif STATIC void asm_x86_push_r32(asm_x86_t *as, int src_r32) { asm_x86_write_byte_1(as, OPCODE_PUSH_R32 | src_r32); } #if 0 void asm_x86_push_i32(asm_x86_t *as, int src_i32) { asm_x86_write_byte_1(as, OPCODE_PUSH_I32); asm_x86_write_word32(as, src_i32); } void asm_x86_push_disp(asm_x86_t *as, int src_r32, int src_offset) { asm_x86_write_byte_1(as, OPCODE_PUSH_M32); asm_x86_write_r32_disp(as, 6, src_r32, src_offset); } #endif STATIC void asm_x86_pop_r32(asm_x86_t *as, int dest_r32) { asm_x86_write_byte_1(as, OPCODE_POP_R32 | dest_r32); } STATIC void asm_x86_ret(asm_x86_t *as) { asm_x86_write_byte_1(as, OPCODE_RET); } void asm_x86_mov_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_MOV_R32_TO_RM32); } void asm_x86_mov_r8_to_mem8(asm_x86_t *as, int src_r32, int dest_r32, int dest_disp) { asm_x86_write_byte_1(as, OPCODE_MOV_R8_TO_RM8); asm_x86_write_r32_disp(as, src_r32, dest_r32, dest_disp); } void asm_x86_mov_r16_to_mem16(asm_x86_t *as, int src_r32, int dest_r32, int dest_disp) { asm_x86_write_byte_2(as, OP_SIZE_PREFIX, OPCODE_MOV_R32_TO_RM32); asm_x86_write_r32_disp(as, src_r32, dest_r32, dest_disp); } void asm_x86_mov_r32_to_mem32(asm_x86_t *as, int src_r32, int dest_r32, int dest_disp) { asm_x86_write_byte_1(as, OPCODE_MOV_R32_TO_RM32); asm_x86_write_r32_disp(as, src_r32, dest_r32, dest_disp); } void asm_x86_mov_mem8_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32) { asm_x86_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM8_TO_R32); asm_x86_write_r32_disp(as, dest_r32, src_r32, src_disp); } void asm_x86_mov_mem16_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32) { asm_x86_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM16_TO_R32); asm_x86_write_r32_disp(as, dest_r32, src_r32, src_disp); } void asm_x86_mov_mem32_to_r32(asm_x86_t *as, int src_r32, int src_disp, int dest_r32) { asm_x86_write_byte_1(as, OPCODE_MOV_RM32_TO_R32); asm_x86_write_r32_disp(as, dest_r32, src_r32, src_disp); } STATIC void asm_x86_lea_disp_to_r32(asm_x86_t *as, int src_r32, int src_disp, int dest_r32) { asm_x86_write_byte_1(as, OPCODE_LEA_MEM_TO_R32); asm_x86_write_r32_disp(as, dest_r32, src_r32, src_disp); } #if 0 void asm_x86_mov_i8_to_r8(asm_x86_t *as, int src_i8, int dest_r32) { asm_x86_write_byte_2(as, OPCODE_MOV_I8_TO_R8 | dest_r32, src_i8); } #endif size_t asm_x86_mov_i32_to_r32(asm_x86_t *as, int32_t src_i32, int dest_r32) { asm_x86_write_byte_1(as, OPCODE_MOV_I32_TO_R32 | dest_r32); size_t loc = mp_asm_base_get_code_pos(&as->base); asm_x86_write_word32(as, src_i32); return loc; } void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_AND_R32_TO_RM32); } void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_OR_R32_TO_RM32); } void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_XOR_R32_TO_RM32); } void asm_x86_shl_r32_cl(asm_x86_t *as, int dest_r32) { asm_x86_generic_r32_r32(as, dest_r32, 4, OPCODE_SHL_RM32_CL); } void asm_x86_shr_r32_cl(asm_x86_t *as, int dest_r32) { asm_x86_generic_r32_r32(as, dest_r32, 5, OPCODE_SHR_RM32_CL); } void asm_x86_sar_r32_cl(asm_x86_t *as, int dest_r32) { asm_x86_generic_r32_r32(as, dest_r32, 7, OPCODE_SAR_RM32_CL); } void asm_x86_add_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_ADD_R32_TO_RM32); } STATIC void asm_x86_add_i32_to_r32(asm_x86_t *as, int src_i32, int dest_r32) { if (SIGNED_FIT8(src_i32)) { asm_x86_write_byte_2(as, OPCODE_ADD_I8_TO_RM32, MODRM_R32(0) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); asm_x86_write_byte_1(as, src_i32 & 0xff); } else { asm_x86_write_byte_2(as, OPCODE_ADD_I32_TO_RM32, MODRM_R32(0) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); asm_x86_write_word32(as, src_i32); } } void asm_x86_sub_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_SUB_R32_FROM_RM32); } STATIC void asm_x86_sub_r32_i32(asm_x86_t *as, int dest_r32, int src_i32) { if (SIGNED_FIT8(src_i32)) { // defaults to 32 bit operation asm_x86_write_byte_2(as, OPCODE_SUB_I8_FROM_RM32, MODRM_R32(5) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); asm_x86_write_byte_1(as, src_i32 & 0xff); } else { // defaults to 32 bit operation asm_x86_write_byte_2(as, OPCODE_SUB_I32_FROM_RM32, MODRM_R32(5) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); asm_x86_write_word32(as, src_i32); } } void asm_x86_mul_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { // imul reg32, reg/mem32 -- 0x0f 0xaf /r asm_x86_write_byte_3(as, 0x0f, 0xaf, MODRM_R32(dest_r32) | MODRM_RM_REG | MODRM_RM_R32(src_r32)); } #if 0 /* shifts not tested */ void asm_x86_shl_r32_by_imm(asm_x86_t *as, int r32, int imm) { asm_x86_write_byte_2(as, OPCODE_SHL_RM32_BY_I8, MODRM_R32(4) | MODRM_RM_REG | MODRM_RM_R32(r32)); asm_x86_write_byte_1(as, imm); } void asm_x86_shr_r32_by_imm(asm_x86_t *as, int r32, int imm) { asm_x86_write_byte_2(as, OPCODE_SHR_RM32_BY_I8, MODRM_R32(5) | MODRM_RM_REG | MODRM_RM_R32(r32)); asm_x86_write_byte_1(as, imm); } void asm_x86_sar_r32_by_imm(asm_x86_t *as, int r32, int imm) { asm_x86_write_byte_2(as, OPCODE_SAR_RM32_BY_I8, MODRM_R32(7) | MODRM_RM_REG | MODRM_RM_R32(r32)); asm_x86_write_byte_1(as, imm); } #endif void asm_x86_cmp_r32_with_r32(asm_x86_t *as, int src_r32_a, int src_r32_b) { asm_x86_generic_r32_r32(as, src_r32_b, src_r32_a, OPCODE_CMP_R32_WITH_RM32); } #if 0 void asm_x86_cmp_i32_with_r32(asm_x86_t *as, int src_i32, int src_r32) { if (SIGNED_FIT8(src_i32)) { asm_x86_write_byte_2(as, OPCODE_CMP_I8_WITH_RM32, MODRM_R32(7) | MODRM_RM_REG | MODRM_RM_R32(src_r32)); asm_x86_write_byte_1(as, src_i32 & 0xff); } else { asm_x86_write_byte_2(as, OPCODE_CMP_I32_WITH_RM32, MODRM_R32(7) | MODRM_RM_REG | MODRM_RM_R32(src_r32)); asm_x86_write_word32(as, src_i32); } } #endif void asm_x86_test_r8_with_r8(asm_x86_t *as, int src_r32_a, int src_r32_b) { asm_x86_write_byte_2(as, OPCODE_TEST_R8_WITH_RM8, MODRM_R32(src_r32_a) | MODRM_RM_REG | MODRM_RM_R32(src_r32_b)); } void asm_x86_test_r32_with_r32(asm_x86_t *as, int src_r32_a, int src_r32_b) { asm_x86_generic_r32_r32(as, src_r32_b, src_r32_a, OPCODE_TEST_R32_WITH_RM32); } void asm_x86_setcc_r8(asm_x86_t *as, mp_uint_t jcc_type, int dest_r8) { asm_x86_write_byte_3(as, OPCODE_SETCC_RM8_A, OPCODE_SETCC_RM8_B | jcc_type, MODRM_R32(0) | MODRM_RM_REG | MODRM_RM_R32(dest_r8)); } void asm_x86_jmp_reg(asm_x86_t *as, int src_r32) { asm_x86_write_byte_2(as, OPCODE_JMP_RM32, MODRM_R32(4) | MODRM_RM_REG | MODRM_RM_R32(src_r32)); } STATIC mp_uint_t get_label_dest(asm_x86_t *as, mp_uint_t label) { assert(label < as->base.max_num_labels); return as->base.label_offsets[label]; } void asm_x86_jmp_label(asm_x86_t *as, mp_uint_t label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; if (dest != (mp_uint_t)-1 && rel < 0) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 8 bit relative jump rel -= 2; if (SIGNED_FIT8(rel)) { asm_x86_write_byte_2(as, OPCODE_JMP_REL8, rel & 0xff); } else { rel += 2; goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: rel -= 5; asm_x86_write_byte_1(as, OPCODE_JMP_REL32); asm_x86_write_word32(as, rel); } } void asm_x86_jcc_label(asm_x86_t *as, mp_uint_t jcc_type, mp_uint_t label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; if (dest != (mp_uint_t)-1 && rel < 0) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 8 bit relative jump rel -= 2; if (SIGNED_FIT8(rel)) { asm_x86_write_byte_2(as, OPCODE_JCC_REL8 | jcc_type, rel & 0xff); } else { rel += 2; goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: rel -= 6; asm_x86_write_byte_2(as, OPCODE_JCC_REL32_A, OPCODE_JCC_REL32_B | jcc_type); asm_x86_write_word32(as, rel); } } void asm_x86_entry(asm_x86_t *as, int num_locals) { assert(num_locals >= 0); asm_x86_push_r32(as, ASM_X86_REG_EBP); asm_x86_push_r32(as, ASM_X86_REG_EBX); asm_x86_push_r32(as, ASM_X86_REG_ESI); asm_x86_push_r32(as, ASM_X86_REG_EDI); num_locals |= 3; // make it odd so stack is aligned on 16 byte boundary asm_x86_sub_r32_i32(as, ASM_X86_REG_ESP, num_locals * WORD_SIZE); as->num_locals = num_locals; } void asm_x86_exit(asm_x86_t *as) { asm_x86_sub_r32_i32(as, ASM_X86_REG_ESP, -as->num_locals * WORD_SIZE); asm_x86_pop_r32(as, ASM_X86_REG_EDI); asm_x86_pop_r32(as, ASM_X86_REG_ESI); asm_x86_pop_r32(as, ASM_X86_REG_EBX); asm_x86_pop_r32(as, ASM_X86_REG_EBP); asm_x86_ret(as); } STATIC int asm_x86_arg_offset_from_esp(asm_x86_t *as, size_t arg_num) { // Above esp are: locals, 4 saved registers, return eip, arguments return (as->num_locals + 4 + 1 + arg_num) * WORD_SIZE; } #if 0 void asm_x86_push_arg(asm_x86_t *as, int src_arg_num) { asm_x86_push_disp(as, ASM_X86_REG_ESP, asm_x86_arg_offset_from_esp(as, src_arg_num)); } #endif void asm_x86_mov_arg_to_r32(asm_x86_t *as, int src_arg_num, int dest_r32) { asm_x86_mov_mem32_to_r32(as, ASM_X86_REG_ESP, asm_x86_arg_offset_from_esp(as, src_arg_num), dest_r32); } #if 0 void asm_x86_mov_r32_to_arg(asm_x86_t *as, int src_r32, int dest_arg_num) { asm_x86_mov_r32_to_mem32(as, src_r32, ASM_X86_REG_ESP, asm_x86_arg_offset_from_esp(as, dest_arg_num)); } #endif // locals: // - stored on the stack in ascending order // - numbered 0 through as->num_locals-1 // - ESP points to the first local // // | ESP // v // l0 l1 l2 ... l(n-1) // ^ ^ // | low address | high address in RAM // STATIC int asm_x86_local_offset_from_esp(asm_x86_t *as, int local_num) { (void)as; // Stack is full descending, ESP points to local0 return local_num * WORD_SIZE; } void asm_x86_mov_local_to_r32(asm_x86_t *as, int src_local_num, int dest_r32) { asm_x86_mov_mem32_to_r32(as, ASM_X86_REG_ESP, asm_x86_local_offset_from_esp(as, src_local_num), dest_r32); } void asm_x86_mov_r32_to_local(asm_x86_t *as, int src_r32, int dest_local_num) { asm_x86_mov_r32_to_mem32(as, src_r32, ASM_X86_REG_ESP, asm_x86_local_offset_from_esp(as, dest_local_num)); } void asm_x86_mov_local_addr_to_r32(asm_x86_t *as, int local_num, int dest_r32) { int offset = asm_x86_local_offset_from_esp(as, local_num); if (offset == 0) { asm_x86_mov_r32_r32(as, dest_r32, ASM_X86_REG_ESP); } else { asm_x86_lea_disp_to_r32(as, ASM_X86_REG_ESP, offset, dest_r32); } } void asm_x86_mov_reg_pcrel(asm_x86_t *as, int dest_r32, mp_uint_t label) { asm_x86_write_byte_1(as, OPCODE_CALL_REL32); asm_x86_write_word32(as, 0); mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; asm_x86_pop_r32(as, dest_r32); // PC rel is usually a forward reference, so need to assume it's large asm_x86_write_byte_2(as, OPCODE_ADD_I32_TO_RM32, MODRM_R32(0) | MODRM_RM_REG | MODRM_RM_R32(dest_r32)); asm_x86_write_word32(as, rel); } #if 0 void asm_x86_push_local(asm_x86_t *as, int local_num) { asm_x86_push_disp(as, ASM_X86_REG_ESP, asm_x86_local_offset_from_esp(as, local_num)); } void asm_x86_push_local_addr(asm_x86_t *as, int local_num, int temp_r32) { asm_x86_mov_r32_r32(as, temp_r32, ASM_X86_REG_ESP); asm_x86_add_i32_to_r32(as, asm_x86_local_offset_from_esp(as, local_num), temp_r32); asm_x86_push_r32(as, temp_r32); } #endif void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r32) { assert(n_args <= 4); // Align stack on 16-byte boundary during the call unsigned int align = ((n_args + 3) & ~3) - n_args; if (align) { asm_x86_sub_r32_i32(as, ASM_X86_REG_ESP, align * WORD_SIZE); } if (n_args > 3) { asm_x86_push_r32(as, ASM_X86_REG_ARG_4); } if (n_args > 2) { asm_x86_push_r32(as, ASM_X86_REG_ARG_3); } if (n_args > 1) { asm_x86_push_r32(as, ASM_X86_REG_ARG_2); } if (n_args > 0) { asm_x86_push_r32(as, ASM_X86_REG_ARG_1); } // Load the pointer to the function and make the call asm_x86_mov_mem32_to_r32(as, ASM_X86_REG_FUN_TABLE, fun_id * WORD_SIZE, temp_r32); asm_x86_write_byte_2(as, OPCODE_CALL_RM32, MODRM_R32(2) | MODRM_RM_REG | MODRM_RM_R32(temp_r32)); // the caller must clean up the stack if (n_args > 0) { asm_x86_add_i32_to_r32(as, (n_args + align) * WORD_SIZE, ASM_X86_REG_ESP); } } #endif // MICROPY_EMIT_X86 // x86 specific stuff #include "py/mpconfig.h" #include "py/nativeglue.h" #if MICROPY_EMIT_X86 // This is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #include "py/asmx86.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (5) // ebx #define NLR_BUF_IDX_LOCAL_2 (7) // esi #define NLR_BUF_IDX_LOCAL_3 (6) // edi // x86 needs a table to know how many args a given function has STATIC byte mp_f_n_args[MP_F_NUMBER_OF] = { [MP_F_CONVERT_OBJ_TO_NATIVE] = 2, [MP_F_CONVERT_NATIVE_TO_OBJ] = 2, [MP_F_NATIVE_SWAP_GLOBALS] = 1, [MP_F_LOAD_NAME] = 1, [MP_F_LOAD_GLOBAL] = 1, [MP_F_LOAD_BUILD_CLASS] = 0, [MP_F_LOAD_ATTR] = 2, [MP_F_LOAD_METHOD] = 3, [MP_F_LOAD_SUPER_METHOD] = 2, [MP_F_STORE_NAME] = 2, [MP_F_STORE_GLOBAL] = 2, [MP_F_STORE_ATTR] = 3, [MP_F_OBJ_SUBSCR] = 3, [MP_F_OBJ_IS_TRUE] = 1, [MP_F_UNARY_OP] = 2, [MP_F_BINARY_OP] = 3, [MP_F_BUILD_TUPLE] = 2, [MP_F_BUILD_LIST] = 2, [MP_F_BUILD_MAP] = 1, [MP_F_BUILD_SET] = 2, [MP_F_STORE_SET] = 2, [MP_F_LIST_APPEND] = 2, [MP_F_STORE_MAP] = 3, [MP_F_MAKE_FUNCTION_FROM_RAW_CODE] = 3, [MP_F_NATIVE_CALL_FUNCTION_N_KW] = 3, [MP_F_CALL_METHOD_N_KW] = 3, [MP_F_CALL_METHOD_N_KW_VAR] = 3, [MP_F_NATIVE_GETITER] = 2, [MP_F_NATIVE_ITERNEXT] = 1, [MP_F_NLR_PUSH] = 1, [MP_F_NLR_POP] = 0, [MP_F_NATIVE_RAISE] = 1, [MP_F_IMPORT_NAME] = 3, [MP_F_IMPORT_FROM] = 2, [MP_F_IMPORT_ALL] = 1, [MP_F_NEW_SLICE] = 3, [MP_F_UNPACK_SEQUENCE] = 3, [MP_F_UNPACK_EX] = 3, [MP_F_DELETE_NAME] = 1, [MP_F_DELETE_GLOBAL] = 1, [MP_F_NEW_CLOSURE] = 3, [MP_F_ARG_CHECK_NUM_SIG] = 3, [MP_F_SETUP_CODE_STATE] = 4, [MP_F_SMALL_INT_FLOOR_DIVIDE] = 2, [MP_F_SMALL_INT_MODULO] = 2, [MP_F_NATIVE_YIELD_FROM] = 3, [MP_F_SETJMP] = 1, }; #define N_X86 (1) #define EXPORT_FUN(name) emit_native_x86_##name #include "py/emitnative.c" #endif #ifndef __IPU__ #include #endif #include #include #include "py/mpconfig.h" // wrapper around everything in this file #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB #include "py/mpstate.h" #include "py/persistentcode.h" #include "py/asmthumb.h" #define UNSIGNED_FIT5(x) ((uint32_t)(x) < 32) #define UNSIGNED_FIT7(x) ((uint32_t)(x) < 128) #define UNSIGNED_FIT8(x) (((x) & 0xffffff00) == 0) #define UNSIGNED_FIT16(x) (((x) & 0xffff0000) == 0) #define SIGNED_FIT8(x) (((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80) #define SIGNED_FIT9(x) (((x) & 0xffffff00) == 0) || (((x) & 0xffffff00) == 0xffffff00) #define SIGNED_FIT12(x) (((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800) #define SIGNED_FIT23(x) (((x) & 0xffc00000) == 0) || (((x) & 0xffc00000) == 0xffc00000) #if MICROPY_EMIT_THUMB_ARMV7M // Note: these actually take an imm12 but the high-bit is not encoded here #define OP_ADD_W_RRI_HI(reg_src) (0xf200 | (reg_src)) #define OP_ADD_W_RRI_LO(reg_dest, imm11) ((imm11 << 4 & 0x7000) | reg_dest << 8 | (imm11 & 0xff)) #define OP_SUB_W_RRI_HI(reg_src) (0xf2a0 | (reg_src)) #define OP_SUB_W_RRI_LO(reg_dest, imm11) ((imm11 << 4 & 0x7000) | reg_dest << 8 | (imm11 & 0xff)) #define OP_LDR_W_HI(reg_base) (0xf8d0 | (reg_base)) #define OP_LDR_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) #endif static inline byte *asm_thumb_get_cur_to_write_bytes(asm_thumb_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); } /* STATIC void asm_thumb_write_byte_1(asm_thumb_t *as, byte b1) { byte *c = asm_thumb_get_cur_to_write_bytes(as, 1); c[0] = b1; } */ /* #define IMM32_L0(x) ((x) & 0xff) #define IMM32_L1(x) (((x) >> 8) & 0xff) #define IMM32_L2(x) (((x) >> 16) & 0xff) #define IMM32_L3(x) (((x) >> 24) & 0xff) STATIC void asm_thumb_write_word32(asm_thumb_t *as, int w32) { byte *c = asm_thumb_get_cur_to_write_bytes(as, 4); c[0] = IMM32_L0(w32); c[1] = IMM32_L1(w32); c[2] = IMM32_L2(w32); c[3] = IMM32_L3(w32); } */ // rlolist is a bit map indicating desired lo-registers #define OP_PUSH_RLIST(rlolist) (0xb400 | (rlolist)) #define OP_PUSH_RLIST_LR(rlolist) (0xb400 | 0x0100 | (rlolist)) #define OP_POP_RLIST(rlolist) (0xbc00 | (rlolist)) #define OP_POP_RLIST_PC(rlolist) (0xbc00 | 0x0100 | (rlolist)) // The number of words must fit in 7 unsigned bits #define OP_ADD_SP(num_words) (0xb000 | (num_words)) #define OP_SUB_SP(num_words) (0xb080 | (num_words)) // locals: // - stored on the stack in ascending order // - numbered 0 through num_locals-1 // - SP points to first local // // | SP // v // l0 l1 l2 ... l(n-1) // ^ ^ // | low address | high address in RAM void asm_thumb_entry(asm_thumb_t *as, int num_locals) { assert(num_locals >= 0); // If this Thumb machine code is run from ARM state then add a prelude // to switch to Thumb state for the duration of the function. #if MICROPY_DYNAMIC_COMPILER || MICROPY_EMIT_ARM || (defined(__arm__) && !defined(__thumb2__) && !defined(__thumb__)) #if MICROPY_DYNAMIC_COMPILER if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_ARMV6) #endif { asm_thumb_op32(as, 0x4010, 0xe92d); // push {r4, lr} asm_thumb_op32(as, 0xe009, 0xe28f); // add lr, pc, 8 + 1 asm_thumb_op32(as, 0xff3e, 0xe12f); // blx lr asm_thumb_op32(as, 0x4010, 0xe8bd); // pop {r4, lr} asm_thumb_op32(as, 0xff1e, 0xe12f); // bx lr } #endif // work out what to push and how many extra spaces to reserve on stack // so that we have enough for all locals and it's aligned an 8-byte boundary // we push extra regs (r1, r2, r3) to help do the stack adjustment // we probably should just always subtract from sp, since this would be more efficient // for push rlist, lowest numbered register at the lowest address uint reglist; uint stack_adjust; // don't pop r0 because it's used for return value switch (num_locals) { case 0: reglist = 0xf2; stack_adjust = 0; break; case 1: reglist = 0xf2; stack_adjust = 0; break; case 2: reglist = 0xfe; stack_adjust = 0; break; case 3: reglist = 0xfe; stack_adjust = 0; break; default: reglist = 0xfe; stack_adjust = ((num_locals - 3) + 1) & (~1); break; } asm_thumb_op16(as, OP_PUSH_RLIST_LR(reglist)); if (stack_adjust > 0) { #if MICROPY_EMIT_THUMB_ARMV7M if (UNSIGNED_FIT7(stack_adjust)) { asm_thumb_op16(as, OP_SUB_SP(stack_adjust)); } else { asm_thumb_op32(as, OP_SUB_W_RRI_HI(ASM_THUMB_REG_SP), OP_SUB_W_RRI_LO(ASM_THUMB_REG_SP, stack_adjust * 4)); } #else int adj = stack_adjust; // we don't expect the stack_adjust to be massive while (!UNSIGNED_FIT7(adj)) { asm_thumb_op16(as, OP_SUB_SP(127)); adj -= 127; } asm_thumb_op16(as, OP_SUB_SP(adj)); #endif } as->push_reglist = reglist; as->stack_adjust = stack_adjust; } void asm_thumb_exit(asm_thumb_t *as) { if (as->stack_adjust > 0) { #if MICROPY_EMIT_THUMB_ARMV7M if (UNSIGNED_FIT7(as->stack_adjust)) { asm_thumb_op16(as, OP_ADD_SP(as->stack_adjust)); } else { asm_thumb_op32(as, OP_ADD_W_RRI_HI(ASM_THUMB_REG_SP), OP_ADD_W_RRI_LO(ASM_THUMB_REG_SP, as->stack_adjust * 4)); } #else int adj = as->stack_adjust; // we don't expect the stack_adjust to be massive while (!UNSIGNED_FIT7(adj)) { asm_thumb_op16(as, OP_ADD_SP(127)); adj -= 127; } asm_thumb_op16(as, OP_ADD_SP(adj)); #endif } asm_thumb_op16(as, OP_POP_RLIST_PC(as->push_reglist)); } STATIC mp_uint_t get_label_dest(asm_thumb_t *as, uint label) { assert(label < as->base.max_num_labels); return as->base.label_offsets[label]; } void asm_thumb_op16(asm_thumb_t *as, uint op) { byte *c = asm_thumb_get_cur_to_write_bytes(as, 2); if (c != NULL) { // little endian c[0] = op; c[1] = op >> 8; } } void asm_thumb_op32(asm_thumb_t *as, uint op1, uint op2) { byte *c = asm_thumb_get_cur_to_write_bytes(as, 4); if (c != NULL) { // little endian, op1 then op2 c[0] = op1; c[1] = op1 >> 8; c[2] = op2; c[3] = op2 >> 8; } } #define OP_FORMAT_4(op, rlo_dest, rlo_src) ((op) | ((rlo_src) << 3) | (rlo_dest)) void asm_thumb_format_4(asm_thumb_t *as, uint op, uint rlo_dest, uint rlo_src) { assert(rlo_dest < ASM_THUMB_REG_R8); assert(rlo_src < ASM_THUMB_REG_R8); asm_thumb_op16(as, OP_FORMAT_4(op, rlo_dest, rlo_src)); } void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src) { uint op_lo; if (reg_src < 8) { op_lo = reg_src << 3; } else { op_lo = 0x40 | ((reg_src - 8) << 3); } if (reg_dest < 8) { op_lo |= reg_dest; } else { op_lo |= 0x80 | (reg_dest - 8); } // mov reg_dest, reg_src asm_thumb_op16(as, 0x4600 | op_lo); } #if MICROPY_EMIT_THUMB_ARMV7M // if loading lo half with movw, the i16 value will be zero extended into the r32 register! size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { assert(reg_dest < ASM_THUMB_REG_R15); size_t loc = mp_asm_base_get_code_pos(&as->base); // mov[wt] reg_dest, #i16_src asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), ((i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); return loc; } #else void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { asm_thumb_mov_rlo_i8(as, rlo_dest, (i16_src >> 8) & 0xff); asm_thumb_lsl_rlo_rlo_i5(as, rlo_dest, rlo_dest, 8); asm_thumb_add_rlo_i8(as, rlo_dest, i16_src & 0xff); } #endif #define OP_B_N(byte_offset) (0xe000 | (((byte_offset) >> 1) & 0x07ff)) bool asm_thumb_b_n_label(asm_thumb_t *as, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction asm_thumb_op16(as, OP_B_N(rel)); return as->base.pass != MP_ASM_PASS_EMIT || SIGNED_FIT12(rel); } #define OP_BCC_N(cond, byte_offset) (0xd000 | ((cond) << 8) | (((byte_offset) >> 1) & 0x00ff)) // all these bit arithmetics need coverage testing! #define OP_BCC_W_HI(cond, byte_offset) (0xf000 | ((cond) << 6) | (((byte_offset) >> 10) & 0x0400) | (((byte_offset) >> 14) & 0x003f)) #define OP_BCC_W_LO(byte_offset) (0x8000 | ((byte_offset) & 0x2000) | (((byte_offset) >> 1) & 0x0fff)) bool asm_thumb_bcc_nw_label(asm_thumb_t *as, int cond, uint label, bool wide) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction if (!wide) { asm_thumb_op16(as, OP_BCC_N(cond, rel)); return as->base.pass != MP_ASM_PASS_EMIT || SIGNED_FIT9(rel); } else { #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); return true; #else // this method should not be called for ARMV6M return false; #endif } } #define OP_BL_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BL_LO(byte_offset) (0xf800 | (((byte_offset) >> 1) & 0x07ff)) bool asm_thumb_bl_label(asm_thumb_t *as, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction asm_thumb_op32(as, OP_BL_HI(rel), OP_BL_LO(rel)); return as->base.pass != MP_ASM_PASS_EMIT || SIGNED_FIT23(rel); } size_t asm_thumb_mov_reg_i32(asm_thumb_t *as, uint reg_dest, mp_uint_t i32) { // movw, movt does it in 8 bytes // ldr [pc, #], dw does it in 6 bytes, but we might not reach to end of code for dw size_t loc = mp_asm_base_get_code_pos(&as->base); #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVT, reg_dest, i32 >> 16); #else // should only be called with lo reg for ARMV6M assert(reg_dest < ASM_THUMB_REG_R8); // sanity check that generated code is aligned assert(!as->base.code_base || !(3u & (uintptr_t)as->base.code_base)); // basically: // (nop) // ldr reg_dest, _data // b 1f // _data: .word i32 // 1: if (as->base.code_offset & 2u) { asm_thumb_op16(as, ASM_THUMB_OP_NOP); } asm_thumb_ldr_rlo_pcrel_i8(as, reg_dest, 0); asm_thumb_op16(as, OP_B_N(2)); asm_thumb_op16(as, i32 & 0xffff); asm_thumb_op16(as, i32 >> 16); #endif return loc; } void asm_thumb_mov_reg_i32_optimised(asm_thumb_t *as, uint reg_dest, int i32) { if (reg_dest < 8 && UNSIGNED_FIT8(i32)) { asm_thumb_mov_rlo_i8(as, reg_dest, i32); } else { #if MICROPY_EMIT_THUMB_ARMV7M if (UNSIGNED_FIT16(i32)) { asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); } else { asm_thumb_mov_reg_i32(as, reg_dest, i32); } #else uint rlo_dest = reg_dest; assert(rlo_dest < ASM_THUMB_REG_R8); // should never be called for ARMV6M bool negate = i32 < 0 && ((i32 + i32) & 0xffffffffu); // don't negate 0x80000000 if (negate) { i32 = -i32; } uint clz = __builtin_clz(i32); uint ctz = i32 ? __builtin_ctz(i32) : 0; assert(clz + ctz <= 32); if (clz + ctz >= 24) { asm_thumb_mov_rlo_i8(as, rlo_dest, (i32 >> ctz) & 0xff); asm_thumb_lsl_rlo_rlo_i5(as, rlo_dest, rlo_dest, ctz); } else if (UNSIGNED_FIT16(i32)) { asm_thumb_mov_rlo_i16(as, rlo_dest, i32); } else { if (negate) { // no point in negating if we're storing in 32 bit anyway negate = false; i32 = -i32; } asm_thumb_mov_reg_i32(as, rlo_dest, i32); } if (negate) { asm_thumb_neg_rlo_rlo(as, rlo_dest, rlo_dest); } #endif } } #define OP_STR_TO_SP_OFFSET(rlo_dest, word_offset) (0x9000 | ((rlo_dest) << 8) | ((word_offset) & 0x00ff)) #define OP_LDR_FROM_SP_OFFSET(rlo_dest, word_offset) (0x9800 | ((rlo_dest) << 8) | ((word_offset) & 0x00ff)) static void asm_thumb_mov_local_check(asm_thumb_t *as, int word_offset) { if (as->base.pass >= MP_ASM_PASS_EMIT) { assert(word_offset >= 0); if (!UNSIGNED_FIT8(word_offset)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("too many locals for native method")); } } } void asm_thumb_mov_local_reg(asm_thumb_t *as, int local_num, uint rlo_src) { assert(rlo_src < ASM_THUMB_REG_R8); int word_offset = local_num; asm_thumb_mov_local_check(as, word_offset); asm_thumb_op16(as, OP_STR_TO_SP_OFFSET(rlo_src, word_offset)); } void asm_thumb_mov_reg_local(asm_thumb_t *as, uint rlo_dest, int local_num) { assert(rlo_dest < ASM_THUMB_REG_R8); int word_offset = local_num; asm_thumb_mov_local_check(as, word_offset); asm_thumb_op16(as, OP_LDR_FROM_SP_OFFSET(rlo_dest, word_offset)); } #define OP_ADD_REG_SP_OFFSET(rlo_dest, word_offset) (0xa800 | ((rlo_dest) << 8) | ((word_offset) & 0x00ff)) void asm_thumb_mov_reg_local_addr(asm_thumb_t *as, uint rlo_dest, int local_num) { assert(rlo_dest < ASM_THUMB_REG_R8); int word_offset = local_num; assert(as->base.pass < MP_ASM_PASS_EMIT || word_offset >= 0); asm_thumb_op16(as, OP_ADD_REG_SP_OFFSET(rlo_dest, word_offset)); } void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel |= 1; // to stay in Thumb state when jumping to this address #if MICROPY_EMIT_THUMB_ARMV7M rel -= 4 + 4; // adjust for mov_reg_i16 and then PC+4 prefetch of add_reg_reg asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, rlo_dest, rel); // 4 bytes #else rel -= 8 + 4; // adjust for four instructions and then PC+4 prefetch of add_reg_reg // 6 bytes asm_thumb_mov_rlo_i16(as, rlo_dest, rel); // 2 bytes - not always needed, but we want to keep the size the same asm_thumb_sxth_rlo_rlo(as, rlo_dest, rlo_dest); #endif asm_thumb_add_reg_reg(as, rlo_dest, ASM_THUMB_REG_R15); // 2 bytes } #if MICROPY_EMIT_THUMB_ARMV7M static inline void asm_thumb_ldr_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { asm_thumb_op32(as, OP_LDR_W_HI(reg_base), OP_LDR_W_LO(reg_dest, word_offset * 4)); } #endif void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(word_offset)) { asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_base, word_offset); } else { #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_ldr_reg_reg_i12(as, reg_dest, reg_base, word_offset); #else word_offset -= 31; if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8) { if (UNSIGNED_FIT8(word_offset) && (word_offset < 64 || reg_dest != reg_base)) { if (word_offset < 64) { if (reg_dest != reg_base) { asm_thumb_mov_reg_reg(as, reg_dest, reg_base); } asm_thumb_add_rlo_i8(as, reg_dest, word_offset * 4); } else { asm_thumb_mov_rlo_i8(as, reg_dest, word_offset); asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, 2); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); } } else { if (reg_dest != reg_base) { asm_thumb_mov_rlo_i16(as, reg_dest, word_offset * 4); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); } else { uint reg_other = reg_dest ^ 7; asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); asm_thumb_mov_rlo_i16(as, reg_other, word_offset * 4); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); } } } else { assert(0); // should never be called for ARMV6M } asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_dest, 31); #endif } } // this could be wrong, because it should have a range of +/- 16MiB... #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) void asm_thumb_b_label(asm_thumb_t *as, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction if (dest != (mp_uint_t)-1 && rel <= -4) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 12 bit relative jump if (SIGNED_FIT12(rel)) { asm_thumb_op16(as, OP_B_N(rel)); } else { goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BW_HI(rel), OP_BW_LO(rel)); #else if (SIGNED_FIT12(rel)) { // this code path has to be the same number of instructions irrespective of rel asm_thumb_op16(as, OP_B_N(rel)); } else { asm_thumb_op16(as, ASM_THUMB_OP_NOP); if (dest != (mp_uint_t)-1) { // we have an actual branch > 12 bits; this is not handled yet mp_raise_NotImplementedError(MP_ERROR_TEXT("native method too big")); } } #endif } } void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction if (dest != (mp_uint_t)-1 && rel <= -4) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 9 bit relative jump if (SIGNED_FIT9(rel)) { asm_thumb_op16(as, OP_BCC_N(cond, rel)); } else { goto large_jump; } } else { // is a forwards jump, so need to assume it's large large_jump: #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); #else // reverse the sense of the branch to jump over a longer branch asm_thumb_op16(as, OP_BCC_N(cond ^ 1, 0)); asm_thumb_b_label(as, label); #endif } } void asm_thumb_bcc_rel9(asm_thumb_t *as, int cond, int rel) { rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction assert(SIGNED_FIT9(rel)); asm_thumb_op16(as, OP_BCC_N(cond, rel)); } void asm_thumb_b_rel12(asm_thumb_t *as, int rel) { rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction assert(SIGNED_FIT12(rel)); asm_thumb_op16(as, OP_B_N(rel)); } #define OP_BLX(reg) (0x4780 | ((reg) << 3)) #define OP_SVC(arg) (0xdf00 | (arg)) void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp) { // Load ptr to function from table, indexed by fun_id, then call it asm_thumb_ldr_reg_reg_i12_optimised(as, reg_temp, ASM_THUMB_REG_FUN_TABLE, fun_id); asm_thumb_op16(as, OP_BLX(reg_temp)); } #endif // MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB // thumb specific stuff #include "py/mpconfig.h" #if MICROPY_EMIT_THUMB // this is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #include "py/asmthumb.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (3) // r4 #define NLR_BUF_IDX_LOCAL_2 (4) // r5 #define NLR_BUF_IDX_LOCAL_3 (5) // r6 #define N_THUMB (1) #define EXPORT_FUN(name) emit_native_thumb_##name #include "py/emitnative.c" #endif #include #ifndef __IPU__ #include #endif #include #include #include #include "py/emit.h" #include "py/asmthumb.h" #if MICROPY_EMIT_INLINE_THUMB typedef enum { // define rules with a compile function #define DEF_RULE(rule, comp, kind, ...) PN_##rule, #define DEF_RULE_NC(rule, kind, ...) #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC PN_const_object, // special node for a constant, generic Python object // define rules without a compile function #define DEF_RULE(rule, comp, kind, ...) #define DEF_RULE_NC(rule, kind, ...) PN_##rule, #include "py/grammar.h" #undef DEF_RULE #undef DEF_RULE_NC } pn_kind_t; struct _emit_inline_asm_t { asm_thumb_t as; uint16_t pass; mp_obj_t *error_slot; mp_uint_t max_num_labels; qstr *label_lookup; }; STATIC void emit_inline_thumb_error_msg(emit_inline_asm_t *emit, mp_rom_error_text_t msg) { *emit->error_slot = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg); } STATIC void emit_inline_thumb_error_exc(emit_inline_asm_t *emit, mp_obj_t exc) { *emit->error_slot = exc; } emit_inline_asm_t *emit_inline_thumb_new(mp_uint_t max_num_labels) { emit_inline_asm_t *emit = m_new_obj(emit_inline_asm_t); memset(&emit->as, 0, sizeof(emit->as)); mp_asm_base_init(&emit->as.base, max_num_labels); emit->max_num_labels = max_num_labels; emit->label_lookup = m_new(qstr, max_num_labels); return emit; } void emit_inline_thumb_free(emit_inline_asm_t *emit) { m_del(qstr, emit->label_lookup, emit->max_num_labels); mp_asm_base_deinit(&emit->as.base, false); m_del_obj(emit_inline_asm_t, emit); } STATIC void emit_inline_thumb_start_pass(emit_inline_asm_t *emit, pass_kind_t pass, mp_obj_t *error_slot) { emit->pass = pass; emit->error_slot = error_slot; if (emit->pass == MP_PASS_CODE_SIZE) { memset(emit->label_lookup, 0, emit->max_num_labels * sizeof(qstr)); } mp_asm_base_start_pass(&emit->as.base, pass == MP_PASS_EMIT ? MP_ASM_PASS_EMIT : MP_ASM_PASS_COMPUTE); asm_thumb_entry(&emit->as, 0); } STATIC void emit_inline_thumb_end_pass(emit_inline_asm_t *emit, mp_uint_t type_sig) { asm_thumb_exit(&emit->as); asm_thumb_end_pass(&emit->as); } STATIC mp_uint_t emit_inline_thumb_count_params(emit_inline_asm_t *emit, mp_uint_t n_params, mp_parse_node_t *pn_params) { if (n_params > 4) { emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("can only have up to 4 parameters to Thumb assembly")); return 0; } for (mp_uint_t i = 0; i < n_params; i++) { if (!MP_PARSE_NODE_IS_ID(pn_params[i])) { emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence r0 to r3")); return 0; } const char *p = qstr_str(MP_PARSE_NODE_LEAF_ARG(pn_params[i])); if (!(strlen(p) == 2 && p[0] == 'r' && (mp_uint_t)p[1] == '0' + i)) { emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence r0 to r3")); return 0; } } return n_params; } STATIC bool emit_inline_thumb_label(emit_inline_asm_t *emit, mp_uint_t label_num, qstr label_id) { assert(label_num < emit->max_num_labels); if (emit->pass == MP_PASS_CODE_SIZE) { // check for duplicate label on first pass for (uint i = 0; i < emit->max_num_labels; i++) { if (emit->label_lookup[i] == label_id) { return false; } } } emit->label_lookup[label_num] = label_id; mp_asm_base_label_assign(&emit->as.base, label_num); return true; } typedef struct _reg_name_t { byte reg; byte name[3]; } reg_name_t; STATIC const reg_name_t reg_name_table[] = { {0, "r0\0"}, {1, "r1\0"}, {2, "r2\0"}, {3, "r3\0"}, {4, "r4\0"}, {5, "r5\0"}, {6, "r6\0"}, {7, "r7\0"}, {8, "r8\0"}, {9, "r9\0"}, {10, "r10"}, {11, "r11"}, {12, "r12"}, {13, "r13"}, {14, "r14"}, {15, "r15"}, {10, "sl\0"}, {11, "fp\0"}, {13, "sp\0"}, {14, "lr\0"}, {15, "pc\0"}, }; #define MAX_SPECIAL_REGISTER_NAME_LENGTH 7 typedef struct _special_reg_name_t { byte reg; char name[MAX_SPECIAL_REGISTER_NAME_LENGTH + 1]; } special_reg_name_t; STATIC const special_reg_name_t special_reg_name_table[] = { {5, "IPSR"}, {17, "BASEPRI"}, }; // return empty string in case of error, so we can attempt to parse the string // without a special check if it was in fact a string STATIC const char *get_arg_str(mp_parse_node_t pn) { if (MP_PARSE_NODE_IS_ID(pn)) { qstr qst = MP_PARSE_NODE_LEAF_ARG(pn); return qstr_str(qst); } else { return ""; } } STATIC mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn, mp_uint_t max_reg) { const char *reg_str = get_arg_str(pn); for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(reg_name_table); i++) { const reg_name_t *r = ®_name_table[i]; if (reg_str[0] == r->name[0] && reg_str[1] == r->name[1] && reg_str[2] == r->name[2] && (reg_str[2] == '\0' || reg_str[3] == '\0')) { if (r->reg > max_reg) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects at most r%d"), op, max_reg)); return 0; } else { return r->reg; } } } emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a register"), op)); return 0; } STATIC mp_uint_t get_arg_special_reg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { const char *reg_str = get_arg_str(pn); for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(special_reg_name_table); i++) { const special_reg_name_t *r = &special_reg_name_table[i]; if (strcmp(r->name, reg_str) == 0) { return r->reg; } } emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a special register"), op)); return 0; } #if MICROPY_EMIT_INLINE_THUMB_FLOAT STATIC mp_uint_t get_arg_vfpreg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { const char *reg_str = get_arg_str(pn); if (reg_str[0] == 's' && reg_str[1] != '\0') { mp_uint_t regno = 0; for (++reg_str; *reg_str; ++reg_str) { mp_uint_t v = *reg_str; if (!('0' <= v && v <= '9')) { goto malformed; } regno = 10 * regno + v - '0'; } if (regno > 31) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects at most r%d"), op, 31)); return 0; } else { return regno; } } malformed: emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects an FPU register"), op)); return 0; } #endif STATIC mp_uint_t get_arg_reglist(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { // a register list looks like {r0, r1, r2} and is parsed as a Python set if (!MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_atom_brace)) { goto bad_arg; } mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; assert(MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 1); // should always be pn = pns->nodes[0]; mp_uint_t reglist = 0; if (MP_PARSE_NODE_IS_ID(pn)) { // set with one element reglist |= 1 << get_arg_reg(emit, op, pn, 15); } else if (MP_PARSE_NODE_IS_STRUCT(pn)) { pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_dictorsetmaker) { assert(MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])); // should succeed mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_dictorsetmaker_list) { // set with multiple elements // get first element of set (we rely on get_arg_reg to catch syntax errors) reglist |= 1 << get_arg_reg(emit, op, pns->nodes[0], 15); // get tail elements (2nd, 3rd, ...) mp_parse_node_t *nodes; int n = mp_parse_node_extract_list(&pns1->nodes[0], PN_dictorsetmaker_list2, &nodes); // process rest of elements for (int i = 0; i < n; i++) { reglist |= 1 << get_arg_reg(emit, op, nodes[i], 15); } } else { goto bad_arg; } } else { goto bad_arg; } } else { goto bad_arg; } return reglist; bad_arg: emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects {r0, r1, ...}"), op)); return 0; } STATIC uint32_t get_arg_i(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn, uint32_t fit_mask) { mp_obj_t o; if (!mp_parse_node_get_int_maybe(pn, &o)) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects an integer"), op)); return 0; } uint32_t i = mp_obj_get_int_truncated(o); if ((i & (~fit_mask)) != 0) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' integer 0x%x doesn't fit in mask 0x%x"), op, i, fit_mask)); return 0; } return i; } STATIC bool get_arg_addr(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn, mp_parse_node_t *pn_base, mp_parse_node_t *pn_offset) { if (!MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_atom_bracket)) { goto bad_arg; } mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (!MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)) { goto bad_arg; } pns = (mp_parse_node_struct_t *)pns->nodes[0]; if (MP_PARSE_NODE_STRUCT_NUM_NODES(pns) != 2) { goto bad_arg; } *pn_base = pns->nodes[0]; *pn_offset = pns->nodes[1]; return true; bad_arg: emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects an address of the form [a, b]"), op)); return false; } STATIC int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { if (!MP_PARSE_NODE_IS_ID(pn)) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a label"), op)); return 0; } qstr label_qstr = MP_PARSE_NODE_LEAF_ARG(pn); for (uint i = 0; i < emit->max_num_labels; i++) { if (emit->label_lookup[i] == label_qstr) { return i; } } // only need to have the labels on the last pass if (emit->pass == MP_PASS_EMIT) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("label '%q' not defined"), label_qstr)); } return 0; } typedef struct _cc_name_t { byte cc; byte name[2]; } cc_name_t; STATIC const cc_name_t cc_name_table[] = { { ASM_THUMB_CC_EQ, "eq" }, { ASM_THUMB_CC_NE, "ne" }, { ASM_THUMB_CC_CS, "cs" }, { ASM_THUMB_CC_CC, "cc" }, { ASM_THUMB_CC_MI, "mi" }, { ASM_THUMB_CC_PL, "pl" }, { ASM_THUMB_CC_VS, "vs" }, { ASM_THUMB_CC_VC, "vc" }, { ASM_THUMB_CC_HI, "hi" }, { ASM_THUMB_CC_LS, "ls" }, { ASM_THUMB_CC_GE, "ge" }, { ASM_THUMB_CC_LT, "lt" }, { ASM_THUMB_CC_GT, "gt" }, { ASM_THUMB_CC_LE, "le" }, }; typedef struct _format_4_op_t { byte op; char name[3]; } format_4_op_t; #define X(x) (((x) >> 4) & 0xff) // only need 1 byte to distinguish these ops STATIC const format_4_op_t format_4_op_table[] = { { X(ASM_THUMB_FORMAT_4_EOR), "eor" }, { X(ASM_THUMB_FORMAT_4_LSL), "lsl" }, { X(ASM_THUMB_FORMAT_4_LSR), "lsr" }, { X(ASM_THUMB_FORMAT_4_ASR), "asr" }, { X(ASM_THUMB_FORMAT_4_ADC), "adc" }, { X(ASM_THUMB_FORMAT_4_SBC), "sbc" }, { X(ASM_THUMB_FORMAT_4_ROR), "ror" }, { X(ASM_THUMB_FORMAT_4_TST), "tst" }, { X(ASM_THUMB_FORMAT_4_NEG), "neg" }, { X(ASM_THUMB_FORMAT_4_CMP), "cmp" }, { X(ASM_THUMB_FORMAT_4_CMN), "cmn" }, { X(ASM_THUMB_FORMAT_4_ORR), "orr" }, { X(ASM_THUMB_FORMAT_4_MUL), "mul" }, { X(ASM_THUMB_FORMAT_4_BIC), "bic" }, { X(ASM_THUMB_FORMAT_4_MVN), "mvn" }, }; #undef X // name is actually a qstr, which should fit in 16 bits typedef struct _format_9_10_op_t { uint16_t op; uint16_t name; } format_9_10_op_t; #define X(x) (x) STATIC const format_9_10_op_t format_9_10_op_table[] = { { X(ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_WORD_TRANSFER), MP_QSTR_ldr }, { X(ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER), MP_QSTR_ldrb }, { X(ASM_THUMB_FORMAT_10_LDRH), MP_QSTR_ldrh }, { X(ASM_THUMB_FORMAT_9_STR | ASM_THUMB_FORMAT_9_WORD_TRANSFER), MP_QSTR_str }, { X(ASM_THUMB_FORMAT_9_STR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER), MP_QSTR_strb }, { X(ASM_THUMB_FORMAT_10_STRH), MP_QSTR_strh }, }; #undef X #if MICROPY_EMIT_INLINE_THUMB_FLOAT // actual opcodes are: 0xee00 | op.hi_nibble, 0x0a00 | op.lo_nibble typedef struct _format_vfp_op_t { byte op; char name[3]; } format_vfp_op_t; STATIC const format_vfp_op_t format_vfp_op_table[] = { { 0x30, "add" }, { 0x34, "sub" }, { 0x20, "mul" }, { 0x80, "div" }, }; #endif // shorthand alias for whether we allow ARMv7-M instructions #define ARMV7M MICROPY_EMIT_INLINE_THUMB_ARMV7M STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_args, mp_parse_node_t *pn_args) { // TODO perhaps make two tables: // one_args = // "b", LAB, asm_thumb_b_n, // "bgt", LAB, asm_thumb_bgt_n, // two_args = // "movs", RLO, I8, asm_thumb_movs_reg_i8 // "movw", REG, REG, asm_thumb_movw_reg_i16 // three_args = // "subs", RLO, RLO, I3, asm_thumb_subs_reg_reg_i3 size_t op_len; const char *op_str = (const char *)qstr_data(op, &op_len); #if MICROPY_EMIT_INLINE_THUMB_FLOAT if (op_str[0] == 'v') { // floating point operations if (n_args == 2) { mp_uint_t op_code = 0x0ac0, op_code_hi; if (op == MP_QSTR_vcmp) { op_code_hi = 0xeeb4; op_vfp_twoargs:; mp_uint_t vd = get_arg_vfpreg(emit, op_str, pn_args[0]); mp_uint_t vm = get_arg_vfpreg(emit, op_str, pn_args[1]); asm_thumb_op32(&emit->as, op_code_hi | ((vd & 1) << 6), op_code | ((vd & 0x1e) << 11) | ((vm & 1) << 5) | (vm & 0x1e) >> 1); } else if (op == MP_QSTR_vsqrt) { op_code_hi = 0xeeb1; goto op_vfp_twoargs; } else if (op == MP_QSTR_vneg) { op_code_hi = 0xeeb1; op_code = 0x0a40; goto op_vfp_twoargs; } else if (op == MP_QSTR_vcvt_f32_s32) { op_code_hi = 0xeeb8; // int to float goto op_vfp_twoargs; } else if (op == MP_QSTR_vcvt_s32_f32) { op_code_hi = 0xeebd; // float to int goto op_vfp_twoargs; } else if (op == MP_QSTR_vmrs) { mp_uint_t reg_dest; const char *reg_str0 = get_arg_str(pn_args[0]); if (strcmp(reg_str0, "APSR_nzcv") == 0) { reg_dest = 15; } else { reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); } const char *reg_str1 = get_arg_str(pn_args[1]); if (strcmp(reg_str1, "FPSCR") == 0) { // FP status to ARM reg asm_thumb_op32(&emit->as, 0xeef1, 0x0a10 | (reg_dest << 12)); } else { goto unknown_op; } } else if (op == MP_QSTR_vmov) { op_code_hi = 0xee00; mp_uint_t r_arm, vm; const char *reg_str = get_arg_str(pn_args[0]); if (reg_str[0] == 'r') { r_arm = get_arg_reg(emit, op_str, pn_args[0], 15); vm = get_arg_vfpreg(emit, op_str, pn_args[1]); op_code_hi |= 0x10; } else { vm = get_arg_vfpreg(emit, op_str, pn_args[0]); r_arm = get_arg_reg(emit, op_str, pn_args[1], 15); } asm_thumb_op32(&emit->as, op_code_hi | ((vm & 0x1e) >> 1), 0x0a10 | (r_arm << 12) | ((vm & 1) << 7)); } else if (op == MP_QSTR_vldr) { op_code_hi = 0xed90; op_vldr_vstr:; mp_uint_t vd = get_arg_vfpreg(emit, op_str, pn_args[0]); mp_parse_node_t pn_base, pn_offset; if (get_arg_addr(emit, op_str, pn_args[1], &pn_base, &pn_offset)) { mp_uint_t rlo_base = get_arg_reg(emit, op_str, pn_base, 7); mp_uint_t i8; i8 = get_arg_i(emit, op_str, pn_offset, 0x3fc) >> 2; asm_thumb_op32(&emit->as, op_code_hi | rlo_base | ((vd & 1) << 6), 0x0a00 | ((vd & 0x1e) << 11) | i8); } } else if (op == MP_QSTR_vstr) { op_code_hi = 0xed80; goto op_vldr_vstr; } else { goto unknown_op; } } else if (n_args == 3) { // search table for arith ops for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(format_vfp_op_table); i++) { if (strncmp(op_str + 1, format_vfp_op_table[i].name, 3) == 0 && op_str[4] == '\0') { mp_uint_t op_code_hi = 0xee00 | (format_vfp_op_table[i].op & 0xf0); mp_uint_t op_code = 0x0a00 | ((format_vfp_op_table[i].op & 0x0f) << 4); mp_uint_t vd = get_arg_vfpreg(emit, op_str, pn_args[0]); mp_uint_t vn = get_arg_vfpreg(emit, op_str, pn_args[1]); mp_uint_t vm = get_arg_vfpreg(emit, op_str, pn_args[2]); asm_thumb_op32(&emit->as, op_code_hi | ((vd & 1) << 6) | (vn >> 1), op_code | (vm >> 1) | ((vm & 1) << 5) | ((vd & 0x1e) << 11) | ((vn & 1) << 7)); return; } } goto unknown_op; } else { goto unknown_op; } return; } #endif if (n_args == 0) { if (op == MP_QSTR_nop) { asm_thumb_op16(&emit->as, ASM_THUMB_OP_NOP); } else if (op == MP_QSTR_wfi) { asm_thumb_op16(&emit->as, ASM_THUMB_OP_WFI); } else { goto unknown_op; } } else if (n_args == 1) { if (op == MP_QSTR_b) { int label_num = get_arg_label(emit, op_str, pn_args[0]); if (!asm_thumb_b_n_label(&emit->as, label_num)) { goto branch_not_in_range; } } else if (op == MP_QSTR_bl) { int label_num = get_arg_label(emit, op_str, pn_args[0]); if (!asm_thumb_bl_label(&emit->as, label_num)) { goto branch_not_in_range; } } else if (op == MP_QSTR_bx) { mp_uint_t r = get_arg_reg(emit, op_str, pn_args[0], 15); asm_thumb_op16(&emit->as, 0x4700 | (r << 3)); } else if (op_str[0] == 'b' && (op_len == 3 || (op_len == 5 && op_str[3] == '_' && (op_str[4] == 'n' || (ARMV7M && op_str[4] == 'w'))))) { mp_uint_t cc = -1; for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { if (op_str[1] == cc_name_table[i].name[0] && op_str[2] == cc_name_table[i].name[1]) { cc = cc_name_table[i].cc; } } if (cc == (mp_uint_t)-1) { goto unknown_op; } int label_num = get_arg_label(emit, op_str, pn_args[0]); bool wide = op_len == 5 && op_str[4] == 'w'; if (wide && !ARMV7M) { goto unknown_op; } if (!asm_thumb_bcc_nw_label(&emit->as, cc, label_num, wide)) { goto branch_not_in_range; } } else if (ARMV7M && op_str[0] == 'i' && op_str[1] == 't') { const char *arg_str = get_arg_str(pn_args[0]); mp_uint_t cc = -1; for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { if (arg_str[0] == cc_name_table[i].name[0] && arg_str[1] == cc_name_table[i].name[1] && arg_str[2] == '\0') { cc = cc_name_table[i].cc; break; } } if (cc == (mp_uint_t)-1) { goto unknown_op; } const char *os = op_str + 2; while (*os != '\0') { os++; } if (os > op_str + 5) { goto unknown_op; } mp_uint_t it_mask = 8; while (--os >= op_str + 2) { it_mask >>= 1; if (*os == 't') { it_mask |= (cc & 1) << 3; } else if (*os == 'e') { it_mask |= ((~cc) & 1) << 3; } else { goto unknown_op; } } asm_thumb_it_cc(&emit->as, cc, it_mask); } else if (op == MP_QSTR_cpsid) { // TODO check pn_args[0] == i asm_thumb_op16(&emit->as, ASM_THUMB_OP_CPSID_I); } else if (op == MP_QSTR_cpsie) { // TODO check pn_args[0] == i asm_thumb_op16(&emit->as, ASM_THUMB_OP_CPSIE_I); } else if (op == MP_QSTR_push) { mp_uint_t reglist = get_arg_reglist(emit, op_str, pn_args[0]); if ((reglist & 0xff00) == 0) { asm_thumb_op16(&emit->as, 0xb400 | reglist); } else { if (!ARMV7M) { goto unknown_op; } asm_thumb_op32(&emit->as, 0xe92d, reglist); } } else if (op == MP_QSTR_pop) { mp_uint_t reglist = get_arg_reglist(emit, op_str, pn_args[0]); if ((reglist & 0xff00) == 0) { asm_thumb_op16(&emit->as, 0xbc00 | reglist); } else { if (!ARMV7M) { goto unknown_op; } asm_thumb_op32(&emit->as, 0xe8bd, reglist); } } else { goto unknown_op; } } else if (n_args == 2) { if (MP_PARSE_NODE_IS_ID(pn_args[1])) { // second arg is a register (or should be) mp_uint_t op_code, op_code_hi; if (op == MP_QSTR_mov) { mp_uint_t reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); mp_uint_t reg_src = get_arg_reg(emit, op_str, pn_args[1], 15); asm_thumb_mov_reg_reg(&emit->as, reg_dest, reg_src); } else if (ARMV7M && op == MP_QSTR_clz) { op_code_hi = 0xfab0; op_code = 0xf080; mp_uint_t rd, rm; op_clz_rbit: rd = get_arg_reg(emit, op_str, pn_args[0], 15); rm = get_arg_reg(emit, op_str, pn_args[1], 15); asm_thumb_op32(&emit->as, op_code_hi | rm, op_code | (rd << 8) | rm); } else if (ARMV7M && op == MP_QSTR_rbit) { op_code_hi = 0xfa90; op_code = 0xf0a0; goto op_clz_rbit; } else if (ARMV7M && op == MP_QSTR_mrs) { mp_uint_t reg_dest = get_arg_reg(emit, op_str, pn_args[0], 12); mp_uint_t reg_src = get_arg_special_reg(emit, op_str, pn_args[1]); asm_thumb_op32(&emit->as, 0xf3ef, 0x8000 | (reg_dest << 8) | reg_src); } else { if (op == MP_QSTR_and_) { op_code = ASM_THUMB_FORMAT_4_AND; mp_uint_t reg_dest, reg_src; op_format_4: reg_dest = get_arg_reg(emit, op_str, pn_args[0], 7); reg_src = get_arg_reg(emit, op_str, pn_args[1], 7); asm_thumb_format_4(&emit->as, op_code, reg_dest, reg_src); return; } // search table for ALU ops for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(format_4_op_table); i++) { if (strncmp(op_str, format_4_op_table[i].name, 3) == 0 && op_str[3] == '\0') { op_code = 0x4000 | (format_4_op_table[i].op << 4); goto op_format_4; } } goto unknown_op; } } else { // second arg is not a register mp_uint_t op_code; if (op == MP_QSTR_mov) { op_code = ASM_THUMB_FORMAT_3_MOV; mp_uint_t rlo_dest, i8_src; op_format_3: rlo_dest = get_arg_reg(emit, op_str, pn_args[0], 7); i8_src = get_arg_i(emit, op_str, pn_args[1], 0xff); asm_thumb_format_3(&emit->as, op_code, rlo_dest, i8_src); } else if (op == MP_QSTR_cmp) { op_code = ASM_THUMB_FORMAT_3_CMP; goto op_format_3; } else if (op == MP_QSTR_add) { op_code = ASM_THUMB_FORMAT_3_ADD; goto op_format_3; } else if (op == MP_QSTR_sub) { op_code = ASM_THUMB_FORMAT_3_SUB; goto op_format_3; #if ARMV7M } else if (op == MP_QSTR_movw) { op_code = ASM_THUMB_OP_MOVW; mp_uint_t reg_dest; op_movw_movt: reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); int i_src = get_arg_i(emit, op_str, pn_args[1], 0xffff); asm_thumb_mov_reg_i16(&emit->as, op_code, reg_dest, i_src); } else if (op == MP_QSTR_movt) { op_code = ASM_THUMB_OP_MOVT; goto op_movw_movt; } else if (op == MP_QSTR_movwt) { // this is a convenience instruction mp_uint_t reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); uint32_t i_src = get_arg_i(emit, op_str, pn_args[1], 0xffffffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVW, reg_dest, i_src & 0xffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVT, reg_dest, (i_src >> 16) & 0xffff); } else if (op == MP_QSTR_ldrex) { mp_uint_t r_dest = get_arg_reg(emit, op_str, pn_args[0], 15); mp_parse_node_t pn_base, pn_offset; if (get_arg_addr(emit, op_str, pn_args[1], &pn_base, &pn_offset)) { mp_uint_t r_base = get_arg_reg(emit, op_str, pn_base, 15); mp_uint_t i8 = get_arg_i(emit, op_str, pn_offset, 0xff) >> 2; asm_thumb_op32(&emit->as, 0xe850 | r_base, 0x0f00 | (r_dest << 12) | i8); } #endif } else { // search table for ldr/str instructions for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(format_9_10_op_table); i++) { if (op == format_9_10_op_table[i].name) { op_code = format_9_10_op_table[i].op; mp_parse_node_t pn_base, pn_offset; mp_uint_t rlo_dest = get_arg_reg(emit, op_str, pn_args[0], 7); if (get_arg_addr(emit, op_str, pn_args[1], &pn_base, &pn_offset)) { mp_uint_t rlo_base = get_arg_reg(emit, op_str, pn_base, 7); mp_uint_t i5; if (op_code & ASM_THUMB_FORMAT_9_BYTE_TRANSFER) { i5 = get_arg_i(emit, op_str, pn_offset, 0x1f); } else if (op_code & ASM_THUMB_FORMAT_10_STRH) { // also catches LDRH i5 = get_arg_i(emit, op_str, pn_offset, 0x3e) >> 1; } else { i5 = get_arg_i(emit, op_str, pn_offset, 0x7c) >> 2; } asm_thumb_format_9_10(&emit->as, op_code, rlo_dest, rlo_base, i5); return; } break; } } goto unknown_op; } } } else if (n_args == 3) { mp_uint_t op_code; if (op == MP_QSTR_lsl) { op_code = ASM_THUMB_FORMAT_1_LSL; mp_uint_t rlo_dest, rlo_src, i5; op_format_1: rlo_dest = get_arg_reg(emit, op_str, pn_args[0], 7); rlo_src = get_arg_reg(emit, op_str, pn_args[1], 7); i5 = get_arg_i(emit, op_str, pn_args[2], 0x1f); asm_thumb_format_1(&emit->as, op_code, rlo_dest, rlo_src, i5); } else if (op == MP_QSTR_lsr) { op_code = ASM_THUMB_FORMAT_1_LSR; goto op_format_1; } else if (op == MP_QSTR_asr) { op_code = ASM_THUMB_FORMAT_1_ASR; goto op_format_1; } else if (op == MP_QSTR_add) { op_code = ASM_THUMB_FORMAT_2_ADD; mp_uint_t rlo_dest, rlo_src; op_format_2: rlo_dest = get_arg_reg(emit, op_str, pn_args[0], 7); rlo_src = get_arg_reg(emit, op_str, pn_args[1], 7); int src_b; if (MP_PARSE_NODE_IS_ID(pn_args[2])) { op_code |= ASM_THUMB_FORMAT_2_REG_OPERAND; src_b = get_arg_reg(emit, op_str, pn_args[2], 7); } else { op_code |= ASM_THUMB_FORMAT_2_IMM_OPERAND; src_b = get_arg_i(emit, op_str, pn_args[2], 0x7); } asm_thumb_format_2(&emit->as, op_code, rlo_dest, rlo_src, src_b); } else if (ARMV7M && op == MP_QSTR_sdiv) { op_code = 0xfb90; // sdiv high part mp_uint_t rd, rn, rm; op_sdiv_udiv: rd = get_arg_reg(emit, op_str, pn_args[0], 15); rn = get_arg_reg(emit, op_str, pn_args[1], 15); rm = get_arg_reg(emit, op_str, pn_args[2], 15); asm_thumb_op32(&emit->as, op_code | rn, 0xf0f0 | (rd << 8) | rm); } else if (ARMV7M && op == MP_QSTR_udiv) { op_code = 0xfbb0; // udiv high part goto op_sdiv_udiv; } else if (op == MP_QSTR_sub) { op_code = ASM_THUMB_FORMAT_2_SUB; goto op_format_2; } else if (ARMV7M && op == MP_QSTR_strex) { mp_uint_t r_dest = get_arg_reg(emit, op_str, pn_args[0], 15); mp_uint_t r_src = get_arg_reg(emit, op_str, pn_args[1], 15); mp_parse_node_t pn_base, pn_offset; if (get_arg_addr(emit, op_str, pn_args[2], &pn_base, &pn_offset)) { mp_uint_t r_base = get_arg_reg(emit, op_str, pn_base, 15); mp_uint_t i8 = get_arg_i(emit, op_str, pn_offset, 0xff) >> 2; asm_thumb_op32(&emit->as, 0xe840 | r_base, (r_src << 12) | (r_dest << 8) | i8); } } else { goto unknown_op; } } else { goto unknown_op; } return; unknown_op: emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("unsupported Thumb instruction '%s' with %d arguments"), op_str, n_args)); return; branch_not_in_range: emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("branch not in range")); return; } const emit_inline_asm_method_table_t emit_inline_thumb_method_table = { #if MICROPY_DYNAMIC_COMPILER emit_inline_thumb_new, emit_inline_thumb_free, #endif emit_inline_thumb_start_pass, emit_inline_thumb_end_pass, emit_inline_thumb_count_params, emit_inline_thumb_label, emit_inline_thumb_op, }; #endif // MICROPY_EMIT_INLINE_THUMB #ifndef __IPU__ #include #endif #include #include #include "py/mpconfig.h" // wrapper around everything in this file #if MICROPY_EMIT_ARM #include "py/asmarm.h" #define SIGNED_FIT24(x) (((x) & 0xff800000) == 0) || (((x) & 0xff000000) == 0xff000000) // Insert word into instruction flow STATIC void emit(asm_arm_t *as, uint op) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 4); if (c != NULL) { *(uint32_t *)c = op; } } // Insert word into instruction flow, add "ALWAYS" condition code STATIC void emit_al(asm_arm_t *as, uint op) { emit(as, op | ASM_ARM_CC_AL); } // Basic instructions without condition code STATIC uint asm_arm_op_push(uint reglist) { // stmfd sp!, {reglist} return 0x92d0000 | (reglist & 0xFFFF); } STATIC uint asm_arm_op_pop(uint reglist) { // ldmfd sp!, {reglist} return 0x8bd0000 | (reglist & 0xFFFF); } STATIC uint asm_arm_op_mov_reg(uint rd, uint rn) { // mov rd, rn return 0x1a00000 | (rd << 12) | rn; } STATIC uint asm_arm_op_mov_imm(uint rd, uint imm) { // mov rd, #imm return 0x3a00000 | (rd << 12) | imm; } STATIC uint asm_arm_op_mvn_imm(uint rd, uint imm) { // mvn rd, #imm return 0x3e00000 | (rd << 12) | imm; } STATIC uint asm_arm_op_add_imm(uint rd, uint rn, uint imm) { // add rd, rn, #imm return 0x2800000 | (rn << 16) | (rd << 12) | (imm & 0xFF); } STATIC uint asm_arm_op_add_reg(uint rd, uint rn, uint rm) { // add rd, rn, rm return 0x0800000 | (rn << 16) | (rd << 12) | rm; } STATIC uint asm_arm_op_sub_imm(uint rd, uint rn, uint imm) { // sub rd, rn, #imm return 0x2400000 | (rn << 16) | (rd << 12) | (imm & 0xFF); } STATIC uint asm_arm_op_sub_reg(uint rd, uint rn, uint rm) { // sub rd, rn, rm return 0x0400000 | (rn << 16) | (rd << 12) | rm; } STATIC uint asm_arm_op_mul_reg(uint rd, uint rm, uint rs) { // mul rd, rm, rs assert(rd != rm); return 0x0000090 | (rd << 16) | (rs << 8) | rm; } STATIC uint asm_arm_op_and_reg(uint rd, uint rn, uint rm) { // and rd, rn, rm return 0x0000000 | (rn << 16) | (rd << 12) | rm; } STATIC uint asm_arm_op_eor_reg(uint rd, uint rn, uint rm) { // eor rd, rn, rm return 0x0200000 | (rn << 16) | (rd << 12) | rm; } STATIC uint asm_arm_op_orr_reg(uint rd, uint rn, uint rm) { // orr rd, rn, rm return 0x1800000 | (rn << 16) | (rd << 12) | rm; } void asm_arm_bkpt(asm_arm_t *as) { // bkpt #0 emit_al(as, 0x1200070); } // locals: // - stored on the stack in ascending order // - numbered 0 through num_locals-1 // - SP points to first local // // | SP // v // l0 l1 l2 ... l(n-1) // ^ ^ // | low address | high address in RAM void asm_arm_entry(asm_arm_t *as, int num_locals) { assert(num_locals >= 0); as->stack_adjust = 0; as->push_reglist = 1 << ASM_ARM_REG_R1 | 1 << ASM_ARM_REG_R2 | 1 << ASM_ARM_REG_R3 | 1 << ASM_ARM_REG_R4 | 1 << ASM_ARM_REG_R5 | 1 << ASM_ARM_REG_R6 | 1 << ASM_ARM_REG_R7 | 1 << ASM_ARM_REG_R8; // Only adjust the stack if there are more locals than usable registers if (num_locals > 3) { as->stack_adjust = num_locals * 4; // Align stack to 8 bytes if (num_locals & 1) { as->stack_adjust += 4; } } emit_al(as, asm_arm_op_push(as->push_reglist | 1 << ASM_ARM_REG_LR)); if (as->stack_adjust > 0) { emit_al(as, asm_arm_op_sub_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); } } void asm_arm_exit(asm_arm_t *as) { if (as->stack_adjust > 0) { emit_al(as, asm_arm_op_add_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); } emit_al(as, asm_arm_op_pop(as->push_reglist | (1 << ASM_ARM_REG_PC))); } void asm_arm_push(asm_arm_t *as, uint reglist) { emit_al(as, asm_arm_op_push(reglist)); } void asm_arm_pop(asm_arm_t *as, uint reglist) { emit_al(as, asm_arm_op_pop(reglist)); } void asm_arm_mov_reg_reg(asm_arm_t *as, uint reg_dest, uint reg_src) { emit_al(as, asm_arm_op_mov_reg(reg_dest, reg_src)); } size_t asm_arm_mov_reg_i32(asm_arm_t *as, uint rd, int imm) { // Insert immediate into code and jump over it emit_al(as, 0x59f0000 | (rd << 12)); // ldr rd, [pc] emit_al(as, 0xa000000); // b pc size_t loc = mp_asm_base_get_code_pos(&as->base); emit(as, imm); return loc; } void asm_arm_mov_reg_i32_optimised(asm_arm_t *as, uint rd, int imm) { // TODO: There are more variants of immediate values if ((imm & 0xFF) == imm) { emit_al(as, asm_arm_op_mov_imm(rd, imm)); } else if (imm < 0 && imm >= -256) { // mvn is "move not", not "move negative" emit_al(as, asm_arm_op_mvn_imm(rd, ~imm)); } else { asm_arm_mov_reg_i32(as, rd, imm); } } void asm_arm_mov_local_reg(asm_arm_t *as, int local_num, uint rd) { // str rd, [sp, #local_num*4] emit_al(as, 0x58d0000 | (rd << 12) | (local_num << 2)); } void asm_arm_mov_reg_local(asm_arm_t *as, uint rd, int local_num) { // ldr rd, [sp, #local_num*4] emit_al(as, 0x59d0000 | (rd << 12) | (local_num << 2)); } void asm_arm_cmp_reg_i8(asm_arm_t *as, uint rd, int imm) { // cmp rd, #imm emit_al(as, 0x3500000 | (rd << 16) | (imm & 0xFF)); } void asm_arm_cmp_reg_reg(asm_arm_t *as, uint rd, uint rn) { // cmp rd, rn emit_al(as, 0x1500000 | (rd << 16) | rn); } void asm_arm_setcc_reg(asm_arm_t *as, uint rd, uint cond) { emit(as, asm_arm_op_mov_imm(rd, 1) | cond); // movCOND rd, #1 emit(as, asm_arm_op_mov_imm(rd, 0) | (cond ^ (1 << 28))); // mov!COND rd, #0 } void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // add rd, rn, rm emit_al(as, asm_arm_op_add_reg(rd, rn, rm)); } void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // sub rd, rn, rm emit_al(as, asm_arm_op_sub_reg(rd, rn, rm)); } void asm_arm_mul_reg_reg_reg(asm_arm_t *as, uint rd, uint rs, uint rm) { // rs and rm are swapped because of restriction rd!=rm // mul rd, rm, rs emit_al(as, asm_arm_op_mul_reg(rd, rm, rs)); } void asm_arm_and_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // and rd, rn, rm emit_al(as, asm_arm_op_and_reg(rd, rn, rm)); } void asm_arm_eor_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // eor rd, rn, rm emit_al(as, asm_arm_op_eor_reg(rd, rn, rm)); } void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // orr rd, rn, rm emit_al(as, asm_arm_op_orr_reg(rd, rn, rm)); } void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num) { // add rd, sp, #local_num*4 emit_al(as, asm_arm_op_add_imm(rd, ASM_ARM_REG_SP, local_num << 2)); } void asm_arm_mov_reg_pcrel(asm_arm_t *as, uint reg_dest, uint label) { assert(label < as->base.max_num_labels); mp_uint_t dest = as->base.label_offsets[label]; mp_int_t rel = dest - as->base.code_offset; rel -= 12 + 8; // adjust for load of rel, and then PC+8 prefetch of add_reg_reg_reg // To load rel int reg_dest, insert immediate into code and jump over it emit_al(as, 0x59f0000 | (reg_dest << 12)); // ldr rd, [pc] emit_al(as, 0xa000000); // b pc emit(as, rel); // Do reg_dest += PC asm_arm_add_reg_reg_reg(as, reg_dest, reg_dest, ASM_ARM_REG_PC); } void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint rs) { // mov rd, rd, lsl rs emit_al(as, 0x1a00010 | (rd << 12) | (rs << 8) | rd); } void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs) { // mov rd, rd, lsr rs emit_al(as, 0x1a00030 | (rd << 12) | (rs << 8) | rd); } void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs) { // mov rd, rd, asr rs emit_al(as, 0x1a00050 | (rd << 12) | (rs << 8) | rd); } void asm_arm_ldr_reg_reg(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { // ldr rd, [rn, #off] emit_al(as, 0x5900000 | (rn << 16) | (rd << 12) | byte_offset); } void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { // ldrh rd, [rn] emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12)); } void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { // ldrb rd, [rn] emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12)); } void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { // str rd, [rm, #off] emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); } void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm) { // strh rd, [rm] emit_al(as, 0x1c000b0 | (rm << 16) | (rd << 12)); } void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm) { // strb rd, [rm] emit_al(as, 0x5c00000 | (rm << 16) | (rd << 12)); } void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // str rd, [rm, rn, lsl #2] emit_al(as, 0x7800100 | (rm << 16) | (rd << 12) | rn); } void asm_arm_strh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // strh doesn't support scaled register index emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1 emit_al(as, 0x18000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // strh rd, [rm, r8] } void asm_arm_strb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // strb rd, [rm, rn] emit_al(as, 0x7c00000 | (rm << 16) | (rd << 12) | rn); } void asm_arm_bcc_label(asm_arm_t *as, int cond, uint label) { assert(label < as->base.max_num_labels); mp_uint_t dest = as->base.label_offsets[label]; mp_int_t rel = dest - as->base.code_offset; rel -= 8; // account for instruction prefetch, PC is 8 bytes ahead of this instruction rel >>= 2; // in ARM mode the branch target is 32-bit aligned, so the 2 LSB are omitted if (SIGNED_FIT24(rel)) { emit(as, cond | 0xa000000 | (rel & 0xffffff)); } else { printf("asm_arm_bcc: branch does not fit in 24 bits\n"); } } void asm_arm_b_label(asm_arm_t *as, uint label) { asm_arm_bcc_label(as, ASM_ARM_CC_AL, label); } void asm_arm_bl_ind(asm_arm_t *as, uint fun_id, uint reg_temp) { // The table offset should fit into the ldr instruction assert(fun_id < (0x1000 / 4)); emit_al(as, asm_arm_op_mov_reg(ASM_ARM_REG_LR, ASM_ARM_REG_PC)); // mov lr, pc emit_al(as, 0x597f000 | (fun_id << 2)); // ldr pc, [r7, #fun_id*4] } void asm_arm_bx_reg(asm_arm_t *as, uint reg_src) { emit_al(as, 0x012fff10 | reg_src); } #endif // MICROPY_EMIT_ARM // ARM specific stuff #include "py/mpconfig.h" #if MICROPY_EMIT_ARM // This is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #include "py/asmarm.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (3) // r4 #define NLR_BUF_IDX_LOCAL_2 (4) // r5 #define NLR_BUF_IDX_LOCAL_3 (5) // r6 #define N_ARM (1) #define EXPORT_FUN(name) emit_native_arm_##name #include "py/emitnative.c" #endif #ifndef __IPU__ #include #endif #include #include "py/mpconfig.h" // wrapper around everything in this file #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_XTENSAWIN #include "py/asmxtensa.h" #define WORD_SIZE (4) #define SIGNED_FIT8(x) ((((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80)) #define SIGNED_FIT12(x) ((((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800)) void asm_xtensa_end_pass(asm_xtensa_t *as) { as->num_const = as->cur_const; as->cur_const = 0; #if 0 // make a hex dump of the machine code if (as->base.pass == MP_ASM_PASS_EMIT) { uint8_t *d = as->base.code_base; printf("XTENSA ASM:"); for (int i = 0; i < ((as->base.code_size + 15) & ~15); ++i) { if (i % 16 == 0) { printf("\n%08x:", (uint32_t)&d[i]); } if (i % 2 == 0) { printf(" "); } printf("%02x", d[i]); } printf("\n"); } #endif } void asm_xtensa_entry(asm_xtensa_t *as, int num_locals) { // jump over the constants asm_xtensa_op_j(as, as->num_const * WORD_SIZE + 4 - 4); mp_asm_base_get_cur_to_write_bytes(&as->base, 1); // padding/alignment byte as->const_table = (uint32_t *)mp_asm_base_get_cur_to_write_bytes(&as->base, as->num_const * 4); // adjust the stack-pointer to store a0, a12, a13, a14, a15 and locals, 16-byte aligned as->stack_adjust = (((ASM_XTENSA_NUM_REGS_SAVED + num_locals) * WORD_SIZE) + 15) & ~15; if (SIGNED_FIT8(-as->stack_adjust)) { asm_xtensa_op_addi(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, -as->stack_adjust); } else { asm_xtensa_op_movi(as, ASM_XTENSA_REG_A9, as->stack_adjust); asm_xtensa_op_sub(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A9); } // save return value (a0) and callee-save registers (a12, a13, a14, a15) asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0); for (int i = 1; i < ASM_XTENSA_NUM_REGS_SAVED; ++i) { asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A11 + i, ASM_XTENSA_REG_A1, i); } } void asm_xtensa_exit(asm_xtensa_t *as) { // restore registers for (int i = ASM_XTENSA_NUM_REGS_SAVED - 1; i >= 1; --i) { asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A11 + i, ASM_XTENSA_REG_A1, i); } asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0); // restore stack-pointer and return if (SIGNED_FIT8(as->stack_adjust)) { asm_xtensa_op_addi(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, as->stack_adjust); } else { asm_xtensa_op_movi(as, ASM_XTENSA_REG_A9, as->stack_adjust); asm_xtensa_op_add_n(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A9); } asm_xtensa_op_ret_n(as); } void asm_xtensa_entry_win(asm_xtensa_t *as, int num_locals) { // jump over the constants asm_xtensa_op_j(as, as->num_const * WORD_SIZE + 4 - 4); mp_asm_base_get_cur_to_write_bytes(&as->base, 1); // padding/alignment byte as->const_table = (uint32_t *)mp_asm_base_get_cur_to_write_bytes(&as->base, as->num_const * 4); as->stack_adjust = 32 + ((((ASM_XTENSA_NUM_REGS_SAVED_WIN + num_locals) * WORD_SIZE) + 15) & ~15); asm_xtensa_op_entry(as, ASM_XTENSA_REG_A1, as->stack_adjust); asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0); } void asm_xtensa_exit_win(asm_xtensa_t *as) { asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0); asm_xtensa_op_retw_n(as); } STATIC uint32_t get_label_dest(asm_xtensa_t *as, uint label) { assert(label < as->base.max_num_labels); return as->base.label_offsets[label]; } void asm_xtensa_op16(asm_xtensa_t *as, uint16_t op) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 2); if (c != NULL) { c[0] = op; c[1] = op >> 8; } } void asm_xtensa_op24(asm_xtensa_t *as, uint32_t op) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 3); if (c != NULL) { c[0] = op; c[1] = op >> 8; c[2] = op >> 16; } } void asm_xtensa_j_label(asm_xtensa_t *as, uint label) { uint32_t dest = get_label_dest(as, label); int32_t rel = dest - as->base.code_offset - 4; // we assume rel, as a signed int, fits in 18-bits asm_xtensa_op_j(as, rel); } void asm_xtensa_bccz_reg_label(asm_xtensa_t *as, uint cond, uint reg, uint label) { uint32_t dest = get_label_dest(as, label); int32_t rel = dest - as->base.code_offset - 4; if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT12(rel)) { printf("ERROR: xtensa bccz out of range\n"); } asm_xtensa_op_bccz(as, cond, reg, rel); } void asm_xtensa_bcc_reg_reg_label(asm_xtensa_t *as, uint cond, uint reg1, uint reg2, uint label) { uint32_t dest = get_label_dest(as, label); int32_t rel = dest - as->base.code_offset - 4; if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT8(rel)) { printf("ERROR: xtensa bcc out of range\n"); } asm_xtensa_op_bcc(as, cond, reg1, reg2, rel); } // convenience function; reg_dest must be different from reg_src[12] void asm_xtensa_setcc_reg_reg_reg(asm_xtensa_t *as, uint cond, uint reg_dest, uint reg_src1, uint reg_src2) { asm_xtensa_op_movi_n(as, reg_dest, 1); asm_xtensa_op_bcc(as, cond, reg_src1, reg_src2, 1); asm_xtensa_op_movi_n(as, reg_dest, 0); } size_t asm_xtensa_mov_reg_i32(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { // load the constant uint32_t const_table_offset = (uint8_t *)as->const_table - as->base.code_base; size_t loc = const_table_offset + as->cur_const * WORD_SIZE; asm_xtensa_op_l32r(as, reg_dest, as->base.code_offset, loc); // store the constant in the table if (as->const_table != NULL) { as->const_table[as->cur_const] = i32; } ++as->cur_const; return loc; } void asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { if (SIGNED_FIT12(i32)) { asm_xtensa_op_movi(as, reg_dest, i32); } else { asm_xtensa_mov_reg_i32(as, reg_dest, i32); } } void asm_xtensa_mov_local_reg(asm_xtensa_t *as, int local_num, uint reg_src) { asm_xtensa_op_s32i(as, reg_src, ASM_XTENSA_REG_A1, local_num); } void asm_xtensa_mov_reg_local(asm_xtensa_t *as, uint reg_dest, int local_num) { asm_xtensa_op_l32i(as, reg_dest, ASM_XTENSA_REG_A1, local_num); } void asm_xtensa_mov_reg_local_addr(asm_xtensa_t *as, uint reg_dest, int local_num) { uint off = local_num * WORD_SIZE; if (SIGNED_FIT8(off)) { asm_xtensa_op_addi(as, reg_dest, ASM_XTENSA_REG_A1, off); } else { asm_xtensa_op_movi(as, reg_dest, off); asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A1); } } void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label) { // Get relative offset from PC uint32_t dest = get_label_dest(as, label); int32_t rel = dest - as->base.code_offset; rel -= 3 + 3; // account for 3 bytes of movi instruction, 3 bytes call0 adjustment asm_xtensa_op_movi(as, reg_dest, rel); // imm has 12-bit range // Use call0 to get PC+3 into a0 // call0 destination must be aligned on 4 bytes: // - code_offset&3=0: off=0, pad=1 // - code_offset&3=1: off=0, pad=0 // - code_offset&3=2: off=1, pad=3 // - code_offset&3=3: off=1, pad=2 uint32_t off = as->base.code_offset >> 1 & 1; uint32_t pad = (5 - as->base.code_offset) & 3; asm_xtensa_op_call0(as, off); mp_asm_base_get_cur_to_write_bytes(&as->base, pad); // Add PC to relative offset asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A0); } void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx) { if (idx < 16) { asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx); } else { asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx); } asm_xtensa_op_callx0(as, ASM_XTENSA_REG_A0); } void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx) { if (idx < 16) { asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx); } else { asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx); } asm_xtensa_op_callx8(as, ASM_XTENSA_REG_A8); } #endif // MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_XTENSAWIN // Xtensa specific stuff #include "py/mpconfig.h" #if MICROPY_EMIT_XTENSA // this is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #include "py/asmxtensa.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (8) // a12 #define NLR_BUF_IDX_LOCAL_2 (9) // a13 #define NLR_BUF_IDX_LOCAL_3 (10) // a14 #define N_XTENSA (1) #define EXPORT_FUN(name) emit_native_xtensa_##name #include "py/emitnative.c" #endif #include #ifndef __IPU__ #include #endif #include #include #include #include "py/emit.h" #include "py/asmxtensa.h" #if MICROPY_EMIT_INLINE_XTENSA struct _emit_inline_asm_t { asm_xtensa_t as; uint16_t pass; mp_obj_t *error_slot; mp_uint_t max_num_labels; qstr *label_lookup; }; STATIC void emit_inline_xtensa_error_msg(emit_inline_asm_t *emit, mp_rom_error_text_t msg) { *emit->error_slot = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg); } STATIC void emit_inline_xtensa_error_exc(emit_inline_asm_t *emit, mp_obj_t exc) { *emit->error_slot = exc; } emit_inline_asm_t *emit_inline_xtensa_new(mp_uint_t max_num_labels) { emit_inline_asm_t *emit = m_new_obj(emit_inline_asm_t); memset(&emit->as, 0, sizeof(emit->as)); mp_asm_base_init(&emit->as.base, max_num_labels); emit->max_num_labels = max_num_labels; emit->label_lookup = m_new(qstr, max_num_labels); return emit; } void emit_inline_xtensa_free(emit_inline_asm_t *emit) { m_del(qstr, emit->label_lookup, emit->max_num_labels); mp_asm_base_deinit(&emit->as.base, false); m_del_obj(emit_inline_asm_t, emit); } STATIC void emit_inline_xtensa_start_pass(emit_inline_asm_t *emit, pass_kind_t pass, mp_obj_t *error_slot) { emit->pass = pass; emit->error_slot = error_slot; if (emit->pass == MP_PASS_CODE_SIZE) { memset(emit->label_lookup, 0, emit->max_num_labels * sizeof(qstr)); } mp_asm_base_start_pass(&emit->as.base, pass == MP_PASS_EMIT ? MP_ASM_PASS_EMIT : MP_ASM_PASS_COMPUTE); asm_xtensa_entry(&emit->as, 0); } STATIC void emit_inline_xtensa_end_pass(emit_inline_asm_t *emit, mp_uint_t type_sig) { asm_xtensa_exit(&emit->as); asm_xtensa_end_pass(&emit->as); } STATIC mp_uint_t emit_inline_xtensa_count_params(emit_inline_asm_t *emit, mp_uint_t n_params, mp_parse_node_t *pn_params) { if (n_params > 4) { emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("can only have up to 4 parameters to Xtensa assembly")); return 0; } for (mp_uint_t i = 0; i < n_params; i++) { if (!MP_PARSE_NODE_IS_ID(pn_params[i])) { emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence a2 to a5")); return 0; } const char *p = qstr_str(MP_PARSE_NODE_LEAF_ARG(pn_params[i])); if (!(strlen(p) == 2 && p[0] == 'a' && (mp_uint_t)p[1] == '2' + i)) { emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence a2 to a5")); return 0; } } return n_params; } STATIC bool emit_inline_xtensa_label(emit_inline_asm_t *emit, mp_uint_t label_num, qstr label_id) { assert(label_num < emit->max_num_labels); if (emit->pass == MP_PASS_CODE_SIZE) { // check for duplicate label on first pass for (uint i = 0; i < emit->max_num_labels; i++) { if (emit->label_lookup[i] == label_id) { return false; } } } emit->label_lookup[label_num] = label_id; mp_asm_base_label_assign(&emit->as.base, label_num); return true; } typedef struct _reg_name_t { byte reg; byte name[3]; } reg_name_t; STATIC const reg_name_t reg_name_table[] = { {0, "a0\0"}, {1, "a1\0"}, {2, "a2\0"}, {3, "a3\0"}, {4, "a4\0"}, {5, "a5\0"}, {6, "a6\0"}, {7, "a7\0"}, {8, "a8\0"}, {9, "a9\0"}, {10, "a10"}, {11, "a11"}, {12, "a12"}, {13, "a13"}, {14, "a14"}, {15, "a15"}, }; // return empty string in case of error, so we can attempt to parse the string // without a special check if it was in fact a string STATIC const char *get_arg_str(mp_parse_node_t pn) { if (MP_PARSE_NODE_IS_ID(pn)) { qstr qst = MP_PARSE_NODE_LEAF_ARG(pn); return qstr_str(qst); } else { return ""; } } STATIC mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { const char *reg_str = get_arg_str(pn); for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(reg_name_table); i++) { const reg_name_t *r = ®_name_table[i]; if (reg_str[0] == r->name[0] && reg_str[1] == r->name[1] && reg_str[2] == r->name[2] && (reg_str[2] == '\0' || reg_str[3] == '\0')) { return r->reg; } } emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a register"), op)); return 0; } STATIC uint32_t get_arg_i(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn, int min, int max) { mp_obj_t o; if (!mp_parse_node_get_int_maybe(pn, &o)) { emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects an integer"), op)); return 0; } uint32_t i = mp_obj_get_int_truncated(o); if (min != max && ((int)i < min || (int)i > max)) { emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' integer %d isn't within range %d..%d"), op, i, min, max)); return 0; } return i; } STATIC int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { if (!MP_PARSE_NODE_IS_ID(pn)) { emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a label"), op)); return 0; } qstr label_qstr = MP_PARSE_NODE_LEAF_ARG(pn); for (uint i = 0; i < emit->max_num_labels; i++) { if (emit->label_lookup[i] == label_qstr) { return i; } } // only need to have the labels on the last pass if (emit->pass == MP_PASS_EMIT) { emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("label '%q' not defined"), label_qstr)); } return 0; } #define RRR (0) #define RRI8 (1) #define RRI8_B (2) typedef struct _opcode_table_3arg_t { uint16_t name; // actually a qstr, which should fit in 16 bits uint8_t type; uint8_t a0 : 4; uint8_t a1 : 4; } opcode_table_3arg_t; STATIC const opcode_table_3arg_t opcode_table_3arg[] = { // arithmetic opcodes: reg, reg, reg {MP_QSTR_and_, RRR, 0, 1}, {MP_QSTR_or_, RRR, 0, 2}, {MP_QSTR_xor, RRR, 0, 3}, {MP_QSTR_add, RRR, 0, 8}, {MP_QSTR_sub, RRR, 0, 12}, {MP_QSTR_mull, RRR, 2, 8}, // load/store/addi opcodes: reg, reg, imm // upper nibble of type encodes the range of the immediate arg {MP_QSTR_l8ui, RRI8 | 0x10, 2, 0}, {MP_QSTR_l16ui, RRI8 | 0x30, 2, 1}, {MP_QSTR_l32i, RRI8 | 0x50, 2, 2}, {MP_QSTR_s8i, RRI8 | 0x10, 2, 4}, {MP_QSTR_s16i, RRI8 | 0x30, 2, 5}, {MP_QSTR_s32i, RRI8 | 0x50, 2, 6}, {MP_QSTR_l16si, RRI8 | 0x30, 2, 9}, {MP_QSTR_addi, RRI8 | 0x00, 2, 12}, // branch opcodes: reg, reg, label {MP_QSTR_ball, RRI8_B, ASM_XTENSA_CC_ALL, 0}, {MP_QSTR_bany, RRI8_B, ASM_XTENSA_CC_ANY, 0}, {MP_QSTR_bbc, RRI8_B, ASM_XTENSA_CC_BC, 0}, {MP_QSTR_bbs, RRI8_B, ASM_XTENSA_CC_BS, 0}, {MP_QSTR_beq, RRI8_B, ASM_XTENSA_CC_EQ, 0}, {MP_QSTR_bge, RRI8_B, ASM_XTENSA_CC_GE, 0}, {MP_QSTR_bgeu, RRI8_B, ASM_XTENSA_CC_GEU, 0}, {MP_QSTR_blt, RRI8_B, ASM_XTENSA_CC_LT, 0}, {MP_QSTR_bnall, RRI8_B, ASM_XTENSA_CC_NALL, 0}, {MP_QSTR_bne, RRI8_B, ASM_XTENSA_CC_NE, 0}, {MP_QSTR_bnone, RRI8_B, ASM_XTENSA_CC_NONE, 0}, }; STATIC void emit_inline_xtensa_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_args, mp_parse_node_t *pn_args) { size_t op_len; const char *op_str = (const char *)qstr_data(op, &op_len); if (n_args == 0) { if (op == MP_QSTR_ret_n) { asm_xtensa_op_ret_n(&emit->as); } else { goto unknown_op; } } else if (n_args == 1) { if (op == MP_QSTR_callx0) { uint r0 = get_arg_reg(emit, op_str, pn_args[0]); asm_xtensa_op_callx0(&emit->as, r0); } else if (op == MP_QSTR_j) { int label = get_arg_label(emit, op_str, pn_args[0]); asm_xtensa_j_label(&emit->as, label); } else if (op == MP_QSTR_jx) { uint r0 = get_arg_reg(emit, op_str, pn_args[0]); asm_xtensa_op_jx(&emit->as, r0); } else { goto unknown_op; } } else if (n_args == 2) { uint r0 = get_arg_reg(emit, op_str, pn_args[0]); if (op == MP_QSTR_beqz) { int label = get_arg_label(emit, op_str, pn_args[1]); asm_xtensa_bccz_reg_label(&emit->as, ASM_XTENSA_CCZ_EQ, r0, label); } else if (op == MP_QSTR_bnez) { int label = get_arg_label(emit, op_str, pn_args[1]); asm_xtensa_bccz_reg_label(&emit->as, ASM_XTENSA_CCZ_NE, r0, label); } else if (op == MP_QSTR_mov || op == MP_QSTR_mov_n) { // we emit mov.n for both "mov" and "mov_n" opcodes uint r1 = get_arg_reg(emit, op_str, pn_args[1]); asm_xtensa_op_mov_n(&emit->as, r0, r1); } else if (op == MP_QSTR_movi) { // for convenience we emit l32r if the integer doesn't fit in movi uint32_t imm = get_arg_i(emit, op_str, pn_args[1], 0, 0); asm_xtensa_mov_reg_i32(&emit->as, r0, imm); } else { goto unknown_op; } } else if (n_args == 3) { // search table for 3 arg instructions for (uint i = 0; i < MP_ARRAY_SIZE(opcode_table_3arg); i++) { const opcode_table_3arg_t *o = &opcode_table_3arg[i]; if (op == o->name) { uint r0 = get_arg_reg(emit, op_str, pn_args[0]); uint r1 = get_arg_reg(emit, op_str, pn_args[1]); if (o->type == RRR) { uint r2 = get_arg_reg(emit, op_str, pn_args[2]); asm_xtensa_op24(&emit->as, ASM_XTENSA_ENCODE_RRR(0, o->a0, o->a1, r0, r1, r2)); } else if (o->type == RRI8_B) { int label = get_arg_label(emit, op_str, pn_args[2]); asm_xtensa_bcc_reg_reg_label(&emit->as, o->a0, r0, r1, label); } else { int shift, min, max; if ((o->type & 0xf0) == 0) { shift = 0; min = -128; max = 127; } else { shift = (o->type & 0xf0) >> 5; min = 0; max = 0xff << shift; } uint32_t imm = get_arg_i(emit, op_str, pn_args[2], min, max); asm_xtensa_op24(&emit->as, ASM_XTENSA_ENCODE_RRI8(o->a0, o->a1, r1, r0, (imm >> shift) & 0xff)); } return; } } goto unknown_op; } else { goto unknown_op; } return; unknown_op: emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("unsupported Xtensa instruction '%s' with %d arguments"), op_str, n_args)); return; /* branch_not_in_range: emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("branch not in range")); return; */ } const emit_inline_asm_method_table_t emit_inline_xtensa_method_table = { #if MICROPY_DYNAMIC_COMPILER emit_inline_xtensa_new, emit_inline_xtensa_free, #endif emit_inline_xtensa_start_pass, emit_inline_xtensa_end_pass, emit_inline_xtensa_count_params, emit_inline_xtensa_label, emit_inline_xtensa_op, }; #endif // MICROPY_EMIT_INLINE_XTENSA // Xtensa-Windowed specific stuff #include "py/mpconfig.h" #if MICROPY_EMIT_XTENSAWIN // this is defined so that the assembler exports generic assembler API macros #define GENERIC_ASM_API (1) #define GENERIC_ASM_API_WIN (1) #include "py/asmxtensa.h" // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (2 + 4) // a4 #define NLR_BUF_IDX_LOCAL_2 (2 + 5) // a5 #define NLR_BUF_IDX_LOCAL_3 (2 + 6) // a6 #define N_NLR_SETJMP (1) #define N_PRELUDE_AS_BYTES_OBJ (1) #define N_XTENSAWIN (1) #define EXPORT_FUN(name) emit_native_xtensawin_##name #include "py/emitnative.c" #endif #include "py/mpconfig.h" #if MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE #include #include #include #include #include "py/formatfloat.h" /*********************************************************************** Routine for converting a arbitrary floating point number into a string. The code in this funcion was inspired from Fred Bayer's pdouble.c. Since pdouble.c was released as Public Domain, I'm releasing this code as public domain as well. The original code can be found in https://github.com/dhylands/format-float Dave Hylands ***********************************************************************/ #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT // 1 sign bit, 8 exponent bits, and 23 mantissa bits. // exponent values 0 and 255 are reserved, exponent can be 1 to 254. // exponent is stored with a bias of 127. // The min and max floats are on the order of 1x10^37 and 1x10^-37 #define FPTYPE float #define FPCONST(x) x##F #define FPROUND_TO_ONE 0.9999995F #define FPDECEXP 32 #define FPMIN_BUF_SIZE 6 // +9e+99 #define FLT_SIGN_MASK 0x80000000 #define FLT_EXP_MASK 0x7F800000 #define FLT_MAN_MASK 0x007FFFFF union floatbits { float f; uint32_t u; }; static inline int fp_signbit(float x) { union floatbits fb = {x}; return fb.u & FLT_SIGN_MASK; } #define fp_isnan(x) isnan(x) #define fp_isinf(x) isinf(x) static inline int fp_iszero(float x) { union floatbits fb = {x}; return fb.u == 0; } static inline int fp_isless1(float x) { union floatbits fb = {x}; return fb.u < 0x3f800000; } #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE #define FPTYPE double #define FPCONST(x) x #define FPROUND_TO_ONE 0.999999999995 #define FPDECEXP 256 #define FPMIN_BUF_SIZE 7 // +9e+199 #define fp_signbit(x) signbit(x) #define fp_isnan(x) isnan(x) #define fp_isinf(x) isinf(x) #define fp_iszero(x) (x == 0) #define fp_isless1(x) (x < 1.0) #endif static const FPTYPE g_pos_pow[] = { #if FPDECEXP > 32 MICROPY_FLOAT_CONST(1e256), MICROPY_FLOAT_CONST(1e128), MICROPY_FLOAT_CONST(1e64), #endif MICROPY_FLOAT_CONST(1e32), MICROPY_FLOAT_CONST(1e16), MICROPY_FLOAT_CONST(1e8), MICROPY_FLOAT_CONST(1e4), MICROPY_FLOAT_CONST(1e2), MICROPY_FLOAT_CONST(1e1) }; static const FPTYPE g_neg_pow[] = { #if FPDECEXP > 32 MICROPY_FLOAT_CONST(1e-256), MICROPY_FLOAT_CONST(1e-128), MICROPY_FLOAT_CONST(1e-64), #endif MICROPY_FLOAT_CONST(1e-32), MICROPY_FLOAT_CONST(1e-16), MICROPY_FLOAT_CONST(1e-8), MICROPY_FLOAT_CONST(1e-4), MICROPY_FLOAT_CONST(1e-2), MICROPY_FLOAT_CONST(1e-1) }; int mp_format_float(FPTYPE f, char *buf, size_t buf_size, char fmt, int prec, char sign) { char *s = buf; if (buf_size <= FPMIN_BUF_SIZE) { // FPMIN_BUF_SIZE is the minimum size needed to store any FP number. // If the buffer does not have enough room for this (plus null terminator) // then don't try to format the float. if (buf_size >= 2) { *s++ = '?'; } if (buf_size >= 1) { *s = '\0'; } return buf_size >= 2; } if (fp_signbit(f) && !fp_isnan(f)) { *s++ = '-'; f = -f; } else { if (sign) { *s++ = sign; } } // buf_remaining contains bytes available for digits and exponent. // It is buf_size minus room for the sign and null byte. int buf_remaining = buf_size - 1 - (s - buf); { char uc = fmt & 0x20; if (fp_isinf(f)) { *s++ = 'I' ^ uc; *s++ = 'N' ^ uc; *s++ = 'F' ^ uc; goto ret; } else if (fp_isnan(f)) { *s++ = 'N' ^ uc; *s++ = 'A' ^ uc; *s++ = 'N' ^ uc; ret: *s = '\0'; return s - buf; } } if (prec < 0) { prec = 6; } char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt fmt |= 0x20; // Force fmt to be lowercase char org_fmt = fmt; if (fmt == 'g' && prec == 0) { prec = 1; } int e, e1; int dec = 0; char e_sign = '\0'; int num_digits = 0; const FPTYPE *pos_pow = g_pos_pow; const FPTYPE *neg_pow = g_neg_pow; if (fp_iszero(f)) { e = 0; if (fmt == 'f') { // Truncate precision to prevent buffer overflow if (prec + 2 > buf_remaining) { prec = buf_remaining - 2; } num_digits = prec + 1; } else { // Truncate precision to prevent buffer overflow if (prec + 6 > buf_remaining) { prec = buf_remaining - 6; } if (fmt == 'e') { e_sign = '+'; } } } else if (fp_isless1(f)) { // We need to figure out what an integer digit will be used // in case 'f' is used (or we revert other format to it below). // As we just tested number to be <1, this is obviously 0, // but we can round it up to 1 below. char first_dig = '0'; if (f >= FPROUND_TO_ONE) { first_dig = '1'; } // Build negative exponent for (e = 0, e1 = FPDECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) { if (*neg_pow > f) { e += e1; f *= *pos_pow; } } char e_sign_char = '-'; if (fp_isless1(f) && f >= FPROUND_TO_ONE) { f = FPCONST(1.0); if (e == 0) { e_sign_char = '+'; } } else if (fp_isless1(f)) { e++; f *= FPCONST(10.0); } // If the user specified 'g' format, and e is <= 4, then we'll switch // to the fixed format ('f') if (fmt == 'f' || (fmt == 'g' && e <= 4)) { fmt = 'f'; dec = -1; *s++ = first_dig; if (org_fmt == 'g') { prec += (e - 1); } // truncate precision to prevent buffer overflow if (prec + 2 > buf_remaining) { prec = buf_remaining - 2; } num_digits = prec; if (num_digits) { *s++ = '.'; while (--e && num_digits) { *s++ = '0'; num_digits--; } } } else { // For e & g formats, we'll be printing the exponent, so set the // sign. e_sign = e_sign_char; dec = 0; if (prec > (buf_remaining - FPMIN_BUF_SIZE)) { prec = buf_remaining - FPMIN_BUF_SIZE; if (fmt == 'g') { prec++; } } } } else { // Build positive exponent for (e = 0, e1 = FPDECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) { if (*pos_pow <= f) { e += e1; f *= *neg_pow; } } // It can be that f was right on the edge of an entry in pos_pow needs to be reduced if ((int)f >= 10) { e += 1; f *= FPCONST(0.1); } // If the user specified fixed format (fmt == 'f') and e makes the // number too big to fit into the available buffer, then we'll // switch to the 'e' format. if (fmt == 'f') { if (e >= buf_remaining) { fmt = 'e'; } else if ((e + prec + 2) > buf_remaining) { prec = buf_remaining - e - 2; if (prec < 0) { // This means no decimal point, so we can add one back // for the decimal. prec++; } } } if (fmt == 'e' && prec > (buf_remaining - FPMIN_BUF_SIZE)) { prec = buf_remaining - FPMIN_BUF_SIZE; } if (fmt == 'g') { // Truncate precision to prevent buffer overflow if (prec + (FPMIN_BUF_SIZE - 1) > buf_remaining) { prec = buf_remaining - (FPMIN_BUF_SIZE - 1); } } // If the user specified 'g' format, and e is < prec, then we'll switch // to the fixed format. if (fmt == 'g' && e < prec) { fmt = 'f'; prec -= (e + 1); } if (fmt == 'f') { dec = e; num_digits = prec + e + 1; } else { e_sign = '+'; } } if (prec < 0) { // This can happen when the prec is trimmed to prevent buffer overflow prec = 0; } // We now have num.f as a floating point number between >= 1 and < 10 // (or equal to zero), and e contains the absolute value of the power of // 10 exponent. and (dec + 1) == the number of dgits before the decimal. // For e, prec is # digits after the decimal // For f, prec is # digits after the decimal // For g, prec is the max number of significant digits // // For e & g there will be a single digit before the decimal // for f there will be e digits before the decimal if (fmt == 'e') { num_digits = prec + 1; } else if (fmt == 'g') { if (prec == 0) { prec = 1; } num_digits = prec; } // Print the digits of the mantissa for (int i = 0; i < num_digits; ++i, --dec) { int32_t d = (int32_t)f; if (d < 0) { *s++ = '0'; } else { *s++ = '0' + d; } if (dec == 0 && prec > 0) { *s++ = '.'; } f -= (FPTYPE)d; f *= FPCONST(10.0); } // Round // If we print non-exponential format (i.e. 'f'), but a digit we're going // to round by (e) is too far away, then there's nothing to round. if ((org_fmt != 'f' || e <= num_digits) && f >= FPCONST(5.0)) { char *rs = s; rs--; while (1) { if (*rs == '.') { rs--; continue; } if (*rs < '0' || *rs > '9') { // + or - rs++; // So we sit on the digit to the right of the sign break; } if (*rs < '9') { (*rs)++; break; } *rs = '0'; if (rs == buf) { break; } rs--; } if (*rs == '0') { // We need to insert a 1 if (rs[1] == '.' && fmt != 'f') { // We're going to round 9.99 to 10.00 // Move the decimal point rs[0] = '.'; rs[1] = '0'; if (e_sign == '-') { e--; if (e == 0) { e_sign = '+'; } } else { e++; } } else { // Need at extra digit at the end to make room for the leading '1' s++; } char *ss = s; while (ss > rs) { *ss = ss[-1]; ss--; } *rs = '1'; } } // verify that we did not overrun the input buffer so far assert((size_t)(s + 1 - buf) <= buf_size); if (org_fmt == 'g' && prec > 0) { // Remove trailing zeros and a trailing decimal point while (s[-1] == '0') { s--; } if (s[-1] == '.') { s--; } } // Append the exponent if (e_sign) { *s++ = e_char; *s++ = e_sign; if (FPMIN_BUF_SIZE == 7 && e >= 100) { *s++ = '0' + (e / 100); } *s++ = '0' + ((e / 10) % 10); *s++ = '0' + (e % 10); } *s = '\0'; // verify that we did not overrun the input buffer assert((size_t)(s + 1 - buf) <= buf_size); return s - buf; } #endif // MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE #include "py/mpconfig.h" #include "py/misc.h" #include "py/parsenumbase.h" // find real radix base, and strip preceding '0x', '0o' and '0b' // puts base in *base, and returns number of bytes to skip the prefix size_t mp_parse_num_base(const char *str, size_t len, int *base) { const byte *p = (const byte *)str; if (len <= 1) { goto no_prefix; } unichar c = *(p++); if ((*base == 0 || *base == 16) && c == '0') { c = *(p++); if ((c | 32) == 'x') { *base = 16; } else if (*base == 0 && (c | 32) == 'o') { *base = 8; } else if (*base == 0 && (c | 32) == 'b') { *base = 2; } else { if (*base == 0) { *base = 10; } p -= 2; } } else if (*base == 8 && c == '0') { c = *(p++); if ((c | 32) != 'o') { p -= 2; } } else if (*base == 2 && c == '0') { c = *(p++); if ((c | 32) != 'b') { p -= 2; } } else { p--; no_prefix: if (*base == 0) { *base = 10; } } return p - (const byte *)str; } #include #include #include "py/runtime.h" #include "py/parsenumbase.h" #include "py/parsenum.h" #include "py/smallint.h" #if MICROPY_PY_BUILTINS_FLOAT #include #endif STATIC NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { // if lex!=NULL then the parser called us and we need to convert the // exception's type from ValueError to SyntaxError and add traceback info if (lex != NULL) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(exc))->type = &mp_type_SyntaxError; mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); } nlr_raise(exc); } mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) { const byte *restrict str = (const byte *)str_; const byte *restrict top = str + len; bool neg = false; mp_obj_t ret_val; // check radix base if ((base != 0 && base < 2) || base > 36) { // this won't be reached if lex!=NULL mp_raise_ValueError(MP_ERROR_TEXT("int() arg 2 must be >= 2 and <= 36")); } // skip leading space for (; str < top && unichar_isspace(*str); str++) { } // parse optional sign if (str < top) { if (*str == '+') { str++; } else if (*str == '-') { str++; neg = true; } } // parse optional base prefix str += mp_parse_num_base((const char *)str, top - str, &base); // string should be an integer number mp_int_t int_val = 0; const byte *restrict str_val_start = str; for (; str < top; str++) { // get next digit as a value mp_uint_t dig = *str; if ('0' <= dig && dig <= '9') { dig -= '0'; } else if (dig == '_') { continue; } else { dig |= 0x20; // make digit lower-case if ('a' <= dig && dig <= 'z') { dig -= 'a' - 10; } else { // unknown character break; } } if (dig >= (mp_uint_t)base) { break; } // add next digi and check for overflow if (mp_small_int_mul_overflow(int_val, base)) { goto overflow; } int_val = int_val * base + dig; if (!MP_SMALL_INT_FITS(int_val)) { goto overflow; } } // negate value if needed if (neg) { int_val = -int_val; } // create the small int ret_val = MP_OBJ_NEW_SMALL_INT(int_val); have_ret_val: // check we parsed something if (str == str_val_start) { goto value_error; } // skip trailing space for (; str < top && unichar_isspace(*str); str++) { } // check we reached the end of the string if (str != top) { goto value_error; } // return the object return ret_val; overflow: // reparse using long int { const char *s2 = (const char *)str_val_start; ret_val = mp_obj_new_int_from_str_len(&s2, top - str_val_start, neg, base); str = (const byte *)s2; goto have_ret_val; } value_error: { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for integer")); raise_exc(exc, lex); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_obj_t exc = mp_obj_new_exception_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for integer with base %d"), base); raise_exc(exc, lex); #else vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 50, &print); mp_printf(&print, "invalid syntax for integer with base %d: ", base); mp_str_print_quoted(&print, str_val_start, top - str_val_start, true); mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_ValueError, mp_obj_new_str_from_vstr(&mp_type_str, &vstr)); raise_exc(exc, lex); #endif } } typedef enum { PARSE_DEC_IN_INTG, PARSE_DEC_IN_FRAC, PARSE_DEC_IN_EXP, } parse_dec_in_t; mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool force_complex, mp_lexer_t *lex) { #if MICROPY_PY_BUILTINS_FLOAT // DEC_VAL_MAX only needs to be rough and is used to retain precision while not overflowing // SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float // EXACT_POWER_OF_10 is the largest value of x so that 10^x can be stored exactly in a float // Note: EXACT_POWER_OF_10 is at least floor(log_5(2^mantissa_length)). Indeed, 10^n = 2^n * 5^n // so we only have to store the 5^n part in the mantissa (the 2^n part will go into the float's // exponent). #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT #define DEC_VAL_MAX 1e20F #define SMALL_NORMAL_VAL (1e-37F) #define SMALL_NORMAL_EXP (-37) #define EXACT_POWER_OF_10 (9) #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE #define DEC_VAL_MAX 1e200 #define SMALL_NORMAL_VAL (1e-307) #define SMALL_NORMAL_EXP (-307) #define EXACT_POWER_OF_10 (22) #endif const char *top = str + len; mp_float_t dec_val = 0; bool dec_neg = false; bool imag = false; // skip leading space for (; str < top && unichar_isspace(*str); str++) { } // parse optional sign if (str < top) { if (*str == '+') { str++; } else if (*str == '-') { str++; dec_neg = true; } } const char *str_val_start = str; // determine what the string is if (str < top && (str[0] | 0x20) == 'i') { // string starts with 'i', should be 'inf' or 'infinity' (case insensitive) if (str + 2 < top && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'f') { // inf str += 3; dec_val = (mp_float_t)INFINITY; if (str + 4 < top && (str[0] | 0x20) == 'i' && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'i' && (str[3] | 0x20) == 't' && (str[4] | 0x20) == 'y') { // infinity str += 5; } } } else if (str < top && (str[0] | 0x20) == 'n') { // string starts with 'n', should be 'nan' (case insensitive) if (str + 2 < top && (str[1] | 0x20) == 'a' && (str[2] | 0x20) == 'n') { // NaN str += 3; dec_val = MICROPY_FLOAT_C_FUN(nan)(""); } } else { // string should be a decimal number parse_dec_in_t in = PARSE_DEC_IN_INTG; bool exp_neg = false; int exp_val = 0; int exp_extra = 0; while (str < top) { unsigned int dig = *str++; if ('0' <= dig && dig <= '9') { dig -= '0'; if (in == PARSE_DEC_IN_EXP) { // don't overflow exp_val when adding next digit, instead just truncate // it and the resulting float will still be correct, either inf or 0.0 // (use INT_MAX/2 to allow adding exp_extra at the end without overflow) if (exp_val < (INT_MAX / 2 - 9) / 10) { exp_val = 10 * exp_val + dig; } } else { if (dec_val < DEC_VAL_MAX) { // dec_val won't overflow so keep accumulating dec_val = 10 * dec_val + dig; if (in == PARSE_DEC_IN_FRAC) { --exp_extra; } } else { // dec_val might overflow and we anyway can't represent more digits // of precision, so ignore the digit and just adjust the exponent if (in == PARSE_DEC_IN_INTG) { ++exp_extra; } } } } else if (in == PARSE_DEC_IN_INTG && dig == '.') { in = PARSE_DEC_IN_FRAC; } else if (in != PARSE_DEC_IN_EXP && ((dig | 0x20) == 'e')) { in = PARSE_DEC_IN_EXP; if (str < top) { if (str[0] == '+') { str++; } else if (str[0] == '-') { str++; exp_neg = true; } } if (str == top) { goto value_error; } } else if (allow_imag && (dig | 0x20) == 'j') { imag = true; break; } else if (dig == '_') { continue; } else { // unknown character str--; break; } } // work out the exponent if (exp_neg) { exp_val = -exp_val; } // apply the exponent, making sure it's not a subnormal value exp_val += exp_extra; if (exp_val < SMALL_NORMAL_EXP) { exp_val -= SMALL_NORMAL_EXP; dec_val *= SMALL_NORMAL_VAL; } // At this point, we need to multiply the mantissa by its base 10 exponent. If possible, // we would rather manipulate numbers that have an exact representation in IEEE754. It // turns out small positive powers of 10 do, whereas small negative powers of 10 don't. // So in that case, we'll yield a division of exact values rather than a multiplication // of slightly erroneous values. if (exp_val < 0 && exp_val >= -EXACT_POWER_OF_10) { dec_val /= MICROPY_FLOAT_C_FUN(pow)(10, -exp_val); } else { dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val); } } // negate value if needed if (dec_neg) { dec_val = -dec_val; } // check we parsed something if (str == str_val_start) { goto value_error; } // skip trailing space for (; str < top && unichar_isspace(*str); str++) { } // check we reached the end of the string if (str != top) { goto value_error; } // return the object #if MICROPY_PY_BUILTINS_COMPLEX if (imag) { return mp_obj_new_complex(0, dec_val); } else if (force_complex) { return mp_obj_new_complex(dec_val, 0); } #else if (imag || force_complex) { raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("complex values not supported")), lex); } #endif else { return mp_obj_new_float(dec_val); } value_error: raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for number")), lex); #else raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("decimal numbers not supported")), lex); #endif } // This code glues the code emitters to the runtime. #include #ifndef __IPU__ #include #endif #include #include #include "py/emitglue.h" #include "py/runtime0.h" #include "py/bc.h" #include "py/objfun.h" #include "py/profile.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define WRITE_CODE (1) #define DEBUG_printf DEBUG_printf #define DEBUG_OP_printf(...) DEBUG_printf(__VA_ARGS__) #else // don't print debugging info #define DEBUG_printf(...) (void)0 #define DEBUG_OP_printf(...) (void)0 #endif #if MICROPY_DEBUG_PRINTERS mp_uint_t mp_verbose_flag = 0; #endif mp_raw_code_t *mp_emit_glue_new_raw_code(void) { mp_raw_code_t *rc = m_new0(mp_raw_code_t, 1); rc->kind = MP_CODE_RESERVED; #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = 0; #endif return rc; } void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS size_t len, #endif mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE size_t n_children, #endif mp_uint_t scope_flags) { rc->kind = MP_CODE_BYTECODE; rc->scope_flags = scope_flags; rc->fun_data = code; #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS rc->fun_data_len = len; #endif rc->children = children; #if MICROPY_PERSISTENT_CODE_SAVE rc->n_children = n_children; #endif #if MICROPY_PY_SYS_SETTRACE mp_bytecode_prelude_t *prelude = &rc->prelude; mp_prof_extract_prelude(code, prelude); #endif #ifdef DEBUG_PRINT #if !(MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS) // const size_t len = 0; #endif DEBUG_printf("assign byte code: code=%p len=" UINT_FMT " flags=%x\n", code, len, (uint)scope_flags); #endif } #if MICROPY_EMIT_MACHINE_CODE void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void *fun_data, mp_uint_t fun_len, mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE size_t n_children, uint16_t prelude_offset, uint16_t n_qstr, mp_qstr_link_entry_t *qstr_link, #endif mp_uint_t scope_flags, mp_uint_t n_pos_args, mp_uint_t type_sig) { assert(kind == MP_CODE_NATIVE_PY || kind == MP_CODE_NATIVE_VIPER || kind == MP_CODE_NATIVE_ASM); // Some architectures require flushing/invalidation of the I/D caches, // so that the generated native code which was created in data RAM will // be available for execution from instruction RAM. #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB #if __ICACHE_PRESENT == 1 // Flush D-cache, so the code emitted is stored in RAM. MP_HAL_CLEAN_DCACHE(fun_data, fun_len); // Invalidate I-cache, so the newly-created code is reloaded from RAM. SCB_InvalidateICache(); #endif #elif MICROPY_EMIT_ARM #if (defined(__linux__) && defined(__GNUC__)) || __ARM_ARCH == 7 __builtin___clear_cache(fun_data, (uint8_t *)fun_data + fun_len); #elif defined(__arm__) // Flush I-cache and D-cache. asm volatile ( "0:" "mrc p15, 0, r15, c7, c10, 3\n" // test and clean D-cache "bne 0b\n" "mov r0, #0\n" "mcr p15, 0, r0, c7, c7, 0\n" // invalidate I-cache and D-cache : : : "r0", "cc"); #endif #endif rc->kind = kind; rc->scope_flags = scope_flags; rc->fun_data = fun_data; #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS rc->fun_data_len = fun_len; #endif rc->children = children; #if MICROPY_PERSISTENT_CODE_SAVE rc->n_children = n_children; rc->prelude_offset = prelude_offset; rc->n_qstr = n_qstr; rc->qstr_link = qstr_link; #endif // These two entries are only needed for MP_CODE_NATIVE_ASM. rc->n_pos_args = n_pos_args; rc->type_sig = type_sig; #ifdef DEBUG_PRINT DEBUG_printf("assign native: kind=%d fun=%p len=" UINT_FMT " n_pos_args=" UINT_FMT " flags=%x\n", kind, fun_data, fun_len, n_pos_args, (uint)scope_flags); for (mp_uint_t i = 0; i < fun_len; i++) { if (i > 0 && i % 16 == 0) { DEBUG_printf("\n"); } DEBUG_printf(" %02x", ((byte *)fun_data)[i]); } DEBUG_printf("\n"); #ifdef WRITE_CODE FILE *fp_write_code = fopen("out-code", "wb"); fwrite(fun_data, fun_len, 1, fp_write_code); fclose(fp_write_code); #endif #else (void)fun_len; #endif } #endif mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, const mp_obj_t *def_args) { DEBUG_OP_printf("make_function_from_raw_code %p\n", rc); assert(rc != NULL); // def_args must be MP_OBJ_NULL or a tuple assert(def_args == NULL || def_args[0] == MP_OBJ_NULL || mp_obj_is_type(def_args[0], &mp_type_tuple)); // def_kw_args must be MP_OBJ_NULL or a dict assert(def_args == NULL || def_args[1] == MP_OBJ_NULL || mp_obj_is_type(def_args[1], &mp_type_dict)); // make the function, depending on the raw code kind mp_obj_t fun; switch (rc->kind) { #if MICROPY_EMIT_NATIVE case MP_CODE_NATIVE_PY: case MP_CODE_NATIVE_VIPER: fun = mp_obj_new_fun_native(def_args, rc->fun_data, context, rc->children); // Check for a generator function, and if so change the type of the object if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_native_gen_wrap; } break; #endif #if MICROPY_EMIT_INLINE_ASM case MP_CODE_NATIVE_ASM: fun = mp_obj_new_fun_asm(rc->n_pos_args, rc->fun_data, rc->type_sig); break; #endif default: // rc->kind should always be set and BYTECODE is the only remaining case assert(rc->kind == MP_CODE_BYTECODE); fun = mp_obj_new_fun_bc(def_args, rc->fun_data, context, rc->children); // check for generator functions and if so change the type of the object if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_gen_wrap; } #if MICROPY_PY_SYS_SETTRACE mp_obj_fun_bc_t *self_fun = (mp_obj_fun_bc_t *)MP_OBJ_TO_PTR(fun); self_fun->rc = rc; #endif break; } return fun; } mp_obj_t mp_make_closure_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, mp_uint_t n_closed_over, const mp_obj_t *args) { DEBUG_OP_printf("make_closure_from_raw_code %p " UINT_FMT " %p\n", rc, n_closed_over, args); // make function object mp_obj_t ffun; if (n_closed_over & 0x100) { // default positional and keyword args given ffun = mp_make_function_from_raw_code(rc, context, args); } else { // default positional and keyword args not given ffun = mp_make_function_from_raw_code(rc, context, NULL); } // wrap function in closure object return mp_obj_new_closure(ffun, n_closed_over & 0xff, args + ((n_closed_over >> 7) & 2)); } #include #ifndef __IPU__ #include #endif #include #include #include "py/reader.h" #include "py/nativeglue.h" #include "py/persistentcode.h" #include "py/bc0.h" #include "py/objstr.h" #include "py/mpthread.h" #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE #include "py/smallint.h" #define QSTR_LAST_STATIC MP_QSTR_zip #if MICROPY_DYNAMIC_COMPILER #define MPY_FEATURE_ARCH_DYNAMIC mp_dynamic_compiler.native_arch #else #define MPY_FEATURE_ARCH_DYNAMIC MPY_FEATURE_ARCH #endif #if MICROPY_PERSISTENT_CODE_LOAD || (MICROPY_PERSISTENT_CODE_SAVE && !MICROPY_DYNAMIC_COMPILER) // The bytecode will depend on the number of bits in a small-int, and // this function computes that (could make it a fixed constant, but it // would need to be defined in mpconfigport.h). STATIC int mp_small_int_bits(void) { mp_int_t i = MP_SMALL_INT_MAX; int n = 1; while (i != 0) { i >>= 1; ++n; } return n; } #endif typedef struct _bytecode_prelude_t { uint n_state; uint n_exc_stack; uint scope_flags; uint n_pos_args; uint n_kwonly_args; uint n_def_pos_args; uint code_info_size; } bytecode_prelude_t; #endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE #if MICROPY_PERSISTENT_CODE_LOAD #include "py/parsenum.h" STATIC int read_byte(mp_reader_t *reader); STATIC size_t read_uint(mp_reader_t *reader); #if MICROPY_EMIT_MACHINE_CODE typedef struct _reloc_info_t { mp_reader_t *reader; uint8_t *rodata; uint8_t *bss; } reloc_info_t; #if MICROPY_EMIT_THUMB STATIC void asm_thumb_rewrite_mov(uint8_t *pc, uint16_t val) { // high part *(uint16_t *)pc = (*(uint16_t *)pc & 0xfbf0) | (val >> 1 & 0x0400) | (val >> 12); // low part *(uint16_t *)(pc + 2) = (*(uint16_t *)(pc + 2) & 0x0f00) | (val << 4 & 0x7000) | (val & 0x00ff); } #endif STATIC void arch_link_qstr(uint8_t *pc, bool is_obj, qstr qst) { mp_uint_t val = qst; if (is_obj) { val = (mp_uint_t)MP_OBJ_NEW_QSTR(qst); } #if MICROPY_EMIT_X86 || MICROPY_EMIT_X64 || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN pc[0] = val & 0xff; pc[1] = (val >> 8) & 0xff; pc[2] = (val >> 16) & 0xff; pc[3] = (val >> 24) & 0xff; #elif MICROPY_EMIT_THUMB if (is_obj) { // qstr object, movw and movt asm_thumb_rewrite_mov(pc, val); // movw asm_thumb_rewrite_mov(pc + 4, val >> 16); // movt } else { // qstr number, movw instruction asm_thumb_rewrite_mov(pc, val); // movw } #endif } void mp_native_relocate(void *ri_in, uint8_t *text, uintptr_t reloc_text) { // Relocate native code reloc_info_t *ri = ri_in; uint8_t op; uintptr_t *addr_to_adjust = NULL; while ((op = read_byte(ri->reader)) != 0xff) { if (op & 1) { // Point to new location to make adjustments size_t addr = read_uint(ri->reader); if ((addr & 1) == 0) { // Point to somewhere in text addr_to_adjust = &((uintptr_t *)text)[addr >> 1]; } else { // Point to somewhere in rodata addr_to_adjust = &((uintptr_t *)ri->rodata)[addr >> 1]; } } op >>= 1; uintptr_t dest; size_t n = 1; if (op <= 5) { if (op & 1) { // Read in number of adjustments to make n = read_uint(ri->reader); } op >>= 1; if (op == 0) { // Destination is text dest = reloc_text; } else if (op == 1) { // Destination is rodata dest = (uintptr_t)ri->rodata; } else { // Destination is bss dest = (uintptr_t)ri->bss; } } else if (op == 6) { // Destination is mp_fun_table itself dest = (uintptr_t)&mp_fun_table; } else { // Destination is an entry in mp_fun_table dest = ((uintptr_t *)&mp_fun_table)[op - 7]; } while (n--) { *addr_to_adjust++ += dest; } } } #endif STATIC int read_byte(mp_reader_t *reader) { return reader->readbyte(reader->data); } STATIC void read_bytes(mp_reader_t *reader, byte *buf, size_t len) { while (len-- > 0) { *buf++ = reader->readbyte(reader->data); } } STATIC size_t read_uint(mp_reader_t *reader) { size_t unum = 0; for (;;) { byte b = reader->readbyte(reader->data); unum = (unum << 7) | (b & 0x7f); if ((b & 0x80) == 0) { break; } } return unum; } STATIC qstr load_qstr(mp_reader_t *reader) { size_t len = read_uint(reader); if (len & 1) { // static qstr return len >> 1; } len >>= 1; char *str = m_new(char, len); read_bytes(reader, (byte *)str, len); read_byte(reader); // read and discard null terminator qstr qst = qstr_from_strn(str, len); m_del(char, str, len); return qst; } STATIC mp_obj_t load_obj(mp_reader_t *reader) { byte obj_type = read_byte(reader); #if MICROPY_EMIT_MACHINE_CODE if (obj_type == 't') { return MP_OBJ_FROM_PTR(&mp_fun_table); } else #endif if (obj_type == 'e') { return MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj); } else { size_t len = read_uint(reader); if (len == 0 && obj_type == 'b') { read_byte(reader); // skip null terminator return mp_const_empty_bytes; } vstr_t vstr; vstr_init_len(&vstr, len); read_bytes(reader, (byte *)vstr.buf, len); if (obj_type == 's' || obj_type == 'b') { read_byte(reader); // skip null terminator return mp_obj_new_str_from_vstr(obj_type == 's' ? &mp_type_str : &mp_type_bytes, &vstr); } else if (obj_type == 'i') { return mp_parse_num_integer(vstr.buf, vstr.len, 10, NULL); } else { assert(obj_type == 'f' || obj_type == 'c'); return mp_parse_num_decimal(vstr.buf, vstr.len, obj_type == 'c', false, NULL); } } } STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader) { // Load function kind and data length size_t kind_len = read_uint(reader); int kind = (kind_len & 3) + MP_CODE_BYTECODE; bool has_children = !!(kind_len & 4); size_t fun_data_len = kind_len >> 3; #if !MICROPY_EMIT_MACHINE_CODE if (kind != MP_CODE_BYTECODE) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); } #endif uint8_t *fun_data = NULL; #if MICROPY_EMIT_MACHINE_CODE size_t prelude_offset = 0; mp_uint_t native_scope_flags = 0; mp_uint_t native_n_pos_args = 0; mp_uint_t native_type_sig = 0; #endif if (kind == MP_CODE_BYTECODE) { // Allocate memory for the bytecode fun_data = m_new(uint8_t, fun_data_len); // Load bytecode read_bytes(reader, fun_data, fun_data_len); #if MICROPY_EMIT_MACHINE_CODE } else { // Allocate memory for native data and load it size_t fun_alloc; MP_PLAT_ALLOC_EXEC(fun_data_len, (void **)&fun_data, &fun_alloc); read_bytes(reader, fun_data, fun_data_len); if (kind == MP_CODE_NATIVE_PY || kind == MP_CODE_NATIVE_VIPER) { // Parse qstr link table and link native code size_t n_qstr_link = read_uint(reader); for (size_t i = 0; i < n_qstr_link; ++i) { size_t off = read_uint(reader); qstr qst = load_qstr(reader); uint8_t *dest = fun_data + (off >> 2); if ((off & 3) == 0) { // Generic 16-bit link dest[0] = qst & 0xff; dest[1] = (qst >> 8) & 0xff; } else if ((off & 3) == 3) { // Generic, aligned qstr-object link *(mp_obj_t *)dest = MP_OBJ_NEW_QSTR(qst); } else { // Architecture-specific link arch_link_qstr(dest, (off & 3) == 2, qst); } } } if (kind == MP_CODE_NATIVE_PY) { // Read prelude offset within fun_data, and extract scope flags. prelude_offset = read_uint(reader); const byte *ip = fun_data + prelude_offset; MP_BC_PRELUDE_SIG_DECODE(ip); native_scope_flags = scope_flags; } else { // Load basic scope info for viper and asm. native_scope_flags = read_uint(reader); if (kind == MP_CODE_NATIVE_ASM) { native_n_pos_args = read_uint(reader); native_type_sig = read_uint(reader); } } #endif } size_t n_children = 0; mp_raw_code_t **children = NULL; #if MICROPY_EMIT_MACHINE_CODE // Load optional BSS/rodata for viper. uint8_t *rodata = NULL; uint8_t *bss = NULL; if (kind == MP_CODE_NATIVE_VIPER) { size_t rodata_size = 0; if (native_scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { rodata_size = read_uint(reader); } size_t bss_size = 0; if (native_scope_flags & MP_SCOPE_FLAG_VIPERBSS) { bss_size = read_uint(reader); } if (rodata_size + bss_size != 0) { bss_size = (uintptr_t)MP_ALIGN(bss_size, sizeof(uintptr_t)); uint8_t *data = m_new0(uint8_t, bss_size + rodata_size); bss = data; rodata = bss + bss_size; if (native_scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { read_bytes(reader, rodata, rodata_size); } // Viper code with BSS/rodata should not have any children. // Reuse the children pointer to reference the BSS/rodata // memory so that it is not reclaimed by the GC. assert(!has_children); children = (void *)data; } } #endif // Load children if any. if (has_children) { n_children = read_uint(reader); children = m_new(mp_raw_code_t *, n_children); for (size_t i = 0; i < n_children; ++i) { children[i] = load_raw_code(reader); } } // Create raw_code and return it mp_raw_code_t *rc = mp_emit_glue_new_raw_code(); if (kind == MP_CODE_BYTECODE) { const byte *ip = fun_data; MP_BC_PRELUDE_SIG_DECODE(ip); // Assign bytecode to raw code object mp_emit_glue_assign_bytecode(rc, fun_data, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS fun_data_len, #endif children, #if MICROPY_PERSISTENT_CODE_SAVE n_children, #endif scope_flags); #if MICROPY_EMIT_MACHINE_CODE } else { // Relocate and commit code to executable address space reloc_info_t ri = {reader, rodata, bss}; #if defined(MP_PLAT_COMMIT_EXEC) void *opt_ri = (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE // If native code needs relocations then it's not guaranteed that a pointer to // the head of `buf` (containing the machine code) will be retained for the GC // to trace. This is because native functions can start inside `buf` and so // it's possible that the only GC-reachable pointers are pointers inside `buf`. // So put this `buf` on a list of reachable root pointers. if (MP_STATE_PORT(track_reloc_code_list) == MP_OBJ_NULL) { MP_STATE_PORT(track_reloc_code_list) = mp_obj_new_list(0, NULL); } mp_obj_list_append(MP_STATE_PORT(track_reloc_code_list), MP_OBJ_FROM_PTR(fun_data)); #endif // Do the relocations. mp_native_relocate(&ri, fun_data, (uintptr_t)fun_data); } #endif // Assign native code to raw code object mp_emit_glue_assign_native(rc, kind, fun_data, fun_data_len, children, #if MICROPY_PERSISTENT_CODE_SAVE n_children, prelude_offset, 0, NULL, #endif native_scope_flags, native_n_pos_args, native_type_sig ); #endif } return rc; } mp_compiled_module_t mp_raw_code_load(mp_reader_t *reader, mp_module_context_t *context) { byte header[4]; read_bytes(reader, header, sizeof(header)); if (header[0] != 'M' || header[1] != MPY_VERSION || MPY_FEATURE_DECODE_FLAGS(header[2]) != MPY_FEATURE_FLAGS || header[3] > mp_small_int_bits()) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); } if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE) { byte arch = MPY_FEATURE_DECODE_ARCH(header[2]); if (!MPY_FEATURE_ARCH_TEST(arch)) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy arch")); } } size_t n_qstr = read_uint(reader); size_t n_obj = read_uint(reader); mp_module_context_alloc_tables(context, n_qstr, n_obj); // Load qstrs. for (size_t i = 0; i < n_qstr; ++i) { context->constants.qstr_table[i] = load_qstr(reader); } // Load constant objects. for (size_t i = 0; i < n_obj; ++i) { context->constants.obj_table[i] = load_obj(reader); } // Load top-level module. mp_compiled_module_t cm2; cm2.rc = load_raw_code(reader); cm2.context = context; #if MICROPY_PERSISTENT_CODE_SAVE cm2.has_native = MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE; cm2.n_qstr = n_qstr; cm2.n_obj = n_obj; #endif reader->close(reader->data); return cm2; } mp_compiled_module_t mp_raw_code_load_mem(const byte *buf, size_t len, mp_module_context_t *context) { mp_reader_t reader; mp_reader_new_mem(&reader, buf, len, 0); return mp_raw_code_load(&reader, context); } #if MICROPY_HAS_FILE_READER mp_compiled_module_t mp_raw_code_load_file(const char *filename, mp_module_context_t *context) { mp_reader_t reader; mp_reader_new_file(&reader, filename); return mp_raw_code_load(&reader, context); } #endif // MICROPY_HAS_FILE_READER #endif // MICROPY_PERSISTENT_CODE_LOAD #if MICROPY_PERSISTENT_CODE_SAVE #include "py/objstr.h" STATIC void mp_print_bytes(mp_print_t *print, const byte *data, size_t len) { print->print_strn(print->data, (const char *)data, len); } #define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) STATIC void mp_print_uint(mp_print_t *print, size_t n) { byte buf[BYTES_FOR_INT]; byte *p = buf + sizeof(buf); *--p = n & 0x7f; n >>= 7; for (; n != 0; n >>= 7) { *--p = 0x80 | (n & 0x7f); } print->print_strn(print->data, (char *)p, buf + sizeof(buf) - p); } STATIC void save_qstr(mp_print_t *print, qstr qst) { if (qst <= QSTR_LAST_STATIC) { // encode static qstr mp_print_uint(print, qst << 1 | 1); return; } size_t len; const byte *str = qstr_data(qst, &len); mp_print_uint(print, len << 1); mp_print_bytes(print, str, len + 1); // +1 to store null terminator } STATIC void save_obj(mp_print_t *print, mp_obj_t o) { #if MICROPY_EMIT_MACHINE_CODE if (o == MP_OBJ_FROM_PTR(&mp_fun_table)) { byte obj_type = 't'; mp_print_bytes(print, &obj_type, 1); } else #endif if (mp_obj_is_str_or_bytes(o)) { byte obj_type; if (mp_obj_is_str(o)) { obj_type = 's'; } else { obj_type = 'b'; } size_t len; const char *str = mp_obj_str_get_data(o, &len); mp_print_bytes(print, &obj_type, 1); mp_print_uint(print, len); mp_print_bytes(print, (const byte *)str, len + 1); // +1 to store null terminator } else if (MP_OBJ_TO_PTR(o) == &mp_const_ellipsis_obj) { byte obj_type = 'e'; mp_print_bytes(print, &obj_type, 1); } else { // we save numbers using a simplistic text representation // TODO could be improved byte obj_type; if (mp_obj_is_int(o)) { obj_type = 'i'; #if MICROPY_PY_BUILTINS_COMPLEX } else if (mp_obj_is_type(o, &mp_type_complex)) { obj_type = 'c'; #endif } else { assert(mp_obj_is_float(o)); obj_type = 'f'; } vstr_t vstr; mp_print_t pr; vstr_init_print(&vstr, 10, &pr); mp_obj_print_helper(&pr, o, PRINT_REPR); mp_print_bytes(print, &obj_type, 1); mp_print_uint(print, vstr.len); mp_print_bytes(print, (const byte *)vstr.buf, vstr.len); vstr_clear(&vstr); } } STATIC void save_raw_code(mp_print_t *print, const mp_raw_code_t *rc) { // Save function kind and data length mp_print_uint(print, (rc->fun_data_len << 3) | ((rc->n_children != 0) << 2) | (rc->kind - MP_CODE_BYTECODE)); // Save function code. mp_print_bytes(print, rc->fun_data, rc->fun_data_len); #if MICROPY_EMIT_MACHINE_CODE if (rc->kind == MP_CODE_NATIVE_PY || rc->kind == MP_CODE_NATIVE_VIPER) { // Save qstr link table for native code mp_print_uint(print, rc->n_qstr); for (size_t i = 0; i < rc->n_qstr; ++i) { mp_print_uint(print, rc->qstr_link[i].off); save_qstr(print, rc->qstr_link[i].qst); } } if (rc->kind == MP_CODE_NATIVE_PY) { // Save prelude size mp_print_uint(print, rc->prelude_offset); } else if (rc->kind == MP_CODE_NATIVE_VIPER || rc->kind == MP_CODE_NATIVE_ASM) { // Save basic scope info for viper and asm mp_print_uint(print, rc->scope_flags & MP_SCOPE_FLAG_ALL_SIG); if (rc->kind == MP_CODE_NATIVE_ASM) { mp_print_uint(print, rc->n_pos_args); mp_print_uint(print, rc->type_sig); } } #endif if (rc->n_children) { mp_print_uint(print, rc->n_children); for (size_t i = 0; i < rc->n_children; ++i) { save_raw_code(print, rc->children[i]); } } } void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print) { // header contains: // byte 'M' // byte version // byte feature flags // byte number of bits in a small int // uint size of qstr window byte header[4] = { 'M', MPY_VERSION, MPY_FEATURE_ENCODE_FLAGS(MPY_FEATURE_FLAGS_DYNAMIC), #if MICROPY_DYNAMIC_COMPILER mp_dynamic_compiler.small_int_bits, #else mp_small_int_bits(), #endif }; if (cm->has_native) { header[2] |= MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH_DYNAMIC); } mp_print_bytes(print, header, sizeof(header)); // Number of entries in constant table. mp_print_uint(print, cm->n_qstr); mp_print_uint(print, cm->n_obj); // Save qstrs. for (size_t i = 0; i < cm->n_qstr; ++i) { save_qstr(print, cm->context->constants.qstr_table[i]); } // Save constant objects. for (size_t i = 0; i < cm->n_obj; ++i) { save_obj(print, (mp_obj_t)cm->context->constants.obj_table[i]); } // Save outer raw code, which will save all its child raw codes. save_raw_code(print, cm->rc); } #if MICROPY_PERSISTENT_CODE_SAVE_FILE #ifndef __IPU__ #include #else #include #endif #include #include STATIC void fd_print_strn(void *env, const char *str, size_t len) { int fd = (intptr_t)env; MP_THREAD_GIL_EXIT(); ssize_t ret = write(fd, str, len); MP_THREAD_GIL_ENTER(); (void)ret; } void mp_raw_code_save_file(mp_compiled_module_t *cm, const char *filename) { MP_THREAD_GIL_EXIT(); int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); MP_THREAD_GIL_ENTER(); mp_print_t fd_print = {(void *)(intptr_t)fd, fd_print_strn}; mp_raw_code_save(cm, &fd_print); MP_THREAD_GIL_EXIT(); close(fd); MP_THREAD_GIL_ENTER(); } #endif // MICROPY_PERSISTENT_CODE_SAVE_FILE #endif // MICROPY_PERSISTENT_CODE_SAVE #include #if MICROPY_DEBUG_VERBOSE #ifndef __IPU__ #include #endif #endif #include #include #include "py/parsenum.h" #include "py/compile.h" #include "py/objstr.h" #include "py/objtuple.h" #include "py/objlist.h" #include "py/objtype.h" #include "py/objmodule.h" #include "py/objgenerator.h" #include "py/smallint.h" #include "py/runtime.h" #include "py/builtin.h" #include "py/stackctrl.h" #include "py/gc.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf #define DEBUG_OP_printf(...) DEBUG_printf(__VA_ARGS__) #else // don't print debugging info #define DEBUG_printf(...) (void)0 #define DEBUG_OP_printf(...) (void)0 #endif const mp_obj_module_t mp_module___main__ = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&MP_STATE_VM(dict_main), }; void mp_init(void) { qstr_init(); // no pending exceptions to start with MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; #if MICROPY_ENABLE_SCHEDULER MP_STATE_VM(sched_state) = MP_SCHED_IDLE; MP_STATE_VM(sched_idx) = 0; MP_STATE_VM(sched_len) = 0; #endif #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF mp_init_emergency_exception_buf(); #endif #if MICROPY_KBD_EXCEPTION // initialise the exception object for raising KeyboardInterrupt MP_STATE_VM(mp_kbd_exception).base.type = &mp_type_KeyboardInterrupt; MP_STATE_VM(mp_kbd_exception).traceback_alloc = 0; MP_STATE_VM(mp_kbd_exception).traceback_len = 0; MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; MP_STATE_VM(mp_kbd_exception).args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; #endif #if MICROPY_ENABLE_COMPILER // optimization disabled by default MP_STATE_VM(mp_optimise_value) = 0; #if MICROPY_EMIT_NATIVE MP_STATE_VM(default_emit_opt) = MP_EMIT_OPT_NONE; #endif #endif // init global module dict mp_obj_dict_init(&MP_STATE_VM(mp_loaded_modules_dict), MICROPY_LOADED_MODULES_DICT_SIZE); // initialise the __main__ module mp_obj_dict_init(&MP_STATE_VM(dict_main), 1); mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(dict_main)), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); // locals = globals for outer module (see Objects/frameobject.c/PyFrame_New()) mp_locals_set(&MP_STATE_VM(dict_main)); mp_globals_set(&MP_STATE_VM(dict_main)); #if MICROPY_CAN_OVERRIDE_BUILTINS // start with no extensions to builtins MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE MP_STATE_VM(track_reloc_code_list) = MP_OBJ_NULL; #endif #if MICROPY_PY_OS_DUPTERM for (size_t i = 0; i < MICROPY_PY_OS_DUPTERM; ++i) { MP_STATE_VM(dupterm_objs[i]) = MP_OBJ_NULL; } #endif #if MICROPY_VFS // initialise the VFS sub-system MP_STATE_VM(vfs_cur) = NULL; MP_STATE_VM(vfs_mount_table) = NULL; #endif #if MICROPY_PY_SYS_PATH_ARGV_DEFAULTS mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), 0); mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); // current dir (or base dir of the script) #if MICROPY_MODULE_FROZEN mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen)); #endif mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); #endif #if MICROPY_PY_SYS_ATEXIT MP_STATE_VM(sys_exitfunc) = mp_const_none; #endif #if MICROPY_PY_SYS_PS1_PS2 MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS1]) = MP_OBJ_NEW_QSTR(MP_QSTR__gt__gt__gt__space_); MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS2]) = MP_OBJ_NEW_QSTR(MP_QSTR__dot__dot__dot__space_); #endif #if MICROPY_PY_SYS_SETTRACE MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; MP_STATE_THREAD(prof_callback_is_executing) = false; MP_STATE_THREAD(current_code_state) = NULL; #endif #if MICROPY_PY_SYS_TRACEBACKLIMIT MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT]) = MP_OBJ_NEW_SMALL_INT(1000); #endif #if MICROPY_PY_BLUETOOTH MP_STATE_VM(bluetooth) = MP_OBJ_NULL; #endif #if MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); #endif // call port specific initialization if any #ifdef MICROPY_PORT_INIT_FUNC MICROPY_PORT_INIT_FUNC; #endif MP_THREAD_GIL_ENTER(); } void mp_deinit(void) { MP_THREAD_GIL_EXIT(); // call port specific deinitialization if any #ifdef MICROPY_PORT_DEINIT_FUNC MICROPY_PORT_DEINIT_FUNC; #endif } mp_obj_t MICROPY_WRAP_MP_LOAD_NAME(mp_load_name)(qstr qst) { // logic: search locals, globals, builtins DEBUG_OP_printf("load name %s\n", qstr_str(qst)); // If we're at the outer scope (locals == globals), dispatch to load_global right away if (mp_locals_get() != mp_globals_get()) { mp_map_elem_t *elem = mp_map_lookup(&mp_locals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); if (elem != NULL) { return elem->value; } } return mp_load_global(qst); } mp_obj_t MICROPY_WRAP_MP_LOAD_GLOBAL(mp_load_global)(qstr qst) { // logic: search globals, builtins DEBUG_OP_printf("load global %s\n", qstr_str(qst)); mp_map_elem_t *elem = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); if (elem == NULL) { #if MICROPY_CAN_OVERRIDE_BUILTINS if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { // lookup in additional dynamic table of builtins first elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); if (elem != NULL) { return elem->value; } } #endif elem = mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); if (elem == NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_NameError, MP_ERROR_TEXT("name not defined")); #else mp_raise_msg_varg(&mp_type_NameError, MP_ERROR_TEXT("name '%q' isn't defined"), qst); #endif } } return elem->value; } mp_obj_t mp_load_build_class(void) { DEBUG_OP_printf("load_build_class\n"); #if MICROPY_CAN_OVERRIDE_BUILTINS if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { // lookup in additional dynamic table of builtins first mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(MP_QSTR___build_class__), MP_MAP_LOOKUP); if (elem != NULL) { return elem->value; } } #endif return MP_OBJ_FROM_PTR(&mp_builtin___build_class___obj); } void mp_store_name(qstr qst, mp_obj_t obj) { DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qst), obj); mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst), obj); } void mp_delete_name(qstr qst) { DEBUG_OP_printf("delete name %s\n", qstr_str(qst)); // TODO convert KeyError to NameError if qst not found mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst)); } void mp_store_global(qstr qst, mp_obj_t obj) { DEBUG_OP_printf("store global %s <- %p\n", qstr_str(qst), obj); mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst), obj); } void mp_delete_global(qstr qst) { DEBUG_OP_printf("delete global %s\n", qstr_str(qst)); // TODO convert KeyError to NameError if qst not found mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst)); } mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { DEBUG_OP_printf("unary " UINT_FMT " %q %p\n", op, mp_unary_op_method_name[op], arg); if (op == MP_UNARY_OP_NOT) { // "not x" is the negative of whether "x" is true per Python semantics return mp_obj_new_bool(mp_obj_is_true(arg) == 0); } else if (mp_obj_is_small_int(arg)) { mp_int_t val = MP_OBJ_SMALL_INT_VALUE(arg); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(val != 0); case MP_UNARY_OP_HASH: return arg; case MP_UNARY_OP_POSITIVE: case MP_UNARY_OP_INT: return arg; case MP_UNARY_OP_NEGATIVE: // check for overflow if (val == MP_SMALL_INT_MIN) { return mp_obj_new_int(-val); } else { return MP_OBJ_NEW_SMALL_INT(-val); } case MP_UNARY_OP_ABS: if (val >= 0) { return arg; } else if (val == MP_SMALL_INT_MIN) { // check for overflow return mp_obj_new_int(-val); } else { return MP_OBJ_NEW_SMALL_INT(-val); } default: assert(op == MP_UNARY_OP_INVERT); return MP_OBJ_NEW_SMALL_INT(~val); } } else if (op == MP_UNARY_OP_HASH && mp_obj_is_str_or_bytes(arg)) { // fast path for hashing str/bytes GET_STR_HASH(arg, h); if (h == 0) { GET_STR_DATA_LEN(arg, data, len); h = qstr_compute_hash(data, len); } return MP_OBJ_NEW_SMALL_INT(h); } else { const mp_obj_type_t *type = mp_obj_get_type(arg); if (type->unary_op != NULL) { mp_obj_t result = type->unary_op(op, arg); if (result != MP_OBJ_NULL) { return result; } } if (op == MP_UNARY_OP_BOOL) { // Type doesn't have unary_op (or didn't handle MP_UNARY_OP_BOOL), // so is implicitly True as this code path is impossible to reach // if arg==mp_const_none. return mp_const_true; } // With MP_UNARY_OP_INT, mp_unary_op() becomes a fallback for mp_obj_get_int(). // In this case provide a more focused error message to not confuse, e.g. chr(1.0) #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE if (op == MP_UNARY_OP_INT) { mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); } else { mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); } #else if (op == MP_UNARY_OP_INT) { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert %s to int"), mp_obj_get_type_str(arg)); } else { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unsupported type for %q: '%s'"), mp_unary_op_method_name[op], mp_obj_get_type_str(arg)); } #endif } } mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { DEBUG_OP_printf("binary " UINT_FMT " %q %p %p\n", op, mp_binary_op_method_name[op], lhs, rhs); // TODO correctly distinguish inplace operators for mutable objects // lookup logic that CPython uses for +=: // check for implemented += // then check for implemented + // then check for implemented seq.inplace_concat // then check for implemented seq.concat // then fail // note that list does not implement + or +=, so that inplace_concat is reached first for += // deal with is if (op == MP_BINARY_OP_IS) { return mp_obj_new_bool(lhs == rhs); } // deal with == and != for all types if (op == MP_BINARY_OP_EQUAL || op == MP_BINARY_OP_NOT_EQUAL) { // mp_obj_equal_not_equal supports a bunch of shortcuts return mp_obj_equal_not_equal(op, lhs, rhs); } // deal with exception_match for all types if (op == MP_BINARY_OP_EXCEPTION_MATCH) { // rhs must be issubclass(rhs, BaseException) if (mp_obj_is_exception_type(rhs)) { if (mp_obj_exception_match(lhs, rhs)) { return mp_const_true; } else { return mp_const_false; } } else if (mp_obj_is_type(rhs, &mp_type_tuple)) { mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(rhs); for (size_t i = 0; i < tuple->len; i++) { rhs = tuple->items[i]; if (!mp_obj_is_exception_type(rhs)) { goto unsupported_op; } if (mp_obj_exception_match(lhs, rhs)) { return mp_const_true; } } return mp_const_false; } goto unsupported_op; } if (mp_obj_is_small_int(lhs)) { mp_int_t lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs); if (mp_obj_is_small_int(rhs)) { mp_int_t rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs); // This is a binary operation: lhs_val op rhs_val // We need to be careful to handle overflow; see CERT INT32-C // Operations that can overflow: // + result always fits in mp_int_t, then handled by SMALL_INT check // - result always fits in mp_int_t, then handled by SMALL_INT check // * checked explicitly // / if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check // % if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check // << checked explicitly switch (op) { case MP_BINARY_OP_OR: case MP_BINARY_OP_INPLACE_OR: lhs_val |= rhs_val; break; case MP_BINARY_OP_XOR: case MP_BINARY_OP_INPLACE_XOR: lhs_val ^= rhs_val; break; case MP_BINARY_OP_AND: case MP_BINARY_OP_INPLACE_AND: lhs_val &= rhs_val; break; case MP_BINARY_OP_LSHIFT: case MP_BINARY_OP_INPLACE_LSHIFT: { if (rhs_val < 0) { // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } else if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE) || lhs_val > (MP_SMALL_INT_MAX >> rhs_val) || lhs_val < (MP_SMALL_INT_MIN >> rhs_val)) { // left-shift will overflow, so use higher precision integer lhs = mp_obj_new_int_from_ll(lhs_val); goto generic_binary_op; } else { // use standard precision lhs_val = (mp_uint_t)lhs_val << rhs_val; } break; } case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: if (rhs_val < 0) { // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } else { // standard precision is enough for right-shift if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE)) { // Shifting to big amounts is underfined behavior // in C and is CPU-dependent; propagate sign bit. rhs_val = sizeof(lhs_val) * MP_BITS_PER_BYTE - 1; } lhs_val >>= rhs_val; } break; case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: lhs_val += rhs_val; break; case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_val -= rhs_val; break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: { // If long long type exists and is larger than mp_int_t, then // we can use the following code to perform overflow-checked multiplication. // Otherwise (eg in x64 case) we must use mp_small_int_mul_overflow. #if 0 // compute result using long long precision long long res = (long long)lhs_val * (long long)rhs_val; if (res > MP_SMALL_INT_MAX || res < MP_SMALL_INT_MIN) { // result overflowed SMALL_INT, so return higher precision integer return mp_obj_new_int_from_ll(res); } else { // use standard precision lhs_val = (mp_int_t)res; } #endif if (mp_small_int_mul_overflow(lhs_val, rhs_val)) { // use higher precision lhs = mp_obj_new_int_from_ll(lhs_val); goto generic_binary_op; } else { // use standard precision return MP_OBJ_NEW_SMALL_INT(lhs_val * rhs_val); } } case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { goto zero_division; } lhs_val = mp_small_int_floor_divide(lhs_val, rhs_val); break; #if MICROPY_PY_BUILTINS_FLOAT case MP_BINARY_OP_TRUE_DIVIDE: case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: if (rhs_val == 0) { goto zero_division; } return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); #endif case MP_BINARY_OP_MODULO: case MP_BINARY_OP_INPLACE_MODULO: { if (rhs_val == 0) { goto zero_division; } lhs_val = mp_small_int_modulo(lhs_val, rhs_val); break; } case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: if (rhs_val < 0) { #if MICROPY_PY_BUILTINS_FLOAT return mp_obj_float_binary_op(op, (mp_float_t)lhs_val, rhs); #else mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } else { mp_int_t ans = 1; while (rhs_val > 0) { if (rhs_val & 1) { if (mp_small_int_mul_overflow(ans, lhs_val)) { goto power_overflow; } ans *= lhs_val; } if (rhs_val == 1) { break; } rhs_val /= 2; if (mp_small_int_mul_overflow(lhs_val, lhs_val)) { goto power_overflow; } lhs_val *= lhs_val; } lhs_val = ans; } break; power_overflow: // use higher precision lhs = mp_obj_new_int_from_ll(MP_OBJ_SMALL_INT_VALUE(lhs)); goto generic_binary_op; case MP_BINARY_OP_DIVMOD: { if (rhs_val == 0) { goto zero_division; } // to reduce stack usage we don't pass a temp array of the 2 items mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); tuple->items[0] = MP_OBJ_NEW_SMALL_INT(mp_small_int_floor_divide(lhs_val, rhs_val)); tuple->items[1] = MP_OBJ_NEW_SMALL_INT(mp_small_int_modulo(lhs_val, rhs_val)); return MP_OBJ_FROM_PTR(tuple); } case MP_BINARY_OP_LESS: return mp_obj_new_bool(lhs_val < rhs_val); case MP_BINARY_OP_MORE: return mp_obj_new_bool(lhs_val > rhs_val); case MP_BINARY_OP_LESS_EQUAL: return mp_obj_new_bool(lhs_val <= rhs_val); case MP_BINARY_OP_MORE_EQUAL: return mp_obj_new_bool(lhs_val >= rhs_val); default: goto unsupported_op; } // This is an inlined version of mp_obj_new_int, for speed if (MP_SMALL_INT_FITS(lhs_val)) { return MP_OBJ_NEW_SMALL_INT(lhs_val); } else { return mp_obj_new_int_from_ll(lhs_val); } #if MICROPY_PY_BUILTINS_FLOAT } else if (mp_obj_is_float(rhs)) { mp_obj_t res = mp_obj_float_binary_op(op, (mp_float_t)lhs_val, rhs); if (res == MP_OBJ_NULL) { goto unsupported_op; } else { return res; } #endif #if MICROPY_PY_BUILTINS_COMPLEX } else if (mp_obj_is_type(rhs, &mp_type_complex)) { mp_obj_t res = mp_obj_complex_binary_op(op, (mp_float_t)lhs_val, 0, rhs); if (res == MP_OBJ_NULL) { goto unsupported_op; } else { return res; } #endif } } // Convert MP_BINARY_OP_IN to MP_BINARY_OP_CONTAINS with swapped args. if (op == MP_BINARY_OP_IN) { op = MP_BINARY_OP_CONTAINS; mp_obj_t temp = lhs; lhs = rhs; rhs = temp; } // generic binary_op supplied by type const mp_obj_type_t *type; generic_binary_op: type = mp_obj_get_type(lhs); if (type->binary_op != NULL) { mp_obj_t result = type->binary_op(op, lhs, rhs); if (result != MP_OBJ_NULL) { return result; } } #if MICROPY_PY_REVERSE_SPECIAL_METHODS if (op >= MP_BINARY_OP_OR && op <= MP_BINARY_OP_POWER) { mp_obj_t t = rhs; rhs = lhs; lhs = t; op += MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; goto generic_binary_op; } else if (op >= MP_BINARY_OP_REVERSE_OR) { // Convert __rop__ back to __op__ for error message mp_obj_t t = rhs; rhs = lhs; lhs = t; op -= MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; } #endif if (op == MP_BINARY_OP_CONTAINS) { // If type didn't support containment then explicitly walk the iterator. // mp_getiter will raise the appropriate exception if lhs is not iterable. mp_obj_iter_buf_t iter_buf; mp_obj_t iter = mp_getiter(lhs, &iter_buf); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { if (mp_obj_equal(next, rhs)) { return mp_const_true; } } return mp_const_false; } unsupported_op: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unsupported types for %q: '%s', '%s'"), mp_binary_op_method_name[op], mp_obj_get_type_str(lhs), mp_obj_get_type_str(rhs)); #endif zero_division: mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } mp_obj_t mp_call_function_0(mp_obj_t fun) { return mp_call_function_n_kw(fun, 0, 0, NULL); } mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg) { return mp_call_function_n_kw(fun, 1, 0, &arg); } mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { mp_obj_t args[2]; args[0] = arg1; args[1] = arg2; return mp_call_function_n_kw(fun, 2, 0, args); } // args contains, eg: arg0 arg1 key0 value0 key1 value1 mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // TODO improve this: fun object can specify its type and we parse here the arguments, // passing to the function arrays of fixed and keyword arguments DEBUG_OP_printf("calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, n_args, n_kw, args); // get the type const mp_obj_type_t *type = mp_obj_get_type(fun_in); // do the call if (type->call != NULL) { return type->call(fun_in, n_args, n_kw, args); } #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(fun_in)); #endif } // args contains: fun self/NULL arg(0) ... arg(n_args-2) arg(n_args-1) kw_key(0) kw_val(0) ... kw_key(n_kw-1) kw_val(n_kw-1) // if n_args==0 and n_kw==0 then there are only fun and self/NULL mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args) { DEBUG_OP_printf("call method (fun=%p, self=%p, n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", args[0], args[1], n_args, n_kw, args); int adjust = (args[1] == MP_OBJ_NULL) ? 0 : 1; return mp_call_function_n_kw(args[0], n_args + adjust, n_kw, args + 2 - adjust); } // This function only needs to be exposed externally when in stackless mode. #if !MICROPY_STACKLESS STATIC #endif void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args) { mp_obj_t fun = *args++; mp_obj_t self = MP_OBJ_NULL; if (have_self) { self = *args++; // may be MP_OBJ_NULL } uint n_args = n_args_n_kw & 0xff; uint n_kw = (n_args_n_kw >> 8) & 0xff; mp_obj_t pos_seq = args[n_args + 2 * n_kw]; // may be MP_OBJ_NULL mp_obj_t kw_dict = args[n_args + 2 * n_kw + 1]; // may be MP_OBJ_NULL DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict); // We need to create the following array of objects: // args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict) // TODO: optimize one day to avoid constructing new arg array? Will be hard. // The new args array mp_obj_t *args2; uint args2_alloc; uint args2_len = 0; // Try to get a hint for the size of the kw_dict uint kw_dict_len = 0; if (kw_dict != MP_OBJ_NULL && mp_obj_is_type(kw_dict, &mp_type_dict)) { kw_dict_len = mp_obj_dict_len(kw_dict); } // Extract the pos_seq sequence to the new args array. // Note that it can be arbitrary iterator. if (pos_seq == MP_OBJ_NULL) { // no sequence // allocate memory for the new array of args args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len); args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); // copy the self if (self != MP_OBJ_NULL) { args2[args2_len++] = self; } // copy the fixed pos args mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); args2_len += n_args; } else if (mp_obj_is_type(pos_seq, &mp_type_tuple) || mp_obj_is_type(pos_seq, &mp_type_list)) { // optimise the case of a tuple and list // get the items size_t len; mp_obj_t *items; mp_obj_get_array(pos_seq, &len, &items); // allocate memory for the new array of args args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len); args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); // copy the self if (self != MP_OBJ_NULL) { args2[args2_len++] = self; } // copy the fixed and variable position args mp_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t); args2_len += n_args + len; } else { // generic iterator // allocate memory for the new array of args args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3; args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); // copy the self if (self != MP_OBJ_NULL) { args2[args2_len++] = self; } // copy the fixed position args mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); args2_len += n_args; // extract the variable position args from the iterator mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(pos_seq, &iter_buf); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { if (args2_len >= args2_alloc) { args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), args2_alloc * 2 * sizeof(mp_obj_t)); args2_alloc *= 2; } args2[args2_len++] = item; } } // The size of the args2 array now is the number of positional args. uint pos_args_len = args2_len; // Copy the fixed kw args. mp_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t); args2_len += 2 * n_kw; // Extract (key,value) pairs from kw_dict dictionary and append to args2. // Note that it can be arbitrary iterator. if (kw_dict == MP_OBJ_NULL) { // pass } else if (mp_obj_is_type(kw_dict, &mp_type_dict)) { // dictionary mp_map_t *map = mp_obj_dict_get_map(kw_dict); assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above for (size_t i = 0; i < map->alloc; i++) { if (mp_map_slot_is_filled(map, i)) { // the key must be a qstr, so intern it if it's a string mp_obj_t key = map->table[i].key; if (!mp_obj_is_qstr(key)) { key = mp_obj_str_intern_checked(key); } args2[args2_len++] = key; args2[args2_len++] = map->table[i].value; } } } else { // generic mapping: // - call keys() to get an iterable of all keys in the mapping // - call __getitem__ for each key to get the corresponding value // get the keys iterable mp_obj_t dest[3]; mp_load_method(kw_dict, MP_QSTR_keys, dest); mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL); mp_obj_t key; while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { // expand size of args array if needed if (args2_len + 1 >= args2_alloc) { uint new_alloc = args2_alloc * 2; if (new_alloc < 4) { new_alloc = 4; } args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t)); args2_alloc = new_alloc; } // the key must be a qstr, so intern it if it's a string if (!mp_obj_is_qstr(key)) { key = mp_obj_str_intern_checked(key); } // get the value corresponding to the key mp_load_method(kw_dict, MP_QSTR___getitem__, dest); dest[2] = key; mp_obj_t value = mp_call_method_n_kw(1, 0, dest); // store the key/value pair in the argument array args2[args2_len++] = key; args2[args2_len++] = value; } } out_args->fun = fun; out_args->args = args2; out_args->n_args = pos_args_len; out_args->n_kw = (args2_len - pos_args_len) / 2; out_args->n_alloc = args2_alloc; } mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args) { mp_call_args_t out_args; mp_call_prepare_args_n_kw_var(have_self, n_args_n_kw, args, &out_args); mp_obj_t res = mp_call_function_n_kw(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); return res; } // unpacked items are stored in reverse order into the array pointed to by items void mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) { size_t seq_len; if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { mp_obj_t *seq_items; mp_obj_get_array(seq_in, &seq_len, &seq_items); if (seq_len < num) { goto too_short; } else if (seq_len > num) { goto too_long; } for (size_t i = 0; i < num; i++) { items[i] = seq_items[num - 1 - i]; } } else { mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(seq_in, &iter_buf); for (seq_len = 0; seq_len < num; seq_len++) { mp_obj_t el = mp_iternext(iterable); if (el == MP_OBJ_STOP_ITERATION) { goto too_short; } items[num - 1 - seq_len] = el; } if (mp_iternext(iterable) != MP_OBJ_STOP_ITERATION) { goto too_long; } } return; too_short: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len); #endif too_long: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("too many values to unpack (expected %d)"), (int)num); #endif } // unpacked items are stored in reverse order into the array pointed to by items void mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) { size_t num_left = num_in & 0xff; size_t num_right = (num_in >> 8) & 0xff; DEBUG_OP_printf("unpack ex " UINT_FMT " " UINT_FMT "\n", num_left, num_right); size_t seq_len; if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { // Make the seq variable volatile so the compiler keeps a reference to it, // since if it's a tuple then seq_items points to the interior of the GC cell // and mp_obj_new_list may trigger a GC which doesn't trace this and reclaims seq. volatile mp_obj_t seq = seq_in; mp_obj_t *seq_items; mp_obj_get_array(seq, &seq_len, &seq_items); if (seq_len < num_left + num_right) { goto too_short; } for (size_t i = 0; i < num_right; i++) { items[i] = seq_items[seq_len - 1 - i]; } items[num_right] = mp_obj_new_list(seq_len - num_left - num_right, seq_items + num_left); for (size_t i = 0; i < num_left; i++) { items[num_right + 1 + i] = seq_items[num_left - 1 - i]; } seq = MP_OBJ_NULL; } else { // Generic iterable; this gets a bit messy: we unpack known left length to the // items destination array, then the rest to a dynamically created list. Once the // iterable is exhausted, we take from this list for the right part of the items. // TODO Improve to waste less memory in the dynamically created list. mp_obj_t iterable = mp_getiter(seq_in, NULL); mp_obj_t item; for (seq_len = 0; seq_len < num_left; seq_len++) { item = mp_iternext(iterable); if (item == MP_OBJ_STOP_ITERATION) { goto too_short; } items[num_left + num_right + 1 - 1 - seq_len] = item; } mp_obj_list_t *rest = MP_OBJ_TO_PTR(mp_obj_new_list(0, NULL)); while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { mp_obj_list_append(MP_OBJ_FROM_PTR(rest), item); } if (rest->len < num_right) { goto too_short; } items[num_right] = MP_OBJ_FROM_PTR(rest); for (size_t i = 0; i < num_right; i++) { items[num_right - 1 - i] = rest->items[rest->len - num_right + i]; } mp_obj_list_set_len(MP_OBJ_FROM_PTR(rest), rest->len - num_right); } return; too_short: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len); #endif } mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) { DEBUG_OP_printf("load attr %p.%s\n", base, qstr_str(attr)); // use load_method mp_obj_t dest[2]; mp_load_method(base, attr, dest); if (dest[1] == MP_OBJ_NULL) { // load_method returned just a normal attribute return dest[0]; } else { // load_method returned a method, so build a bound method object return mp_obj_new_bound_meth(dest[0], dest[1]); } } #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG // The following "checked fun" type is local to the mp_convert_member_lookup // function, and serves to check that the first argument to a builtin function // has the correct type. typedef struct _mp_obj_checked_fun_t { mp_obj_base_t base; const mp_obj_type_t *type; mp_obj_t fun; } mp_obj_checked_fun_t; STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_checked_fun_t *self = MP_OBJ_TO_PTR(self_in); if (n_args > 0) { const mp_obj_type_t *arg0_type = mp_obj_get_type(args[0]); if (arg0_type != self->type) { #if MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_DETAILED mp_raise_TypeError(MP_ERROR_TEXT("argument has wrong type")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("argument should be a '%q' not a '%q'"), self->type->name, arg0_type->name); #endif } } return mp_call_function_n_kw(self->fun, n_args, n_kw, args); } STATIC const mp_obj_type_t mp_type_checked_fun = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = checked_fun_call, }; STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) { mp_obj_checked_fun_t *o = m_new_obj(mp_obj_checked_fun_t); o->base.type = &mp_type_checked_fun; o->type = type; o->fun = fun; return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG // Given a member that was extracted from an instance, convert it correctly // and put the result in the dest[] array for a possible method call. // Conversion means dealing with static/class methods, callables, and values. // see http://docs.python.org/3/howto/descriptor.html // and also https://mail.python.org/pipermail/python-dev/2015-March/138950.html void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest) { if (mp_obj_is_obj(member)) { const mp_obj_type_t *m_type = ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type; if (m_type->flags & MP_TYPE_FLAG_BINDS_SELF) { // `member` is a function that binds self as its first argument. if (m_type->flags & MP_TYPE_FLAG_BUILTIN_FUN) { // `member` is a built-in function, which has special behaviour. if (mp_obj_is_instance_type(type)) { // Built-in functions on user types always behave like a staticmethod. dest[0] = member; } #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG else if (self == MP_OBJ_NULL && type != &mp_type_object) { // `member` is a built-in method without a first argument, so wrap // it in a type checker that will check self when it's supplied. // Note that object will do its own checking so shouldn't be wrapped. dest[0] = mp_obj_new_checked_fun(type, member); } #endif else { // Return a (built-in) bound method, with self being this object. dest[0] = member; dest[1] = self; } } else { // Return a bound method, with self being this object. dest[0] = member; dest[1] = self; } } else if (m_type == &mp_type_staticmethod) { // `member` is a staticmethod, return the function that it wraps. dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; } else if (m_type == &mp_type_classmethod) { // `member` is a classmethod, return a bound method with self being the type of // this object. This type should be the type of the original instance, not the // base type (which is what is passed in the `type` argument to this function). if (self != MP_OBJ_NULL) { type = mp_obj_get_type(self); } dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; dest[1] = MP_OBJ_FROM_PTR(type); } else { // `member` is a value, so just return that value. dest[0] = member; } } else { // `member` is a value, so just return that value. dest[0] = member; } } // no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL // normal attribute found, returns: dest[0] == , dest[1] == MP_OBJ_NULL // method attribute found, returns: dest[0] == , dest[1] == void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { // clear output to indicate no attribute/method found yet dest[0] = MP_OBJ_NULL; dest[1] = MP_OBJ_NULL; // Note: the specific case of obj being an instance type is fast-path'ed in the VM // for the MP_BC_LOAD_ATTR opcode. Instance types handle type->attr and look up directly // in their member's map. // get the type const mp_obj_type_t *type = mp_obj_get_type(obj); // look for built-in names #if MICROPY_CPYTHON_COMPAT if (attr == MP_QSTR___class__) { // a.__class__ is equivalent to type(a) dest[0] = MP_OBJ_FROM_PTR(type); return; } #endif if (attr == MP_QSTR___next__ && type->iternext != NULL) { dest[0] = MP_OBJ_FROM_PTR(&mp_builtin_next_obj); dest[1] = obj; return; } if (type->attr != NULL) { // this type can do its own load, so call it type->attr(obj, attr, dest); // If type->attr has set dest[1] = MP_OBJ_SENTINEL, we should proceed // with lookups below (i.e. in locals_dict). If not, return right away. if (dest[1] != MP_OBJ_SENTINEL) { return; } // Clear the fail flag set by type->attr so it's like it never ran. dest[1] = MP_OBJ_NULL; } if (type->locals_dict != NULL) { // generic method lookup // this is a lookup in the object (ie not class or type) assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now mp_map_t *locals_map = &type->locals_dict->map; mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { mp_convert_member_lookup(obj, type, elem->value, dest); } return; } } void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { DEBUG_OP_printf("load method %p.%s\n", base, qstr_str(attr)); mp_load_method_maybe(base, attr, dest); if (dest[0] == MP_OBJ_NULL) { // no attribute/method called attr #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); #else // following CPython, we give a more detailed error message for type objects if (mp_obj_is_type(base, &mp_type_type)) { mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("type object '%q' has no attribute '%q'"), ((mp_obj_type_t *)MP_OBJ_TO_PTR(base))->name, attr); } else { mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("'%s' object has no attribute '%q'"), mp_obj_get_type_str(base), attr); } #endif } } // Acts like mp_load_method_maybe but catches AttributeError, and all other exceptions if requested void mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_load_method_maybe(obj, attr, dest); nlr_pop(); } else { if (!catch_all_exc && !mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_AttributeError))) { // Re-raise the exception nlr_raise(MP_OBJ_FROM_PTR(nlr.ret_val)); } } } void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) { DEBUG_OP_printf("store attr %p.%s <- %p\n", base, qstr_str(attr), value); const mp_obj_type_t *type = mp_obj_get_type(base); if (type->attr != NULL) { mp_obj_t dest[2] = {MP_OBJ_SENTINEL, value}; type->attr(base, attr, dest); if (dest[0] == MP_OBJ_NULL) { // success return; } } #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); #else mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("'%s' object has no attribute '%q'"), mp_obj_get_type_str(base), attr); #endif } mp_obj_t mp_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { assert(o_in); const mp_obj_type_t *type = mp_obj_get_type(o_in); // Check for native getiter which is the identity. We handle this case explicitly // so we don't unnecessarily allocate any RAM for the iter_buf, which won't be used. if (type->getiter == mp_identity_getiter) { return o_in; } // check for native getiter (corresponds to __iter__) if (type->getiter != NULL) { if (iter_buf == NULL && type->getiter != mp_obj_instance_getiter) { // if caller did not provide a buffer then allocate one on the heap // mp_obj_instance_getiter is special, it will allocate only if needed iter_buf = m_new_obj(mp_obj_iter_buf_t); } mp_obj_t iter = type->getiter(o_in, iter_buf); if (iter != MP_OBJ_NULL) { return iter; } } // check for __getitem__ mp_obj_t dest[2]; mp_load_method_maybe(o_in, MP_QSTR___getitem__, dest); if (dest[0] != MP_OBJ_NULL) { // __getitem__ exists, create and return an iterator if (iter_buf == NULL) { // if caller did not provide a buffer then allocate one on the heap iter_buf = m_new_obj(mp_obj_iter_buf_t); } return mp_obj_new_getitem_iter(dest, iter_buf); } // object not iterable #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not iterable")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't iterable"), mp_obj_get_type_str(o_in)); #endif } // may return MP_OBJ_STOP_ITERATION as an optimisation instead of raise StopIteration() // may also raise StopIteration() mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->iternext != NULL) { MP_STATE_THREAD(stop_iteration_arg) = MP_OBJ_NULL; return type->iternext(o_in); } else { // check for __next__ method mp_obj_t dest[2]; mp_load_method_maybe(o_in, MP_QSTR___next__, dest); if (dest[0] != MP_OBJ_NULL) { // __next__ exists, call it and return its result return mp_call_method_n_kw(0, 0, dest); } else { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in)); #endif } } } // will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration() (or any subclass thereof) // may raise other exceptions mp_obj_t mp_iternext(mp_obj_t o_in) { MP_STACK_CHECK(); // enumerate, filter, map and zip can recursively call mp_iternext const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->iternext != NULL) { MP_STATE_THREAD(stop_iteration_arg) = MP_OBJ_NULL; return type->iternext(o_in); } else { // check for __next__ method mp_obj_t dest[2]; mp_load_method_maybe(o_in, MP_QSTR___next__, dest); if (dest[0] != MP_OBJ_NULL) { // __next__ exists, call it and return its result nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_method_n_kw(0, 0, dest); nlr_pop(); return ret; } else { if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { return mp_make_stop_iteration(mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val))); } else { nlr_jump(nlr.ret_val); } } } else { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in)); #endif } } } mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { assert((send_value != MP_OBJ_NULL) ^ (throw_value != MP_OBJ_NULL)); const mp_obj_type_t *type = mp_obj_get_type(self_in); if (type == &mp_type_gen_instance) { return mp_obj_gen_resume(self_in, send_value, throw_value, ret_val); } if (type->iternext != NULL && send_value == mp_const_none) { MP_STATE_THREAD(stop_iteration_arg) = MP_OBJ_NULL; mp_obj_t ret = type->iternext(self_in); *ret_val = ret; if (ret != MP_OBJ_STOP_ITERATION) { return MP_VM_RETURN_YIELD; } else { // The generator is finished. // This is an optimised "raise StopIteration(*ret_val)". *ret_val = MP_STATE_THREAD(stop_iteration_arg); if (*ret_val == MP_OBJ_NULL) { *ret_val = mp_const_none; } return MP_VM_RETURN_NORMAL; } } mp_obj_t dest[3]; // Reserve slot for send() arg // Python instance iterator protocol if (send_value == mp_const_none) { mp_load_method_maybe(self_in, MP_QSTR___next__, dest); if (dest[0] != MP_OBJ_NULL) { *ret_val = mp_call_method_n_kw(0, 0, dest); return MP_VM_RETURN_YIELD; } } // Either python instance generator protocol, or native object // generator protocol. if (send_value != MP_OBJ_NULL) { mp_load_method(self_in, MP_QSTR_send, dest); dest[2] = send_value; *ret_val = mp_call_method_n_kw(1, 0, dest); return MP_VM_RETURN_YIELD; } assert(throw_value != MP_OBJ_NULL); { if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(throw_value)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { mp_load_method_maybe(self_in, MP_QSTR_close, dest); if (dest[0] != MP_OBJ_NULL) { // TODO: Exceptions raised in close() are not propagated, // printed to sys.stderr *ret_val = mp_call_method_n_kw(0, 0, dest); // We assume one can't "yield" from close() return MP_VM_RETURN_NORMAL; } } else { mp_load_method_maybe(self_in, MP_QSTR_throw, dest); if (dest[0] != MP_OBJ_NULL) { dest[2] = throw_value; *ret_val = mp_call_method_n_kw(1, 0, dest); // If .throw() method returned, we assume it's value to yield // - any exception would be thrown with nlr_raise(). return MP_VM_RETURN_YIELD; } } // If there's nowhere to throw exception into, then we assume that object // is just incapable to handle it, so any exception thrown into it // will be propagated up. This behavior is approved by test_pep380.py // test_delegation_of_close_to_non_generator(), // test_delegating_throw_to_non_generator() if (mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); } else { *ret_val = mp_make_raise_obj(throw_value); } return MP_VM_RETURN_EXCEPTION; } } mp_obj_t mp_make_raise_obj(mp_obj_t o) { DEBUG_printf("raise %p\n", o); if (mp_obj_is_exception_type(o)) { // o is an exception type (it is derived from BaseException (or is BaseException)) // create and return a new exception instance by calling o // TODO could have an option to disable traceback, then builtin exceptions (eg TypeError) // could have const instances in ROM which we return here instead o = mp_call_function_n_kw(o, 0, 0, NULL); } if (mp_obj_is_exception_instance(o)) { // o is an instance of an exception, so use it as the exception return o; } else { // o cannot be used as an exception, so return a type error (which will be raised by the caller) return mp_obj_new_exception_msg(&mp_type_TypeError, MP_ERROR_TEXT("exceptions must derive from BaseException")); } } mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { DEBUG_printf("import name '%s' level=%d\n", qstr_str(name), MP_OBJ_SMALL_INT_VALUE(level)); // build args array mp_obj_t args[5]; args[0] = MP_OBJ_NEW_QSTR(name); args[1] = mp_const_none; // TODO should be globals args[2] = mp_const_none; // TODO should be locals args[3] = fromlist; args[4] = level; #if MICROPY_CAN_OVERRIDE_BUILTINS // Lookup __import__ and call that if it exists mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); if (bo_dict != NULL) { mp_map_elem_t *import = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); if (import != NULL) { return mp_call_function_n_kw(import->value, 5, 0, args); } } #endif return mp_builtin___import__(5, args); } mp_obj_t mp_import_from(mp_obj_t module, qstr name) { DEBUG_printf("import from %p %s\n", module, qstr_str(name)); mp_obj_t dest[2]; mp_load_method_maybe(module, name, dest); if (dest[1] != MP_OBJ_NULL) { // Hopefully we can't import bound method from an object import_error: mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name); } if (dest[0] != MP_OBJ_NULL) { return dest[0]; } #if MICROPY_ENABLE_EXTERNAL_IMPORT // See if it's a package, then can try FS import if (!mp_obj_is_package(module)) { goto import_error; } mp_load_method_maybe(module, MP_QSTR___name__, dest); size_t pkg_name_len; const char *pkg_name = mp_obj_str_get_data(dest[0], &pkg_name_len); const uint dot_name_len = pkg_name_len + 1 + qstr_len(name); char *dot_name = mp_local_alloc(dot_name_len); memcpy(dot_name, pkg_name, pkg_name_len); dot_name[pkg_name_len] = '.'; memcpy(dot_name + pkg_name_len + 1, qstr_str(name), qstr_len(name)); qstr dot_name_q = qstr_from_strn(dot_name, dot_name_len); mp_local_free(dot_name); // For fromlist, pass sentinel "non empty" value to force returning of leaf module return mp_import_name(dot_name_q, mp_const_true, MP_OBJ_NEW_SMALL_INT(0)); #else // Package import not supported with external imports disabled goto import_error; #endif } void mp_import_all(mp_obj_t module) { DEBUG_printf("import all %p\n", module); // TODO: Support __all__ mp_map_t *map = &mp_obj_module_get_globals(module)->map; for (size_t i = 0; i < map->alloc; i++) { if (mp_map_slot_is_filled(map, i)) { // Entry in module global scope may be generated programmatically // (and thus be not a qstr for longer names). Avoid turning it in // qstr if it has '_' and was used exactly to save memory. const char *name = mp_obj_str_get_str(map->table[i].key); if (*name != '_') { qstr qname = mp_obj_str_get_qstr(map->table[i].key); mp_store_name(qname, map->table[i].value); } } } } #if MICROPY_ENABLE_COMPILER mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_input_kind, mp_obj_dict_t *globals, mp_obj_dict_t *locals) { // save context mp_obj_dict_t *volatile old_globals = mp_globals_get(); mp_obj_dict_t *volatile old_locals = mp_locals_get(); // set new context mp_globals_set(globals); mp_locals_set(locals); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, parse_input_kind); mp_obj_t module_fun = mp_compile(&parse_tree, source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT); mp_obj_t ret; if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { // for compile only, return value is the module function ret = module_fun; } else { // execute module function and get return value ret = mp_call_function_0(module_fun); } // finish nlr block, restore context and return value nlr_pop(); mp_globals_set(old_globals); mp_locals_set(old_locals); return ret; } else { // exception; restore context and re-raise same exception mp_globals_set(old_globals); mp_locals_set(old_locals); nlr_jump(nlr.ret_val); } } #endif // MICROPY_ENABLE_COMPILER NORETURN void m_malloc_fail(size_t num_bytes) { DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); #if MICROPY_ENABLE_GC if (gc_is_locked()) { mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")); } #endif mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes); } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { nlr_raise(mp_obj_new_exception(exc_type)); } NORETURN void mp_raise_ValueError_no_msg(void) { mp_raise_type(&mp_type_ValueError); } NORETURN void mp_raise_TypeError_no_msg(void) { mp_raise_type(&mp_type_TypeError); } NORETURN void mp_raise_NotImplementedError_no_msg(void) { mp_raise_type(&mp_type_NotImplementedError); } #else NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { if (msg == NULL) { nlr_raise(mp_obj_new_exception(exc_type)); } else { nlr_raise(mp_obj_new_exception_msg(exc_type, msg)); } } mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, va_list args); NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { va_list args; va_start(args, fmt); mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); va_end(args); nlr_raise(exc); } NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_ValueError, msg); } NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_TypeError, msg); } NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_NotImplementedError, msg); } #endif NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg) { nlr_raise(mp_obj_new_exception_arg1(exc_type, arg)); } NORETURN void mp_raise_StopIteration(mp_obj_t arg) { if (arg == MP_OBJ_NULL) { mp_raise_type(&mp_type_StopIteration); } else { mp_raise_type_arg(&mp_type_StopIteration, arg); } } NORETURN void mp_raise_OSError(int errno_) { mp_raise_type_arg(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_)); } #if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK NORETURN void mp_raise_recursion_depth(void) { mp_raise_type_arg(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded)); } #endif /* * Copyright (c) 2015 Josef Gajdusek * Copyright (c) 2015 Paul Sokolovsky */ #include "py/runtime.h" mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_function_1(fun, arg); nlr_pop(); return ret; } else { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); return MP_OBJ_NULL; } } mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_function_2(fun, arg1, arg2); nlr_pop(); return ret; } else { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); return MP_OBJ_NULL; } } #ifndef __IPU__ #include #endif #include "py/runtime.h" // Schedules an exception on the main thread (for exceptions "thrown" by async // sources such as interrupts and UNIX signal handlers). void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) { MP_STATE_MAIN_THREAD(mp_pending_exception) = exc; #if MICROPY_ENABLE_SCHEDULER if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } #endif } #if MICROPY_KBD_EXCEPTION // This function may be called asynchronously at any time so only do the bare minimum. void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) { MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; mp_sched_exception(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); } #endif #if MICROPY_ENABLE_SCHEDULER #define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1)) // This is a macro so it is guaranteed to be inlined in functions like // mp_sched_schedule that may be located in a special memory region. #define mp_sched_full() (mp_sched_num_pending() == MICROPY_SCHEDULER_DEPTH) static inline bool mp_sched_empty(void) { MP_STATIC_ASSERT(MICROPY_SCHEDULER_DEPTH <= 255); // MICROPY_SCHEDULER_DEPTH must fit in 8 bits MP_STATIC_ASSERT((IDX_MASK(MICROPY_SCHEDULER_DEPTH) == 0)); // MICROPY_SCHEDULER_DEPTH must be a power of 2 return mp_sched_num_pending() == 0; } // A variant of this is inlined in the VM at the pending exception check void mp_handle_pending(bool raise_exc) { if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); // Re-check state is still pending now that we're in the atomic section. if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); if (obj != MP_OBJ_NULL) { MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; if (!mp_sched_num_pending()) { MP_STATE_VM(sched_state) = MP_SCHED_IDLE; } if (raise_exc) { MICROPY_END_ATOMIC_SECTION(atomic_state); nlr_raise(obj); } } mp_handle_pending_tail(atomic_state); } else { MICROPY_END_ATOMIC_SECTION(atomic_state); } } } // This function should only be called by mp_handle_pending, // or by the VM's inlined version of that function. void mp_handle_pending_tail(mp_uint_t atomic_state) { MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; if (!mp_sched_empty()) { mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)]; MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1); --MP_STATE_VM(sched_len); MICROPY_END_ATOMIC_SECTION(atomic_state); mp_call_function_1_protected(item.func, item.arg); } else { MICROPY_END_ATOMIC_SECTION(atomic_state); } mp_sched_unlock(); } void mp_sched_lock(void) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); if (MP_STATE_VM(sched_state) < 0) { --MP_STATE_VM(sched_state); } else { MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; } MICROPY_END_ATOMIC_SECTION(atomic_state); } void mp_sched_unlock(void) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); assert(MP_STATE_VM(sched_state) < 0); if (++MP_STATE_VM(sched_state) == 0) { // vm became unlocked if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL || mp_sched_num_pending()) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } else { MP_STATE_VM(sched_state) = MP_SCHED_IDLE; } } MICROPY_END_ATOMIC_SECTION(atomic_state); } bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); bool ret; if (!mp_sched_full()) { if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++); MP_STATE_VM(sched_queue)[iput].func = function; MP_STATE_VM(sched_queue)[iput].arg = arg; MICROPY_SCHED_HOOK_SCHEDULED; ret = true; } else { // schedule queue is full ret = false; } MICROPY_END_ATOMIC_SECTION(atomic_state); return ret; } #else // MICROPY_ENABLE_SCHEDULER // A variant of this is inlined in the VM at the pending exception check void mp_handle_pending(bool raise_exc) { if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; if (raise_exc) { nlr_raise(obj); } } } #endif // MICROPY_ENABLE_SCHEDULER #include #ifndef __IPU__ #include #endif #include #include #include "py/runtime.h" #include "py/smallint.h" #include "py/nativeglue.h" #include "py/gc.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_printf(...) (void)0 #endif #if MICROPY_EMIT_NATIVE int mp_native_type_from_qstr(qstr qst) { switch (qst) { case MP_QSTR_object: return MP_NATIVE_TYPE_OBJ; case MP_QSTR_bool: return MP_NATIVE_TYPE_BOOL; case MP_QSTR_int: return MP_NATIVE_TYPE_INT; case MP_QSTR_uint: return MP_NATIVE_TYPE_UINT; case MP_QSTR_ptr: return MP_NATIVE_TYPE_PTR; case MP_QSTR_ptr8: return MP_NATIVE_TYPE_PTR8; case MP_QSTR_ptr16: return MP_NATIVE_TYPE_PTR16; case MP_QSTR_ptr32: return MP_NATIVE_TYPE_PTR32; default: return -1; } } // convert a MicroPython object to a valid native value based on type mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type) { DEBUG_printf("mp_native_from_obj(%p, " UINT_FMT ")\n", obj, type); switch (type & 0xf) { case MP_NATIVE_TYPE_OBJ: return (mp_uint_t)obj; case MP_NATIVE_TYPE_BOOL: return mp_obj_is_true(obj); case MP_NATIVE_TYPE_INT: case MP_NATIVE_TYPE_UINT: return mp_obj_get_int_truncated(obj); default: { // cast obj to a pointer mp_buffer_info_t bufinfo; if (mp_get_buffer(obj, &bufinfo, MP_BUFFER_READ)) { return (mp_uint_t)bufinfo.buf; } else { // assume obj is an integer that represents an address return mp_obj_get_int_truncated(obj); } } } } #endif #if MICROPY_EMIT_MACHINE_CODE // convert a native value to a MicroPython object based on type mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type) { DEBUG_printf("mp_native_to_obj(" UINT_FMT ", " UINT_FMT ")\n", val, type); switch (type & 0xf) { case MP_NATIVE_TYPE_OBJ: return (mp_obj_t)val; case MP_NATIVE_TYPE_BOOL: return mp_obj_new_bool(val); case MP_NATIVE_TYPE_INT: return mp_obj_new_int(val); case MP_NATIVE_TYPE_UINT: return mp_obj_new_int_from_uint(val); default: // a pointer // we return just the value of the pointer as an integer return mp_obj_new_int_from_uint(val); } } #endif #if MICROPY_EMIT_NATIVE && !MICROPY_DYNAMIC_COMPILER #if !MICROPY_PY_BUILTINS_SET mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { (void)n_args; (void)items; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("set unsupported")); } void mp_obj_set_store(mp_obj_t self_in, mp_obj_t item) { (void)self_in; (void)item; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("set unsupported")); } #endif #if !MICROPY_PY_BUILTINS_SLICE mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) { (void)ostart; (void)ostop; (void)ostep; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("slice unsupported")); } #endif STATIC mp_obj_dict_t *mp_native_swap_globals(mp_obj_dict_t *new_globals) { if (new_globals == NULL) { // Globals were the originally the same so don't restore them return NULL; } mp_obj_dict_t *old_globals = mp_globals_get(); if (old_globals == new_globals) { // Don't set globals if they are the same, and return NULL to indicate this return NULL; } mp_globals_set(new_globals); return old_globals; } // wrapper that accepts n_args and n_kw in one argument // (native emitter can only pass at most 3 arguments to a function) STATIC mp_obj_t mp_native_call_function_n_kw(mp_obj_t fun_in, size_t n_args_kw, const mp_obj_t *args) { return mp_call_function_n_kw(fun_in, n_args_kw & 0xff, (n_args_kw >> 8) & 0xff, args); } // wrapper that makes raise obj and raises it // END_FINALLY opcode requires that we don't raise if o==None STATIC void mp_native_raise(mp_obj_t o) { if (o != MP_OBJ_NULL && o != mp_const_none) { nlr_raise(mp_make_raise_obj(o)); } } // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { if (iter == NULL) { return mp_getiter(obj, NULL); } else { obj = mp_getiter(obj, iter); if (obj != MP_OBJ_FROM_PTR(iter)) { // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. iter->base.type = MP_OBJ_NULL; iter->buf[0] = obj; } return NULL; } } // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_iternext(mp_obj_iter_buf_t *iter) { mp_obj_t obj; if (iter->base.type == MP_OBJ_NULL) { obj = iter->buf[0]; } else { obj = MP_OBJ_FROM_PTR(iter); } return mp_iternext(obj); } STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value) { mp_vm_return_kind_t ret_kind; nlr_buf_t nlr_buf; mp_obj_t throw_value = *ret_value; if (nlr_push(&nlr_buf) == 0) { if (throw_value != MP_OBJ_NULL) { send_value = MP_OBJ_NULL; } ret_kind = mp_resume(gen, send_value, throw_value, ret_value); nlr_pop(); } else { ret_kind = MP_VM_RETURN_EXCEPTION; *ret_value = nlr_buf.ret_val; } if (ret_kind == MP_VM_RETURN_YIELD) { return true; } else if (ret_kind == MP_VM_RETURN_NORMAL) { if (*ret_value == MP_OBJ_STOP_ITERATION) { *ret_value = mp_const_none; } } else { assert(ret_kind == MP_VM_RETURN_EXCEPTION); if (!mp_obj_exception_match(*ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { nlr_raise(*ret_value); } *ret_value = mp_obj_exception_get_value(*ret_value); } if (throw_value != MP_OBJ_NULL && mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { nlr_raise(mp_make_raise_obj(throw_value)); } return false; } #if !MICROPY_PY_BUILTINS_FLOAT STATIC mp_obj_t mp_obj_new_float_from_f(float f) { (void)f; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("float unsupported")); } STATIC mp_obj_t mp_obj_new_float_from_d(double d) { (void)d; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("float unsupported")); } STATIC float mp_obj_get_float_to_f(mp_obj_t o) { (void)o; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("float unsupported")); } STATIC double mp_obj_get_float_to_d(mp_obj_t o) { (void)o; mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("float unsupported")); } #endif // these must correspond to the respective enum in nativeglue.h const mp_fun_table_t mp_fun_table = { mp_const_none, mp_const_false, mp_const_true, mp_native_from_obj, mp_native_to_obj, mp_native_swap_globals, mp_load_name, mp_load_global, mp_load_build_class, mp_load_attr, mp_load_method, mp_load_super_method, mp_store_name, mp_store_global, mp_store_attr, mp_obj_subscr, mp_obj_is_true, mp_unary_op, mp_binary_op, mp_obj_new_tuple, mp_obj_new_list, mp_obj_new_dict, mp_obj_new_set, mp_obj_set_store, mp_obj_list_append, mp_obj_dict_store, mp_make_function_from_raw_code, mp_native_call_function_n_kw, mp_call_method_n_kw, mp_call_method_n_kw_var, mp_native_getiter, mp_native_iternext, #if MICROPY_NLR_SETJMP nlr_push_tail, #else nlr_push, #endif nlr_pop, mp_native_raise, mp_import_name, mp_import_from, mp_import_all, mp_obj_new_slice, mp_unpack_sequence, mp_unpack_ex, mp_delete_name, mp_delete_global, mp_obj_new_closure, mp_arg_check_num_sig, mp_setup_code_state, mp_small_int_floor_divide, mp_small_int_modulo, mp_native_yield_from, #if MICROPY_NLR_SETJMP setjmp, #else NULL, #endif // Additional entries for dynamic runtime, starts at index 50 memset, memmove, gc_realloc, mp_printf, mp_vprintf, mp_raise_msg, mp_obj_get_type, mp_obj_new_str, mp_obj_new_bytes, mp_obj_new_bytearray_by_ref, mp_obj_new_float_from_f, mp_obj_new_float_from_d, mp_obj_get_float_to_f, mp_obj_get_float_to_d, mp_get_buffer_raise, mp_get_stream_raise, &mp_plat_print, &mp_type_type, &mp_type_str, &mp_type_list, &mp_type_dict, &mp_type_fun_builtin_0, &mp_type_fun_builtin_1, &mp_type_fun_builtin_2, &mp_type_fun_builtin_3, &mp_type_fun_builtin_var, &mp_stream_read_obj, &mp_stream_readinto_obj, &mp_stream_unbuffered_readline_obj, &mp_stream_write_obj, }; #elif MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER const int mp_fun_table; #endif // MICROPY_EMIT_NATIVE /* * Copyright (c) 2020 Damien P. George */ #include "py/pairheap.h" // The mp_pairheap_t.next pointer can take one of the following values: // - NULL: the node is the top of the heap // - LSB set: the node is the last of the children and points to its parent node // - other: the node is a child and not the last child // The macros below help manage this pointer. #define NEXT_MAKE_RIGHTMOST_PARENT(parent) ((void *)((uintptr_t)(parent) | 1)) #define NEXT_IS_RIGHTMOST_PARENT(next) ((uintptr_t)(next) & 1) #define NEXT_GET_RIGHTMOST_PARENT(next) ((void *)((uintptr_t)(next) & ~1)) // O(1), stable mp_pairheap_t *mp_pairheap_meld(mp_pairheap_lt_t lt, mp_pairheap_t *heap1, mp_pairheap_t *heap2) { if (heap1 == NULL) { return heap2; } if (heap2 == NULL) { return heap1; } if (lt(heap1, heap2)) { if (heap1->child == NULL) { heap1->child = heap2; } else { heap1->child_last->next = heap2; } heap1->child_last = heap2; heap2->next = NEXT_MAKE_RIGHTMOST_PARENT(heap1); return heap1; } else { heap1->next = heap2->child; heap2->child = heap1; if (heap1->next == NULL) { heap2->child_last = heap1; heap1->next = NEXT_MAKE_RIGHTMOST_PARENT(heap2); } return heap2; } } // amortised O(log N), stable mp_pairheap_t *mp_pairheap_pairing(mp_pairheap_lt_t lt, mp_pairheap_t *child) { if (child == NULL) { return NULL; } mp_pairheap_t *heap = NULL; while (!NEXT_IS_RIGHTMOST_PARENT(child)) { mp_pairheap_t *n1 = child; child = child->next; n1->next = NULL; if (!NEXT_IS_RIGHTMOST_PARENT(child)) { mp_pairheap_t *n2 = child; child = child->next; n2->next = NULL; n1 = mp_pairheap_meld(lt, n1, n2); } heap = mp_pairheap_meld(lt, heap, n1); } heap->next = NULL; return heap; } // amortised O(log N), stable mp_pairheap_t *mp_pairheap_delete(mp_pairheap_lt_t lt, mp_pairheap_t *heap, mp_pairheap_t *node) { // Simple case of the top being the node to delete if (node == heap) { mp_pairheap_t *child = heap->child; node->child = NULL; return mp_pairheap_pairing(lt, child); } // Case where node is not in the heap if (node->next == NULL) { return heap; } // Find parent of node mp_pairheap_t *parent = node; while (!NEXT_IS_RIGHTMOST_PARENT(parent->next)) { parent = parent->next; } parent = NEXT_GET_RIGHTMOST_PARENT(parent->next); // Replace node with pairing of its children mp_pairheap_t *next; if (node == parent->child && node->child == NULL) { if (NEXT_IS_RIGHTMOST_PARENT(node->next)) { parent->child = NULL; } else { parent->child = node->next; } node->next = NULL; return heap; } else if (node == parent->child) { mp_pairheap_t *child = node->child; next = node->next; node->child = NULL; node->next = NULL; node = mp_pairheap_pairing(lt, child); parent->child = node; } else { mp_pairheap_t *n = parent->child; while (node != n->next) { n = n->next; } mp_pairheap_t *child = node->child; next = node->next; node->child = NULL; node->next = NULL; node = mp_pairheap_pairing(lt, child); if (node == NULL) { node = n; } else { n->next = node; } } node->next = next; if (NEXT_IS_RIGHTMOST_PARENT(next)) { parent->child_last = node; } return heap; } #include "ringbuf.h" int ringbuf_get16(ringbuf_t *r) { int v = ringbuf_peek16(r); if (v == -1) { return v; } r->iget += 2; if (r->iget >= r->size) { r->iget -= r->size; } return v; } int ringbuf_peek16(ringbuf_t *r) { if (r->iget == r->iput) { return -1; } uint32_t iget_a = r->iget + 1; if (iget_a == r->size) { iget_a = 0; } if (iget_a == r->iput) { return -1; } return (r->buf[r->iget] << 8) | (r->buf[iget_a]); } int ringbuf_put16(ringbuf_t *r, uint16_t v) { uint32_t iput_a = r->iput + 1; if (iput_a == r->size) { iput_a = 0; } if (iput_a == r->iget) { return -1; } uint32_t iput_b = iput_a + 1; if (iput_b == r->size) { iput_b = 0; } if (iput_b == r->iget) { return -1; } r->buf[r->iput] = (v >> 8) & 0xff; r->buf[iput_a] = v & 0xff; r->iput = iput_b; return 0; } #include "py/runtime.h" #include "py/stackctrl.h" void mp_stack_ctrl_init(void) { volatile int stack_dummy; MP_STATE_THREAD(stack_top) = (char *)&stack_dummy; } void mp_stack_set_top(void *top) { MP_STATE_THREAD(stack_top) = top; } mp_uint_t mp_stack_usage(void) { // Assumes descending stack volatile int stack_dummy; return MP_STATE_THREAD(stack_top) - (char *)&stack_dummy; } #if MICROPY_STACK_CHECK void mp_stack_set_limit(mp_uint_t limit) { MP_STATE_THREAD(stack_limit) = limit; } void mp_stack_check(void) { if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { mp_raise_recursion_depth(); } } #endif // MICROPY_STACK_CHECK #include #include #include "py/runtime.h" void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { // TODO maybe take the function name as an argument so we can print nicer error messages // The reverse of MP_OBJ_FUN_MAKE_SIG bool takes_kw = sig & 1; size_t n_args_min = sig >> 17; size_t n_args_max = (sig >> 1) & 0xffff; if (n_kw && !takes_kw) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError(MP_ERROR_TEXT("function doesn't take keyword arguments")); #endif } if (n_args_min == n_args_max) { if (n_args != n_args_min) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), n_args_min, n_args); #endif } } else { if (n_args < n_args_min) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function missing %d required positional arguments"), n_args_min - n_args); #endif } else if (n_args > n_args_max) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function expected at most %d arguments, got %d"), n_args_max, n_args); #endif } } } void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { size_t pos_found = 0, kws_found = 0; for (size_t i = 0; i < n_allowed; i++) { mp_obj_t given_arg; if (i < n_pos) { if (allowed[i].flags & MP_ARG_KW_ONLY) { goto extra_positional; } pos_found++; given_arg = pos[i]; } else { mp_map_elem_t *kw = mp_map_lookup(kws, MP_OBJ_NEW_QSTR(allowed[i].qst), MP_MAP_LOOKUP); if (kw == NULL) { if (allowed[i].flags & MP_ARG_REQUIRED) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%q' argument required"), allowed[i].qst); #endif } out_vals[i] = allowed[i].defval; continue; } else { kws_found++; given_arg = kw->value; } } if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_BOOL) { out_vals[i].u_bool = mp_obj_is_true(given_arg); } else if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_INT) { out_vals[i].u_int = mp_obj_get_int(given_arg); } else { assert((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_OBJ); out_vals[i].u_obj = given_arg; } } if (pos_found < n_pos) { extra_positional: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else // TODO better error message mp_raise_TypeError(MP_ERROR_TEXT("extra positional arguments given")); #endif } if (kws_found < kws->used) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else // TODO better error message mp_raise_TypeError(MP_ERROR_TEXT("extra keyword arguments given")); #endif } } void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_pos); mp_arg_parse_all(n_pos, args, &kw_args, n_allowed, allowed, out_vals); } NORETURN void mp_arg_error_terse_mismatch(void) { mp_raise_TypeError(MP_ERROR_TEXT("argument num/types mismatch")); } #if MICROPY_CPYTHON_COMPAT NORETURN void mp_arg_error_unimpl_kw(void) { mp_raise_NotImplementedError(MP_ERROR_TEXT("keyword argument(s) not yet implemented - use normal args instead")); } #endif #include #ifndef __IPU__ #include #endif #include "py/emit.h" #include "py/runtime.h" #if MICROPY_WARNINGS void mp_warning(const char *category, const char *msg, ...) { if (category == NULL) { category = "Warning"; } mp_print_str(MICROPY_ERROR_PRINTER, category); mp_print_str(MICROPY_ERROR_PRINTER, ": "); va_list args; va_start(args, msg); mp_vprintf(MICROPY_ERROR_PRINTER, msg, args); mp_print_str(MICROPY_ERROR_PRINTER, "\n"); va_end(args); } void mp_emitter_warning(pass_kind_t pass, const char *msg) { if (pass == MP_PASS_CODE_SIZE) { mp_warning(NULL, msg); } } #endif // MICROPY_WARNINGS #include "py/profile.h" #include "py/bc0.h" #include "py/gc.h" #include "py/objfun.h" #if MICROPY_PY_SYS_SETTRACE #define prof_trace_cb MP_STATE_THREAD(prof_trace_callback) #define QSTR_MAP(context, idx) (context->constants.qstr_table[idx]) STATIC uint mp_prof_bytecode_lineno(const mp_raw_code_t *rc, size_t bc) { const mp_bytecode_prelude_t *prelude = &rc->prelude; return mp_bytecode_get_source_line(prelude->line_info, prelude->line_info_top, bc); } void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelude) { const byte *ip = bytecode; MP_BC_PRELUDE_SIG_DECODE(ip); prelude->n_state = n_state; prelude->n_exc_stack = n_exc_stack; prelude->scope_flags = scope_flags; prelude->n_pos_args = n_pos_args; prelude->n_kwonly_args = n_kwonly_args; prelude->n_def_pos_args = n_def_pos_args; MP_BC_PRELUDE_SIZE_DECODE(ip); prelude->line_info_top = ip + n_info; prelude->opcodes = ip + n_info + n_cell; prelude->qstr_block_name_idx = mp_decode_uint_value(ip); for (size_t i = 0; i < 1 + n_pos_args + n_kwonly_args; ++i) { ip = mp_decode_uint_skip(ip); } prelude->line_info = ip; } /******************************************************************************/ // code object STATIC void code_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_code_t *o = MP_OBJ_TO_PTR(o_in); const mp_raw_code_t *rc = o->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; mp_printf(print, "", QSTR_MAP(o->context, prelude->qstr_block_name_idx), o, QSTR_MAP(o->context, 0), rc->line_of_definition ); } STATIC mp_obj_tuple_t *code_consts(const mp_module_context_t *context, const mp_raw_code_t *rc) { mp_obj_tuple_t *consts = MP_OBJ_TO_PTR(mp_obj_new_tuple(rc->n_children + 1, NULL)); size_t const_no = 0; for (size_t i = 0; i < rc->n_children; ++i) { mp_obj_t code = mp_obj_new_code(context, rc->children[i]); if (code == MP_OBJ_NULL) { m_malloc_fail(sizeof(mp_obj_code_t)); } consts->items[const_no++] = code; } consts->items[const_no++] = mp_const_none; return consts; } STATIC mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { // const mp_bytecode_prelude_t *prelude = &rc->prelude; uint start = 0; uint stop = rc->fun_data_len - start; uint last_lineno = mp_prof_bytecode_lineno(rc, start); uint lasti = 0; const uint buffer_chunk_size = (stop - start) >> 2; // heuristic magic uint buffer_size = buffer_chunk_size; byte *buffer = m_new(byte, buffer_size); uint buffer_index = 0; for (uint i = start; i < stop; ++i) { uint lineno = mp_prof_bytecode_lineno(rc, i); size_t line_diff = lineno - last_lineno; if (line_diff > 0) { uint instr_diff = (i - start) - lasti; assert(instr_diff < 256); assert(line_diff < 256); if (buffer_index + 2 > buffer_size) { buffer = m_renew(byte, buffer, buffer_size, buffer_size + buffer_chunk_size); buffer_size = buffer_size + buffer_chunk_size; } last_lineno = lineno; lasti = i - start; buffer[buffer_index++] = instr_diff; buffer[buffer_index++] = line_diff; } } mp_obj_t o = mp_obj_new_bytes(buffer, buffer_index); m_del(byte, buffer, buffer_size); return o; } STATIC void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } mp_obj_code_t *o = MP_OBJ_TO_PTR(self_in); const mp_raw_code_t *rc = o->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; switch (attr) { case MP_QSTR_co_code: dest[0] = mp_obj_new_bytes( (void *)prelude->opcodes, rc->fun_data_len - (prelude->opcodes - (const byte *)rc->fun_data) ); break; case MP_QSTR_co_consts: dest[0] = MP_OBJ_FROM_PTR(code_consts(o->context, rc)); break; case MP_QSTR_co_filename: dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, 0)); break; case MP_QSTR_co_firstlineno: dest[0] = MP_OBJ_NEW_SMALL_INT(mp_prof_bytecode_lineno(rc, 0)); break; case MP_QSTR_co_name: dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, prelude->qstr_block_name_idx)); break; case MP_QSTR_co_names: dest[0] = MP_OBJ_FROM_PTR(o->dict_locals); break; case MP_QSTR_co_lnotab: if (!o->lnotab) { o->lnotab = raw_code_lnotab(rc); } dest[0] = o->lnotab; break; } } const mp_obj_type_t mp_type_settrace_codeobj = { { &mp_type_type }, .name = MP_QSTR_code, .print = code_print, .unary_op = mp_generic_unary_op, .attr = code_attr, }; mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc) { mp_obj_code_t *o = m_new_obj_maybe(mp_obj_code_t); if (o == NULL) { return MP_OBJ_NULL; } o->base.type = &mp_type_settrace_codeobj; o->context = context; o->rc = rc; o->dict_locals = mp_locals_get(); // this is a wrong! how to do this properly? o->lnotab = MP_OBJ_NULL; return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ // frame object STATIC void frame_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_frame_t *frame = MP_OBJ_TO_PTR(o_in); mp_obj_code_t *code = frame->code; const mp_raw_code_t *rc = code->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; mp_printf(print, "", frame, QSTR_MAP(code->context, 0), frame->lineno, QSTR_MAP(code->context, prelude->qstr_block_name_idx) ); } STATIC void frame_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } mp_obj_frame_t *o = MP_OBJ_TO_PTR(self_in); switch (attr) { case MP_QSTR_f_back: dest[0] = mp_const_none; if (o->code_state->prev_state) { dest[0] = MP_OBJ_FROM_PTR(o->code_state->prev_state->frame); } break; case MP_QSTR_f_code: dest[0] = MP_OBJ_FROM_PTR(o->code); break; case MP_QSTR_f_globals: dest[0] = MP_OBJ_FROM_PTR(o->code_state->fun_bc->context->module.globals); break; case MP_QSTR_f_lasti: dest[0] = MP_OBJ_NEW_SMALL_INT(o->lasti); break; case MP_QSTR_f_lineno: dest[0] = MP_OBJ_NEW_SMALL_INT(o->lineno); break; } } const mp_obj_type_t mp_type_frame = { { &mp_type_type }, .name = MP_QSTR_frame, .print = frame_print, .unary_op = mp_generic_unary_op, .attr = frame_attr, }; mp_obj_t mp_obj_new_frame(const mp_code_state_t *code_state) { if (gc_is_locked()) { return MP_OBJ_NULL; } mp_obj_frame_t *o = m_new_obj_maybe(mp_obj_frame_t); if (o == NULL) { return MP_OBJ_NULL; } mp_obj_code_t *code = o->code = MP_OBJ_TO_PTR(mp_obj_new_code(code_state->fun_bc->context, code_state->fun_bc->rc)); if (code == NULL) { return MP_OBJ_NULL; } const mp_raw_code_t *rc = code->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; o->code_state = code_state; o->base.type = &mp_type_frame; o->back = NULL; o->code = code; o->lasti = code_state->ip - prelude->opcodes; o->lineno = mp_prof_bytecode_lineno(rc, o->lasti); o->trace_opcodes = false; o->callback = MP_OBJ_NULL; return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ // Trace logic typedef struct { struct _mp_obj_frame_t *frame; mp_obj_t event; mp_obj_t arg; } prof_callback_args_t; STATIC mp_obj_t mp_prof_callback_invoke(mp_obj_t callback, prof_callback_args_t *args) { assert(mp_obj_is_callable(callback)); mp_prof_is_executing = true; mp_obj_t a[3] = {MP_OBJ_FROM_PTR(args->frame), args->event, args->arg}; mp_obj_t top = mp_call_function_n_kw(callback, 3, 0, a); mp_prof_is_executing = false; if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { mp_handle_pending(true); } return top; } mp_obj_t mp_prof_settrace(mp_obj_t callback) { if (mp_obj_is_callable(callback)) { prof_trace_cb = callback; } else { prof_trace_cb = MP_OBJ_NULL; } return mp_const_none; } mp_obj_t mp_prof_frame_enter(mp_code_state_t *code_state) { assert(!mp_prof_is_executing); mp_obj_frame_t *frame = MP_OBJ_TO_PTR(mp_obj_new_frame(code_state)); if (frame == NULL) { // Couldn't allocate a frame object return MP_OBJ_NULL; } if (code_state->prev_state && code_state->frame == NULL) { // We are entering not-yet-traced frame // which means it's a CALL event (not a GENERATOR) // so set the function definition line. const mp_raw_code_t *rc = code_state->fun_bc->rc; frame->lineno = rc->line_of_definition; if (!rc->line_of_definition) { frame->lineno = mp_prof_bytecode_lineno(rc, 0); } } code_state->frame = frame; if (!prof_trace_cb) { return MP_OBJ_NULL; } mp_obj_t top; prof_callback_args_t _args, *args = &_args; args->frame = code_state->frame; // SETTRACE event CALL args->event = MP_OBJ_NEW_QSTR(MP_QSTR_call); args->arg = mp_const_none; top = mp_prof_callback_invoke(prof_trace_cb, args); code_state->frame->callback = mp_obj_is_callable(top) ? top : MP_OBJ_NULL; // Invalidate the last executed line number so the LINE trace can trigger after this CALL. frame->lineno = 0; return top; } mp_obj_t mp_prof_frame_update(const mp_code_state_t *code_state) { mp_obj_frame_t *frame = code_state->frame; if (frame == NULL) { // Frame was not allocated (eg because there was no memory available) return MP_OBJ_NULL; } mp_obj_frame_t *o = frame; mp_obj_code_t *code = o->code; const mp_raw_code_t *rc = code->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; assert(o->code_state == code_state); o->lasti = code_state->ip - prelude->opcodes; o->lineno = mp_prof_bytecode_lineno(rc, o->lasti); return MP_OBJ_FROM_PTR(o); } mp_obj_t mp_prof_instr_tick(mp_code_state_t *code_state, bool is_exception) { // Detect execution recursion assert(!mp_prof_is_executing); assert(code_state->frame); assert(mp_obj_get_type(code_state->frame) == &mp_type_frame); // Detect data recursion assert(code_state != code_state->prev_state); mp_obj_t top = mp_const_none; mp_obj_t callback = code_state->frame->callback; prof_callback_args_t _args, *args = &_args; args->frame = code_state->frame; args->event = mp_const_none; args->arg = mp_const_none; // Call event's are handled inside mp_prof_frame_enter // SETTRACE event EXCEPTION if (is_exception) { args->event = MP_OBJ_NEW_QSTR(MP_QSTR_exception); top = mp_prof_callback_invoke(callback, args); return top; } // SETTRACE event LINE const mp_raw_code_t *rc = code_state->fun_bc->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; size_t prev_line_no = args->frame->lineno; size_t current_line_no = mp_prof_bytecode_lineno(rc, code_state->ip - prelude->opcodes); if (prev_line_no != current_line_no) { args->frame->lineno = current_line_no; args->event = MP_OBJ_NEW_QSTR(MP_QSTR_line); top = mp_prof_callback_invoke(callback, args); } // SETTRACE event RETURN const byte *ip = code_state->ip; if (*ip == MP_BC_RETURN_VALUE || *ip == MP_BC_YIELD_VALUE) { args->event = MP_OBJ_NEW_QSTR(MP_QSTR_return); top = mp_prof_callback_invoke(callback, args); if (code_state->prev_state && *ip == MP_BC_RETURN_VALUE) { code_state->frame->callback = MP_OBJ_NULL; } } // SETTRACE event OPCODE // TODO: frame.f_trace_opcodes=True if (false) { args->event = MP_OBJ_NEW_QSTR(MP_QSTR_opcode); } return top; } /******************************************************************************/ // DEBUG // This section is for debugging the settrace feature itself, and is not intended // to be included in production/release builds. The code structure for this block // was taken from py/showbc.c and should not be used as a reference. To enable // this debug feature enable MICROPY_PROF_INSTR_DEBUG_PRINT_ENABLE in py/profile.h. #if MICROPY_PROF_INSTR_DEBUG_PRINT_ENABLE #include "runtime0.h" #define DECODE_UINT { \ unum = 0; \ do { \ unum = (unum << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0); \ } #define DECODE_ULABEL do { unum = (ip[0] | (ip[1] << 8)); ip += 2; } while (0) #define DECODE_SLABEL do { unum = (ip[0] | (ip[1] << 8)) - 0x8000; ip += 2; } while (0) #define DECODE_QSTR \ qst = ip[0] | ip[1] << 8; \ ip += 2; #define DECODE_PTR \ DECODE_UINT; \ ptr = (const byte *)const_table[unum] #define DECODE_OBJ \ DECODE_UINT; \ obj = (mp_obj_t)const_table[unum] typedef struct _mp_dis_instruction_t { mp_uint_t qstr_opname; mp_uint_t arg; mp_obj_t argobj; mp_obj_t argobjex_cache; } mp_dis_instruction_t; STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_table, mp_dis_instruction_t *instruction) { mp_uint_t unum; const byte *ptr; mp_obj_t obj; qstr qst; instruction->qstr_opname = MP_QSTR_; instruction->arg = 0; instruction->argobj = mp_const_none; instruction->argobjex_cache = mp_const_none; switch (*ip++) { case MP_BC_LOAD_CONST_FALSE: instruction->qstr_opname = MP_QSTR_LOAD_CONST_FALSE; break; case MP_BC_LOAD_CONST_NONE: instruction->qstr_opname = MP_QSTR_LOAD_CONST_NONE; break; case MP_BC_LOAD_CONST_TRUE: instruction->qstr_opname = MP_QSTR_LOAD_CONST_TRUE; break; case MP_BC_LOAD_CONST_SMALL_INT: { mp_int_t num = 0; if ((ip[0] & 0x40) != 0) { // Number is negative num--; } do { num = (num << 7) | (*ip & 0x7f); } while ((*ip++ & 0x80) != 0); instruction->qstr_opname = MP_QSTR_LOAD_CONST_SMALL_INT; instruction->arg = num; break; } case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_CONST_STRING; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_CONST_OBJ: DECODE_OBJ; instruction->qstr_opname = MP_QSTR_LOAD_CONST_OBJ; instruction->arg = unum; instruction->argobj = obj; break; case MP_BC_LOAD_NULL: instruction->qstr_opname = MP_QSTR_LOAD_NULL; break; case MP_BC_LOAD_FAST_N: DECODE_UINT; instruction->qstr_opname = MP_QSTR_LOAD_FAST_N; instruction->arg = unum; break; case MP_BC_LOAD_DEREF: DECODE_UINT; instruction->qstr_opname = MP_QSTR_LOAD_DEREF; instruction->arg = unum; break; case MP_BC_LOAD_NAME: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_NAME; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_GLOBAL; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_ATTR: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_ATTR; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_METHOD: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_METHOD; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_LOAD_SUPER_METHOD; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_LOAD_BUILD_CLASS: instruction->qstr_opname = MP_QSTR_LOAD_BUILD_CLASS; break; case MP_BC_LOAD_SUBSCR: instruction->qstr_opname = MP_QSTR_LOAD_SUBSCR; break; case MP_BC_STORE_FAST_N: DECODE_UINT; instruction->qstr_opname = MP_QSTR_STORE_FAST_N; instruction->arg = unum; break; case MP_BC_STORE_DEREF: DECODE_UINT; instruction->qstr_opname = MP_QSTR_STORE_DEREF; instruction->arg = unum; break; case MP_BC_STORE_NAME: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_STORE_NAME; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_STORE_GLOBAL; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_STORE_ATTR: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_STORE_ATTR; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_STORE_SUBSCR: instruction->qstr_opname = MP_QSTR_STORE_SUBSCR; break; case MP_BC_DELETE_FAST: DECODE_UINT; instruction->qstr_opname = MP_QSTR_DELETE_FAST; instruction->arg = unum; break; case MP_BC_DELETE_DEREF: DECODE_UINT; instruction->qstr_opname = MP_QSTR_DELETE_DEREF; instruction->arg = unum; break; case MP_BC_DELETE_NAME: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_DELETE_NAME; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_DELETE_GLOBAL; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_DUP_TOP: instruction->qstr_opname = MP_QSTR_DUP_TOP; break; case MP_BC_DUP_TOP_TWO: instruction->qstr_opname = MP_QSTR_DUP_TOP_TWO; break; case MP_BC_POP_TOP: instruction->qstr_opname = MP_QSTR_POP_TOP; break; case MP_BC_ROT_TWO: instruction->qstr_opname = MP_QSTR_ROT_TWO; break; case MP_BC_ROT_THREE: instruction->qstr_opname = MP_QSTR_ROT_THREE; break; case MP_BC_JUMP: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_JUMP; instruction->arg = unum; break; case MP_BC_POP_JUMP_IF_TRUE: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_POP_JUMP_IF_TRUE; instruction->arg = unum; break; case MP_BC_POP_JUMP_IF_FALSE: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_POP_JUMP_IF_FALSE; instruction->arg = unum; break; case MP_BC_JUMP_IF_TRUE_OR_POP: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_JUMP_IF_TRUE_OR_POP; instruction->arg = unum; break; case MP_BC_JUMP_IF_FALSE_OR_POP: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_JUMP_IF_FALSE_OR_POP; instruction->arg = unum; break; case MP_BC_SETUP_WITH: DECODE_ULABEL; // loop-like labels are always forward instruction->qstr_opname = MP_QSTR_SETUP_WITH; instruction->arg = unum; break; case MP_BC_WITH_CLEANUP: instruction->qstr_opname = MP_QSTR_WITH_CLEANUP; break; case MP_BC_UNWIND_JUMP: DECODE_SLABEL; instruction->qstr_opname = MP_QSTR_UNWIND_JUMP; instruction->arg = unum; break; case MP_BC_SETUP_EXCEPT: DECODE_ULABEL; // except labels are always forward instruction->qstr_opname = MP_QSTR_SETUP_EXCEPT; instruction->arg = unum; break; case MP_BC_SETUP_FINALLY: DECODE_ULABEL; // except labels are always forward instruction->qstr_opname = MP_QSTR_SETUP_FINALLY; instruction->arg = unum; break; case MP_BC_END_FINALLY: // if TOS is an exception, reraises the exception (3 values on TOS) // if TOS is an integer, does something else // if TOS is None, just pops it and continues // else error instruction->qstr_opname = MP_QSTR_END_FINALLY; break; case MP_BC_GET_ITER: instruction->qstr_opname = MP_QSTR_GET_ITER; break; case MP_BC_GET_ITER_STACK: instruction->qstr_opname = MP_QSTR_GET_ITER_STACK; break; case MP_BC_FOR_ITER: DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward instruction->qstr_opname = MP_QSTR_FOR_ITER; instruction->arg = unum; break; case MP_BC_BUILD_TUPLE: DECODE_UINT; instruction->qstr_opname = MP_QSTR_BUILD_TUPLE; instruction->arg = unum; break; case MP_BC_BUILD_LIST: DECODE_UINT; instruction->qstr_opname = MP_QSTR_BUILD_LIST; instruction->arg = unum; break; case MP_BC_BUILD_MAP: DECODE_UINT; instruction->qstr_opname = MP_QSTR_BUILD_MAP; instruction->arg = unum; break; case MP_BC_STORE_MAP: instruction->qstr_opname = MP_QSTR_STORE_MAP; break; case MP_BC_BUILD_SET: DECODE_UINT; instruction->qstr_opname = MP_QSTR_BUILD_SET; instruction->arg = unum; break; #if MICROPY_PY_BUILTINS_SLICE case MP_BC_BUILD_SLICE: DECODE_UINT; instruction->qstr_opname = MP_QSTR_BUILD_SLICE; instruction->arg = unum; break; #endif case MP_BC_STORE_COMP: DECODE_UINT; instruction->qstr_opname = MP_QSTR_STORE_COMP; instruction->arg = unum; break; case MP_BC_UNPACK_SEQUENCE: DECODE_UINT; instruction->qstr_opname = MP_QSTR_UNPACK_SEQUENCE; instruction->arg = unum; break; case MP_BC_UNPACK_EX: DECODE_UINT; instruction->qstr_opname = MP_QSTR_UNPACK_EX; instruction->arg = unum; break; case MP_BC_MAKE_FUNCTION: DECODE_PTR; instruction->qstr_opname = MP_QSTR_MAKE_FUNCTION; instruction->arg = unum; instruction->argobj = mp_obj_new_int_from_ull((uint64_t)ptr); break; case MP_BC_MAKE_FUNCTION_DEFARGS: DECODE_PTR; instruction->qstr_opname = MP_QSTR_MAKE_FUNCTION_DEFARGS; instruction->arg = unum; instruction->argobj = mp_obj_new_int_from_ull((uint64_t)ptr); break; case MP_BC_MAKE_CLOSURE: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; instruction->qstr_opname = MP_QSTR_MAKE_CLOSURE; instruction->arg = unum; instruction->argobj = mp_obj_new_int_from_ull((uint64_t)ptr); instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(n_closed_over); break; } case MP_BC_MAKE_CLOSURE_DEFARGS: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; instruction->qstr_opname = MP_QSTR_MAKE_CLOSURE_DEFARGS; instruction->arg = unum; instruction->argobj = mp_obj_new_int_from_ull((uint64_t)ptr); instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(n_closed_over); break; } case MP_BC_CALL_FUNCTION: DECODE_UINT; instruction->qstr_opname = MP_QSTR_CALL_FUNCTION; instruction->arg = unum & 0xff; instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT((unum >> 8) & 0xff); break; case MP_BC_CALL_FUNCTION_VAR_KW: DECODE_UINT; instruction->qstr_opname = MP_QSTR_CALL_FUNCTION_VAR_KW; instruction->arg = unum & 0xff; instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT((unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD: DECODE_UINT; instruction->qstr_opname = MP_QSTR_CALL_METHOD; instruction->arg = unum & 0xff; instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT((unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD_VAR_KW: DECODE_UINT; instruction->qstr_opname = MP_QSTR_CALL_METHOD_VAR_KW; instruction->arg = unum & 0xff; instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT((unum >> 8) & 0xff); break; case MP_BC_RETURN_VALUE: instruction->qstr_opname = MP_QSTR_RETURN_VALUE; break; case MP_BC_RAISE_LAST: instruction->qstr_opname = MP_QSTR_RAISE_LAST; break; case MP_BC_RAISE_OBJ: instruction->qstr_opname = MP_QSTR_RAISE_OBJ; break; case MP_BC_RAISE_FROM: instruction->qstr_opname = MP_QSTR_RAISE_FROM; break; case MP_BC_YIELD_VALUE: instruction->qstr_opname = MP_QSTR_YIELD_VALUE; break; case MP_BC_YIELD_FROM: instruction->qstr_opname = MP_QSTR_YIELD_FROM; break; case MP_BC_IMPORT_NAME: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_IMPORT_NAME; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; instruction->qstr_opname = MP_QSTR_IMPORT_FROM; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); break; case MP_BC_IMPORT_STAR: instruction->qstr_opname = MP_QSTR_IMPORT_STAR; break; default: if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { instruction->qstr_opname = MP_QSTR_LOAD_CONST_SMALL_INT; instruction->arg = (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16; } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { instruction->qstr_opname = MP_QSTR_LOAD_FAST; instruction->arg = (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI; } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { instruction->qstr_opname = MP_QSTR_STORE_FAST; instruction->arg = (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI; } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { instruction->qstr_opname = MP_QSTR_UNARY_OP; instruction->arg = (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI; } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; instruction->qstr_opname = MP_QSTR_BINARY_OP; instruction->arg = op; } else { mp_printf(&mp_plat_print, "code %p, opcode 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); return ip; } break; } return ip; } void mp_prof_print_instr(const byte *ip, mp_code_state_t *code_state) { mp_dis_instruction_t _instruction, *instruction = &_instruction; mp_prof_opcode_decode(ip, code_state->fun_bc->rc->const_table, instruction); const mp_raw_code_t *rc = code_state->fun_bc->rc; const mp_bytecode_prelude_t *prelude = &rc->prelude; mp_uint_t offset = ip - prelude->opcodes; mp_printf(&mp_plat_print, "instr"); /* long path */ if (1) { mp_printf(&mp_plat_print, "@0x%p:%q:%q+0x%04x:%d", ip, prelude->qstr_source_file, prelude->qstr_block_name, offset, mp_prof_bytecode_lineno(rc, offset) ); } /* bytecode */ if (0) { mp_printf(&mp_plat_print, " %02x %02x %02x %02x", ip[0], ip[1], ip[2], ip[3]); } mp_printf(&mp_plat_print, " 0x%02x %q [%d]", *ip, instruction->qstr_opname, instruction->arg); if (instruction->argobj != mp_const_none) { mp_printf(&mp_plat_print, " $"); mp_obj_print_helper(&mp_plat_print, instruction->argobj, PRINT_REPR); } if (instruction->argobjex_cache != mp_const_none) { mp_printf(&mp_plat_print, " #"); mp_obj_print_helper(&mp_plat_print, instruction->argobjex_cache, PRINT_REPR); } mp_printf(&mp_plat_print, "\n"); } #endif // MICROPY_PROF_INSTR_DEBUG_PRINT_ENABLE #endif // MICROPY_PY_SYS_SETTRACE #include #include #include #include #include "py/mpconfig.h" #include "py/misc.h" #include "py/runtime.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif #if MICROPY_OPT_MAP_LOOKUP_CACHE // MP_STATE_VM(map_lookup_cache) provides a cache of index to the last known // position of that index in any map. On a cache hit, this allows // short-circuiting the full linear search in the case of an ordered map // (i.e. all builtin modules and objects' locals dicts), and computation of // the hash (and potentially some linear probing) in the case of a regular // map. Note the same cache is shared across all maps. // Gets the index into the cache for this index. Shift down by two to remove // mp_obj_t tag bits. #define MAP_CACHE_OFFSET(index) ((((uintptr_t)(index)) >> 2) % MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE) // Gets the map cache entry for the corresponding index. #define MAP_CACHE_ENTRY(index) (MP_STATE_VM(map_lookup_cache)[MAP_CACHE_OFFSET(index)]) // Retrieve the mp_obj_t at the location suggested by the cache. #define MAP_CACHE_GET(map, index) (&(map)->table[MAP_CACHE_ENTRY(index) % (map)->alloc]) // Update the cache for this index. #define MAP_CACHE_SET(index, pos) MAP_CACHE_ENTRY(index) = (pos) & 0xff; #else #define MAP_CACHE_SET(index, pos) #endif // This table of sizes is used to control the growth of hash tables. // The first set of sizes are chosen so the allocation fits exactly in a // 4-word GC block, and it's not so important for these small values to be // prime. The latter sizes are prime and increase at an increasing rate. STATIC const uint16_t hash_allocation_sizes[] = { 0, 2, 4, 6, 8, 10, 12, // +2 17, 23, 29, 37, 47, 59, 73, // *1.25 97, 127, 167, 223, 293, 389, 521, 691, 919, 1223, 1627, 2161, // *1.33 3229, 4831, 7243, 10861, 16273, 24407, 36607, 54907, // *1.5 }; STATIC size_t get_hash_alloc_greater_or_equal_to(size_t x) { for (size_t i = 0; i < MP_ARRAY_SIZE(hash_allocation_sizes); i++) { if (hash_allocation_sizes[i] >= x) { return hash_allocation_sizes[i]; } } // ran out of primes in the table! // return something sensible, at least make it odd return (x + x / 2) | 1; } /******************************************************************************/ /* map */ void mp_map_init(mp_map_t *map, size_t n) { if (n == 0) { map->alloc = 0; map->table = NULL; } else { map->alloc = n; map->table = m_new0(mp_map_elem_t, map->alloc); } map->used = 0; map->all_keys_are_qstrs = 1; map->is_fixed = 0; map->is_ordered = 0; } void mp_map_init_fixed_table(mp_map_t *map, size_t n, const mp_obj_t *table) { map->alloc = n; map->used = n; map->all_keys_are_qstrs = 1; map->is_fixed = 1; map->is_ordered = 1; map->table = (mp_map_elem_t *)table; } // Differentiate from mp_map_clear() - semantics is different void mp_map_deinit(mp_map_t *map) { if (!map->is_fixed) { m_del(mp_map_elem_t, map->table, map->alloc); } map->used = map->alloc = 0; } void mp_map_clear(mp_map_t *map) { if (!map->is_fixed) { m_del(mp_map_elem_t, map->table, map->alloc); } map->alloc = 0; map->used = 0; map->all_keys_are_qstrs = 1; map->is_fixed = 0; map->table = NULL; } STATIC void mp_map_rehash(mp_map_t *map) { size_t old_alloc = map->alloc; size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1); DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc); mp_map_elem_t *old_table = map->table; mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc); // If we reach this point, table resizing succeeded, now we can edit the old map. map->alloc = new_alloc; map->used = 0; map->all_keys_are_qstrs = 1; map->table = new_table; for (size_t i = 0; i < old_alloc; i++) { if (old_table[i].key != MP_OBJ_NULL && old_table[i].key != MP_OBJ_SENTINEL) { mp_map_lookup(map, old_table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = old_table[i].value; } } m_del(mp_map_elem_t, old_table, old_alloc); } // MP_MAP_LOOKUP behaviour: // - returns NULL if not found, else the slot it was found in with key,value non-null // MP_MAP_LOOKUP_ADD_IF_NOT_FOUND behaviour: // - returns slot, with key non-null and value=MP_OBJ_NULL if it was added // MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour: // - returns NULL if not found, else the slot if was found in with key null and value non-null mp_map_elem_t *MICROPY_WRAP_MP_MAP_LOOKUP(mp_map_lookup)(mp_map_t * map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // If the map is a fixed array then we must only be called for a lookup assert(!map->is_fixed || lookup_kind == MP_MAP_LOOKUP); #if MICROPY_OPT_MAP_LOOKUP_CACHE // Try the cache for lookup or add-if-not-found. if (lookup_kind != MP_MAP_LOOKUP_REMOVE_IF_FOUND && map->alloc) { mp_map_elem_t *slot = MAP_CACHE_GET(map, index); // Note: Just comparing key for value equality will have false negatives, but // these will be handled by the regular path below. if (slot->key == index) { return slot; } } #endif // Work out if we can compare just pointers bool compare_only_ptrs = map->all_keys_are_qstrs; if (compare_only_ptrs) { if (mp_obj_is_qstr(index)) { // Index is a qstr, so can just do ptr comparison. } else if (mp_obj_is_type(index, &mp_type_str)) { // Index is a non-interned string. // We can either intern the string, or force a full equality comparison. // We chose the latter, since interning costs time and potentially RAM, // and it won't necessarily benefit subsequent calls because these calls // most likely won't pass the newly-interned string. compare_only_ptrs = false; } else if (lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { // If we are not adding, then we can return straight away a failed // lookup because we know that the index will never be found. return NULL; } } // if the map is an ordered array then we must do a brute force linear search if (map->is_ordered) { for (mp_map_elem_t *elem = &map->table[0], *top = &map->table[map->used]; elem < top; elem++) { if (elem->key == index || (!compare_only_ptrs && mp_obj_equal(elem->key, index))) { #if MICROPY_PY_COLLECTIONS_ORDEREDDICT if (MP_UNLIKELY(lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND)) { // remove the found element by moving the rest of the array down mp_obj_t value = elem->value; --map->used; memmove(elem, elem + 1, (top - elem - 1) * sizeof(*elem)); // put the found element after the end so the caller can access it if needed // note: caller must NULL the value so the GC can clean up (e.g. see dict_get_helper). elem = &map->table[map->used]; elem->key = MP_OBJ_NULL; elem->value = value; } #endif MAP_CACHE_SET(index, elem - map->table); return elem; } } #if MICROPY_PY_COLLECTIONS_ORDEREDDICT if (MP_LIKELY(lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)) { return NULL; } if (map->used == map->alloc) { // TODO: Alloc policy map->alloc += 4; map->table = m_renew(mp_map_elem_t, map->table, map->used, map->alloc); mp_seq_clear(map->table, map->used, map->alloc, sizeof(*map->table)); } mp_map_elem_t *elem = map->table + map->used++; elem->key = index; if (!mp_obj_is_qstr(index)) { map->all_keys_are_qstrs = 0; } return elem; #else return NULL; #endif } // map is a hash table (not an ordered array), so do a hash lookup if (map->alloc == 0) { if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { mp_map_rehash(map); } else { return NULL; } } // get hash of index, with fast path for common case of qstr mp_uint_t hash; if (mp_obj_is_qstr(index)) { hash = qstr_hash(MP_OBJ_QSTR_VALUE(index)); } else { hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); } size_t pos = hash % map->alloc; size_t start_pos = pos; mp_map_elem_t *avail_slot = NULL; for (;;) { mp_map_elem_t *slot = &map->table[pos]; if (slot->key == MP_OBJ_NULL) { // found NULL slot, so index is not in table if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { map->used += 1; if (avail_slot == NULL) { avail_slot = slot; } avail_slot->key = index; avail_slot->value = MP_OBJ_NULL; if (!mp_obj_is_qstr(index)) { map->all_keys_are_qstrs = 0; } return avail_slot; } else { return NULL; } } else if (slot->key == MP_OBJ_SENTINEL) { // found deleted slot, remember for later if (avail_slot == NULL) { avail_slot = slot; } } else if (slot->key == index || (!compare_only_ptrs && mp_obj_equal(slot->key, index))) { // found index // Note: CPython does not replace the index; try x={True:'true'};x[1]='one';x if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { // delete element in this slot map->used--; if (map->table[(pos + 1) % map->alloc].key == MP_OBJ_NULL) { // optimisation if next slot is empty slot->key = MP_OBJ_NULL; } else { slot->key = MP_OBJ_SENTINEL; } // keep slot->value so that caller can access it if needed } MAP_CACHE_SET(index, pos); return slot; } // not yet found, keep searching in this table pos = (pos + 1) % map->alloc; if (pos == start_pos) { // search got back to starting position, so index is not in table if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { if (avail_slot != NULL) { // there was an available slot, so use that map->used++; avail_slot->key = index; avail_slot->value = MP_OBJ_NULL; if (!mp_obj_is_qstr(index)) { map->all_keys_are_qstrs = 0; } return avail_slot; } else { // not enough room in table, rehash it mp_map_rehash(map); // restart the search for the new element start_pos = pos = hash % map->alloc; } } else { return NULL; } } } } /******************************************************************************/ /* set */ #if MICROPY_PY_BUILTINS_SET void mp_set_init(mp_set_t *set, size_t n) { set->alloc = n; set->used = 0; set->table = m_new0(mp_obj_t, set->alloc); } STATIC void mp_set_rehash(mp_set_t *set) { size_t old_alloc = set->alloc; mp_obj_t *old_table = set->table; set->alloc = get_hash_alloc_greater_or_equal_to(set->alloc + 1); set->used = 0; set->table = m_new0(mp_obj_t, set->alloc); for (size_t i = 0; i < old_alloc; i++) { if (old_table[i] != MP_OBJ_NULL && old_table[i] != MP_OBJ_SENTINEL) { mp_set_lookup(set, old_table[i], MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); } } m_del(mp_obj_t, old_table, old_alloc); } mp_obj_t mp_set_lookup(mp_set_t *set, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // Note: lookup_kind can be MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND which // is handled by using bitwise operations. if (set->alloc == 0) { if (lookup_kind & MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { mp_set_rehash(set); } else { return MP_OBJ_NULL; } } mp_uint_t hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); size_t pos = hash % set->alloc; size_t start_pos = pos; mp_obj_t *avail_slot = NULL; for (;;) { mp_obj_t elem = set->table[pos]; if (elem == MP_OBJ_NULL) { // found NULL slot, so index is not in table if (lookup_kind & MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { if (avail_slot == NULL) { avail_slot = &set->table[pos]; } set->used++; *avail_slot = index; return index; } else { return MP_OBJ_NULL; } } else if (elem == MP_OBJ_SENTINEL) { // found deleted slot, remember for later if (avail_slot == NULL) { avail_slot = &set->table[pos]; } } else if (mp_obj_equal(elem, index)) { // found index if (lookup_kind & MP_MAP_LOOKUP_REMOVE_IF_FOUND) { // delete element set->used--; if (set->table[(pos + 1) % set->alloc] == MP_OBJ_NULL) { // optimisation if next slot is empty set->table[pos] = MP_OBJ_NULL; } else { set->table[pos] = MP_OBJ_SENTINEL; } } return elem; } // not yet found, keep searching in this table pos = (pos + 1) % set->alloc; if (pos == start_pos) { // search got back to starting position, so index is not in table if (lookup_kind & MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { if (avail_slot != NULL) { // there was an available slot, so use that set->used++; *avail_slot = index; return index; } else { // not enough room in table, rehash it mp_set_rehash(set); // restart the search for the new element start_pos = pos = hash % set->alloc; } } else { return MP_OBJ_NULL; } } } } mp_obj_t mp_set_remove_first(mp_set_t *set) { for (size_t pos = 0; pos < set->alloc; pos++) { if (mp_set_slot_is_filled(set, pos)) { mp_obj_t elem = set->table[pos]; // delete element set->used--; if (set->table[(pos + 1) % set->alloc] == MP_OBJ_NULL) { // optimisation if next slot is empty set->table[pos] = MP_OBJ_NULL; } else { set->table[pos] = MP_OBJ_SENTINEL; } return elem; } } return MP_OBJ_NULL; } void mp_set_clear(mp_set_t *set) { m_del(mp_obj_t, set->table, set->alloc); set->alloc = 0; set->used = 0; set->table = NULL; } #endif // MICROPY_PY_BUILTINS_SET #if defined(DEBUG_PRINT) && DEBUG_PRINT void mp_map_dump(mp_map_t *map) { for (size_t i = 0; i < map->alloc; i++) { if (map->table[i].key != MP_OBJ_NULL) { mp_obj_print(map->table[i].key, PRINT_REPR); } else { DEBUG_printf("(nil)"); } DEBUG_printf(": %p\n", map->table[i].value); } DEBUG_printf("---\n"); } #endif #ifndef __IPU__ #include #endif #include #include #include #include "py/obj.h" #include "py/objtype.h" #include "py/objint.h" #include "py/objstr.h" #include "py/runtime.h" #include "py/stackctrl.h" #include "py/stream.h" // for mp_obj_print const mp_obj_type_t *MICROPY_WRAP_MP_OBJ_GET_TYPE(mp_obj_get_type)(mp_const_obj_t o_in) { #if MICROPY_OBJ_IMMEDIATE_OBJS && MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A if (mp_obj_is_obj(o_in)) { const mp_obj_base_t *o = MP_OBJ_TO_PTR(o_in); return o->type; } else { static const mp_obj_type_t *const types[] = { NULL, &mp_type_int, &mp_type_str, &mp_type_int, NULL, &mp_type_int, &mp_type_NoneType, &mp_type_int, NULL, &mp_type_int, &mp_type_str, &mp_type_int, NULL, &mp_type_int, &mp_type_bool, &mp_type_int, }; return types[(uintptr_t)o_in & 0xf]; } #elif MICROPY_OBJ_IMMEDIATE_OBJS && MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C if (mp_obj_is_small_int(o_in)) { return &mp_type_int; } else if (mp_obj_is_obj(o_in)) { const mp_obj_base_t *o = MP_OBJ_TO_PTR(o_in); return o->type; #if MICROPY_PY_BUILTINS_FLOAT } else if ((((mp_uint_t)(o_in)) & 0xff800007) != 0x00000006) { return &mp_type_float; #endif } else { static const mp_obj_type_t *const types[] = { &mp_type_str, &mp_type_NoneType, &mp_type_str, &mp_type_bool, }; return types[((uintptr_t)o_in >> 3) & 3]; } #else if (mp_obj_is_small_int(o_in)) { return &mp_type_int; } else if (mp_obj_is_qstr(o_in)) { return &mp_type_str; #if MICROPY_PY_BUILTINS_FLOAT && ( \ MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D) } else if (mp_obj_is_float(o_in)) { return &mp_type_float; #endif #if MICROPY_OBJ_IMMEDIATE_OBJS } else if (mp_obj_is_immediate_obj(o_in)) { static const mp_obj_type_t *const types[2] = {&mp_type_NoneType, &mp_type_bool}; return types[MP_OBJ_IMMEDIATE_OBJ_VALUE(o_in) & 1]; #endif } else { const mp_obj_base_t *o = MP_OBJ_TO_PTR(o_in); return o->type; } #endif } const char *mp_obj_get_type_str(mp_const_obj_t o_in) { return qstr_str(mp_obj_get_type(o_in)->name); } void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { // There can be data structures nested too deep, or just recursive MP_STACK_CHECK(); #ifndef NDEBUG if (o_in == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); return; } #endif const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->print != NULL) { type->print((mp_print_t *)print, o_in, kind); } else { mp_printf(print, "<%q>", type->name); } } void mp_obj_print(mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_print_helper(MP_PYTHON_PRINTER, o_in, kind); } // helper function to print an exception with traceback void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc) { if (mp_obj_is_exception_instance(exc)) { size_t n, *values; mp_obj_exception_get_traceback(exc, &n, &values); if (n > 0) { assert(n % 3 == 0); mp_print_str(print, "Traceback (most recent call last):\n"); for (int i = n - 3; i >= 0; i -= 3) { #if MICROPY_ENABLE_SOURCE_LINE mp_printf(print, " File \"%q\", line %d", values[i], (int)values[i + 1]); #else mp_printf(print, " File \"%q\"", values[i]); #endif // the block name can be NULL if it's unknown qstr block = values[i + 2]; if (block == MP_QSTRnull) { mp_print_str(print, "\n"); } else { mp_printf(print, ", in %q\n", block); } } } } mp_obj_print_helper(print, exc, PRINT_EXC); mp_print_str(print, "\n"); } bool mp_obj_is_true(mp_obj_t arg) { if (arg == mp_const_false) { return 0; } else if (arg == mp_const_true) { return 1; } else if (arg == mp_const_none) { return 0; } else if (mp_obj_is_small_int(arg)) { if (arg == MP_OBJ_NEW_SMALL_INT(0)) { return 0; } else { return 1; } } else { const mp_obj_type_t *type = mp_obj_get_type(arg); if (type->unary_op != NULL) { mp_obj_t result = type->unary_op(MP_UNARY_OP_BOOL, arg); if (result != MP_OBJ_NULL) { return result == mp_const_true; } } mp_obj_t len = mp_obj_len_maybe(arg); if (len != MP_OBJ_NULL) { // obj has a length, truth determined if len != 0 return len != MP_OBJ_NEW_SMALL_INT(0); } else { // any other obj is true per Python semantics return 1; } } } bool mp_obj_is_callable(mp_obj_t o_in) { const mp_call_fun_t call = mp_obj_get_type(o_in)->call; if (call != mp_obj_instance_call) { return call != NULL; } return mp_obj_instance_is_callable(o_in); } // This function implements the '==' and '!=' operators. // // From the Python language reference: // (https://docs.python.org/3/reference/expressions.html#not-in) // "The objects need not have the same type. If both are numbers, they are converted // to a common type. Otherwise, the == and != operators always consider objects of // different types to be unequal." // // This means that False==0 and True==1 are true expressions. // // Furthermore, from the v3.4.2 code for object.c: "Practical amendments: If rich // comparison returns NotImplemented, == and != are decided by comparing the object // pointer." mp_obj_t mp_obj_equal_not_equal(mp_binary_op_t op, mp_obj_t o1, mp_obj_t o2) { mp_obj_t local_true = (op == MP_BINARY_OP_NOT_EQUAL) ? mp_const_false : mp_const_true; mp_obj_t local_false = (op == MP_BINARY_OP_NOT_EQUAL) ? mp_const_true : mp_const_false; int pass_number = 0; // Shortcut for very common cases if (o1 == o2 && (mp_obj_is_small_int(o1) || !(mp_obj_get_type(o1)->flags & MP_TYPE_FLAG_EQ_NOT_REFLEXIVE))) { return local_true; } // fast path for strings if (mp_obj_is_str(o1)) { if (mp_obj_is_str(o2)) { // both strings, use special function return mp_obj_str_equal(o1, o2) ? local_true : local_false; #if MICROPY_PY_STR_BYTES_CMP_WARN } else if (mp_obj_is_type(o2, &mp_type_bytes)) { str_bytes_cmp: mp_warning(MP_WARN_CAT(BytesWarning), "Comparison between bytes and str"); return local_false; #endif } else { goto skip_one_pass; } #if MICROPY_PY_STR_BYTES_CMP_WARN } else if (mp_obj_is_str(o2) && mp_obj_is_type(o1, &mp_type_bytes)) { // o1 is not a string (else caught above), so the objects are not equal goto str_bytes_cmp; #endif } // fast path for small ints if (mp_obj_is_small_int(o1)) { if (mp_obj_is_small_int(o2)) { // both SMALL_INT, and not equal if we get here return local_false; } else { goto skip_one_pass; } } // generic type, call binary_op(MP_BINARY_OP_EQUAL) while (pass_number < 2) { const mp_obj_type_t *type = mp_obj_get_type(o1); // If a full equality test is not needed and the other object is a different // type then we don't need to bother trying the comparison. if (type->binary_op != NULL && ((type->flags & MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE) || mp_obj_get_type(o2) == type)) { // CPython is asymmetric: it will try __eq__ if there's no __ne__ but not the // other way around. If the class doesn't need a full test we can skip __ne__. if (op == MP_BINARY_OP_NOT_EQUAL && (type->flags & MP_TYPE_FLAG_EQ_HAS_NEQ_TEST)) { mp_obj_t r = type->binary_op(MP_BINARY_OP_NOT_EQUAL, o1, o2); if (r != MP_OBJ_NULL) { return r; } } // Try calling __eq__. mp_obj_t r = type->binary_op(MP_BINARY_OP_EQUAL, o1, o2); if (r != MP_OBJ_NULL) { if (op == MP_BINARY_OP_EQUAL) { return r; } else { return mp_obj_is_true(r) ? local_true : local_false; } } } skip_one_pass: // Try the other way around if none of the above worked ++pass_number; mp_obj_t temp = o1; o1 = o2; o2 = temp; } // equality not implemented, so fall back to pointer conparison return (o1 == o2) ? local_true : local_false; } bool mp_obj_equal(mp_obj_t o1, mp_obj_t o2) { return mp_obj_is_true(mp_obj_equal_not_equal(MP_BINARY_OP_EQUAL, o1, o2)); } mp_int_t mp_obj_get_int(mp_const_obj_t arg) { // This function essentially performs implicit type conversion to int // Note that Python does NOT provide implicit type conversion from // float to int in the core expression language, try some_list[1.0]. if (arg == mp_const_false) { return 0; } else if (arg == mp_const_true) { return 1; } else if (mp_obj_is_small_int(arg)) { return MP_OBJ_SMALL_INT_VALUE(arg); } else if (mp_obj_is_type(arg, &mp_type_int)) { return mp_obj_int_get_checked(arg); } else { mp_obj_t res = mp_unary_op(MP_UNARY_OP_INT, (mp_obj_t)arg); return mp_obj_int_get_checked(res); } } mp_int_t mp_obj_get_int_truncated(mp_const_obj_t arg) { if (mp_obj_is_int(arg)) { return mp_obj_int_get_truncated(arg); } else { return mp_obj_get_int(arg); } } // returns false if arg is not of integral type // returns true and sets *value if it is of integral type // can throw OverflowError if arg is of integral type, but doesn't fit in a mp_int_t bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value) { if (arg == mp_const_false) { *value = 0; } else if (arg == mp_const_true) { *value = 1; } else if (mp_obj_is_small_int(arg)) { *value = MP_OBJ_SMALL_INT_VALUE(arg); } else if (mp_obj_is_type(arg, &mp_type_int)) { *value = mp_obj_int_get_checked(arg); } else { return false; } return true; } #if MICROPY_PY_BUILTINS_FLOAT bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value) { mp_float_t val; if (arg == mp_const_false) { val = 0; } else if (arg == mp_const_true) { val = 1; } else if (mp_obj_is_small_int(arg)) { val = (mp_float_t)MP_OBJ_SMALL_INT_VALUE(arg); #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE } else if (mp_obj_is_type(arg, &mp_type_int)) { val = mp_obj_int_as_float_impl(arg); #endif } else if (mp_obj_is_float(arg)) { val = mp_obj_float_get(arg); } else { return false; } *value = val; return true; } mp_float_t mp_obj_get_float(mp_obj_t arg) { mp_float_t val; if (!mp_obj_get_float_maybe(arg, &val)) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to float")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert %s to float"), mp_obj_get_type_str(arg)); #endif } return val; } #if MICROPY_PY_BUILTINS_COMPLEX bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (arg == mp_const_false) { *real = 0; *imag = 0; } else if (arg == mp_const_true) { *real = 1; *imag = 0; } else if (mp_obj_is_small_int(arg)) { *real = (mp_float_t)MP_OBJ_SMALL_INT_VALUE(arg); *imag = 0; #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE } else if (mp_obj_is_type(arg, &mp_type_int)) { *real = mp_obj_int_as_float_impl(arg); *imag = 0; #endif } else if (mp_obj_is_float(arg)) { *real = mp_obj_float_get(arg); *imag = 0; } else if (mp_obj_is_type(arg, &mp_type_complex)) { mp_obj_complex_get(arg, real, imag); } else { return false; } return true; } void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (!mp_obj_get_complex_maybe(arg, real, imag)) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to complex")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert %s to complex"), mp_obj_get_type_str(arg)); #endif } } #endif #endif // note: returned value in *items may point to the interior of a GC block void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { if (mp_obj_is_type(o, &mp_type_tuple)) { mp_obj_tuple_get(o, len, items); } else if (mp_obj_is_type(o, &mp_type_list)) { mp_obj_list_get(o, len, items); } else { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("expected tuple/list")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("object '%s' isn't a tuple or list"), mp_obj_get_type_str(o)); #endif } } // note: returned value in *items may point to the interior of a GC block void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items) { size_t seq_len; mp_obj_get_array(o, &seq_len, items); if (seq_len != len) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("tuple/list has wrong length")); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("requested length %d but object has length %d"), (int)len, (int)seq_len); #endif } } // is_slice determines whether the index is a slice index size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice) { mp_int_t i; if (mp_obj_is_small_int(index)) { i = MP_OBJ_SMALL_INT_VALUE(index); } else if (!mp_obj_get_int_maybe(index, &i)) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("indices must be integers")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("%q indices must be integers, not %s"), type->name, mp_obj_get_type_str(index)); #endif } if (i < 0) { i += len; } if (is_slice) { if (i < 0) { i = 0; } else if ((mp_uint_t)i > len) { i = len; } } else { if (i < 0 || (mp_uint_t)i >= len) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("index out of range")); #else mp_raise_msg_varg(&mp_type_IndexError, MP_ERROR_TEXT("%q index out of range"), type->name); #endif } } // By this point 0 <= i <= len and so fits in a size_t return (size_t)i; } mp_obj_t mp_obj_id(mp_obj_t o_in) { mp_int_t id = (mp_int_t)o_in; if (!mp_obj_is_obj(o_in)) { return mp_obj_new_int(id); } else if (id >= 0) { // Many OSes and CPUs have affinity for putting "user" memories // into low half of address space, and "system" into upper half. // We're going to take advantage of that and return small int // (signed) for such "user" addresses. return MP_OBJ_NEW_SMALL_INT(id); } else { // If that didn't work, well, let's return long int, just as // a (big) positive value, so it will never clash with the range // of small int returned in previous case. return mp_obj_new_int_from_uint((mp_uint_t)id); } } // will raise a TypeError if object has no length mp_obj_t mp_obj_len(mp_obj_t o_in) { mp_obj_t len = mp_obj_len_maybe(o_in); if (len == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in)); #endif } else { return len; } } // may return MP_OBJ_NULL mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) { if ( #if !MICROPY_PY_BUILTINS_STR_UNICODE // It's simple - unicode is slow, non-unicode is fast mp_obj_is_str(o_in) || #endif mp_obj_is_type(o_in, &mp_type_bytes)) { GET_STR_LEN(o_in, l); return MP_OBJ_NEW_SMALL_INT(l); } else { const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->unary_op != NULL) { return type->unary_op(MP_UNARY_OP_LEN, o_in); } else { return MP_OBJ_NULL; } } } mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { const mp_obj_type_t *type = mp_obj_get_type(base); if (type->subscr != NULL) { mp_obj_t ret = type->subscr(base, index, value); if (ret != MP_OBJ_NULL) { return ret; } // TODO: call base classes here? } if (value == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base)); #endif } else if (value == MP_OBJ_SENTINEL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base)); #endif } else { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item assignment")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base)); #endif } } // Return input argument. Useful as .getiter for objects which are // their own iterators, etc. mp_obj_t mp_identity(mp_obj_t self) { return self; } MP_DEFINE_CONST_FUN_OBJ_1(mp_identity_obj, mp_identity); mp_obj_t mp_identity_getiter(mp_obj_t self, mp_obj_iter_buf_t *iter_buf) { (void)iter_buf; return self; } bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { const mp_obj_type_t *type = mp_obj_get_type(obj); if (type->buffer_p.get_buffer == NULL) { return false; } int ret = type->buffer_p.get_buffer(obj, bufinfo, flags); if (ret != 0) { return false; } return true; } void mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { if (!mp_get_buffer(obj, bufinfo, flags)) { mp_raise_TypeError(MP_ERROR_TEXT("object with buffer protocol required")); } } mp_obj_t mp_generic_unary_op(mp_unary_op_t op, mp_obj_t o_in) { switch (op) { case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT((mp_uint_t)o_in); default: return MP_OBJ_NULL; // op not supported } } #include #include #include #include "py/runtime.h" #include "py/binary.h" #include "py/objstr.h" #include "py/objarray.h" #if MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW // About memoryview object: We want to reuse as much code as possible from // array, and keep the memoryview object 4 words in size so it fits in 1 GC // block. Also, memoryview must keep a pointer to the base of the buffer so // that the buffer is not GC'd if the original parent object is no longer // around (we are assuming that all memoryview'able objects return a pointer // which points to the start of a GC chunk). Given the above constraints we // do the following: // - typecode high bit is set if the buffer is read-write (else read-only) // - free is the offset in elements to the first item in the memoryview // - len is the length in elements // - items points to the start of the original buffer // Note that we don't handle the case where the original buffer might change // size due to a resize of the original parent object. #if MICROPY_PY_BUILTINS_MEMORYVIEW #define TYPECODE_MASK (0x7f) #define memview_offset free #else // make (& TYPECODE_MASK) a null operation if memorview not enabled #define TYPECODE_MASK (~(size_t)0) // memview_offset should not be accessed if memoryview is not enabled, // so not defined to catch errors #endif STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf); STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg); STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in); STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); /******************************************************************************/ // array #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY STATIC void array_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); if (o->typecode == BYTEARRAY_TYPECODE) { mp_print_str(print, "bytearray(b"); mp_str_print_quoted(print, o->items, o->len, true); } else { mp_printf(print, "array('%c'", o->typecode); if (o->len > 0) { mp_print_str(print, ", ["); for (size_t i = 0; i < o->len; i++) { if (i > 0) { mp_print_str(print, ", "); } mp_obj_print_helper(print, mp_binary_get_val_array(o->typecode, o->items, i), PRINT_REPR); } mp_print_str(print, "]"); } } mp_print_str(print, ")"); } #endif #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY mp_obj_array_t *array_new(char typecode, size_t n) { int typecode_size = mp_binary_get_size('@', typecode, NULL); mp_obj_array_t *o = m_new_obj(mp_obj_array_t); #if MICROPY_PY_BUILTINS_BYTEARRAY && MICROPY_PY_ARRAY o->base.type = (typecode == BYTEARRAY_TYPECODE) ? &mp_type_bytearray : &mp_type_array; #elif MICROPY_PY_BUILTINS_BYTEARRAY o->base.type = &mp_type_bytearray; #else o->base.type = &mp_type_array; #endif o->typecode = typecode; o->free = 0; o->len = n; o->items = m_new(byte, typecode_size * o->len); return o; } #endif #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY STATIC mp_obj_t array_construct(char typecode, mp_obj_t initializer) { // bytearrays can be raw-initialised from anything with the buffer protocol // other arrays can only be raw-initialised from bytes and bytearray objects mp_buffer_info_t bufinfo; if (((MICROPY_PY_BUILTINS_BYTEARRAY && typecode == BYTEARRAY_TYPECODE) || (MICROPY_PY_ARRAY && (mp_obj_is_type(initializer, &mp_type_bytes) || (MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(initializer, &mp_type_bytearray))))) && mp_get_buffer(initializer, &bufinfo, MP_BUFFER_READ)) { // construct array from raw bytes // we round-down the len to make it a multiple of sz (CPython raises error) size_t sz = mp_binary_get_size('@', typecode, NULL); size_t len = bufinfo.len / sz; mp_obj_array_t *o = array_new(typecode, len); memcpy(o->items, bufinfo.buf, len * sz); return MP_OBJ_FROM_PTR(o); } size_t len; // Try to create array of exact len if initializer len is known mp_obj_t len_in = mp_obj_len_maybe(initializer); if (len_in == MP_OBJ_NULL) { len = 0; } else { len = MP_OBJ_SMALL_INT_VALUE(len_in); } mp_obj_array_t *array = array_new(typecode, len); mp_obj_t iterable = mp_getiter(initializer, NULL); mp_obj_t item; size_t i = 0; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { if (len == 0) { array_append(MP_OBJ_FROM_PTR(array), item); } else { mp_binary_set_val_array(typecode, array->items, i++, item); } } return MP_OBJ_FROM_PTR(array); } #endif #if MICROPY_PY_ARRAY STATIC mp_obj_t array_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 1, 2, false); // get typecode const char *typecode = mp_obj_str_get_str(args[0]); if (n_args == 1) { // 1 arg: make an empty array return MP_OBJ_FROM_PTR(array_new(*typecode, 0)); } else { // 2 args: construct the array from the given object return array_construct(*typecode, args[1]); } } #endif #if MICROPY_PY_BUILTINS_BYTEARRAY STATIC mp_obj_t bytearray_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; // Can take 2nd/3rd arg if constructs from str mp_arg_check_num(n_args, n_kw, 0, 3, false); if (n_args == 0) { // no args: construct an empty bytearray return MP_OBJ_FROM_PTR(array_new(BYTEARRAY_TYPECODE, 0)); } else if (mp_obj_is_int(args[0])) { // 1 arg, an integer: construct a blank bytearray of that length mp_uint_t len = mp_obj_get_int(args[0]); mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, len); memset(o->items, 0, len); return MP_OBJ_FROM_PTR(o); } else { // 1 arg: construct the bytearray from that return array_construct(BYTEARRAY_TYPECODE, args[0]); } } #endif #if MICROPY_PY_BUILTINS_MEMORYVIEW mp_obj_t mp_obj_new_memoryview(byte typecode, size_t nitems, void *items) { mp_obj_array_t *self = m_new_obj(mp_obj_array_t); mp_obj_memoryview_init(self, typecode, 0, nitems, items); return MP_OBJ_FROM_PTR(self); } STATIC mp_obj_t memoryview_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; // TODO possibly allow memoryview constructor to take start/stop so that one // can do memoryview(b, 4, 8) instead of memoryview(b)[4:8] (uses less RAM) mp_arg_check_num(n_args, n_kw, 1, 1, false); mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); mp_obj_array_t *self = MP_OBJ_TO_PTR(mp_obj_new_memoryview(bufinfo.typecode, bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL), bufinfo.buf)); // If the input object is a memoryview then need to point the items of the // new memoryview to the start of the buffer so the GC can trace it. if (mp_obj_get_type(args[0]) == &mp_type_memoryview) { mp_obj_array_t *other = MP_OBJ_TO_PTR(args[0]); self->memview_offset = other->memview_offset; self->items = other->items; } // test if the object can be written to if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) { self->typecode |= MP_OBJ_ARRAY_TYPECODE_FLAG_RW; // indicate writable buffer } return MP_OBJ_FROM_PTR(self); } #if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE STATIC void memoryview_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { return; } if (attr == MP_QSTR_itemsize) { mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); dest[0] = MP_OBJ_NEW_SMALL_INT(mp_binary_get_size('@', self->typecode & TYPECODE_MASK, NULL)); } } #endif #endif STATIC mp_obj_t array_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(o->len != 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(o->len); default: return MP_OBJ_NULL; // op not supported } } STATIC int typecode_for_comparison(int typecode, bool *is_unsigned) { if (typecode == BYTEARRAY_TYPECODE) { typecode = 'B'; } if (typecode <= 'Z') { typecode += 32; // to lowercase *is_unsigned = true; } return typecode; } STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_obj_array_t *lhs = MP_OBJ_TO_PTR(lhs_in); switch (op) { case MP_BINARY_OP_ADD: { // allow to add anything that has the buffer protocol (extension to CPython) mp_buffer_info_t lhs_bufinfo; mp_buffer_info_t rhs_bufinfo; array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); mp_get_buffer_raise(rhs_in, &rhs_bufinfo, MP_BUFFER_READ); size_t sz = mp_binary_get_size('@', lhs_bufinfo.typecode, NULL); // convert byte count to element count (in case rhs is not multiple of sz) size_t rhs_len = rhs_bufinfo.len / sz; // note: lhs->len is element count of lhs, lhs_bufinfo.len is byte count mp_obj_array_t *res = array_new(lhs_bufinfo.typecode, lhs->len + rhs_len); mp_seq_cat((byte *)res->items, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_len * sz, byte); return MP_OBJ_FROM_PTR(res); } case MP_BINARY_OP_INPLACE_ADD: { #if MICROPY_PY_BUILTINS_MEMORYVIEW if (lhs->base.type == &mp_type_memoryview) { return MP_OBJ_NULL; // op not supported } #endif array_extend(lhs_in, rhs_in); return lhs_in; } case MP_BINARY_OP_CONTAINS: { #if MICROPY_PY_BUILTINS_BYTEARRAY // Can search string only in bytearray mp_buffer_info_t lhs_bufinfo; mp_buffer_info_t rhs_bufinfo; if (mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { if (!mp_obj_is_type(lhs_in, &mp_type_bytearray)) { return mp_const_false; } array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); return mp_obj_new_bool( find_subbytes(lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len, 1) != NULL); } #endif // Otherwise, can only look for a scalar numeric value in an array if (mp_obj_is_int(rhs_in) || mp_obj_is_float(rhs_in)) { mp_raise_NotImplementedError(NULL); } return mp_const_false; } case MP_BINARY_OP_EQUAL: case MP_BINARY_OP_LESS: case MP_BINARY_OP_LESS_EQUAL: case MP_BINARY_OP_MORE: case MP_BINARY_OP_MORE_EQUAL: { mp_buffer_info_t lhs_bufinfo; mp_buffer_info_t rhs_bufinfo; array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); if (!mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { return mp_const_false; } // mp_seq_cmp_bytes is used so only compatible representations can be correctly compared. // The type doesn't matter: array/bytearray/str/bytes all have the same buffer layout, so // just check if the typecodes are compatible; for testing equality the types should have the // same code except for signedness, and not be floating point because nan never equals nan. // For > and < the types should be the same and unsigned. // Note that typecode_for_comparison always returns lowercase letters to save code size. // No need for (& TYPECODE_MASK) here: xxx_get_buffer already takes care of that. bool is_unsigned = false; const int lhs_code = typecode_for_comparison(lhs_bufinfo.typecode, &is_unsigned); const int rhs_code = typecode_for_comparison(rhs_bufinfo.typecode, &is_unsigned); if (lhs_code == rhs_code && lhs_code != 'f' && lhs_code != 'd' && (op == MP_BINARY_OP_EQUAL || is_unsigned)) { return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len)); } // mp_obj_equal_not_equal treats returning MP_OBJ_NULL as 'fall back to pointer comparison' // for MP_BINARY_OP_EQUAL but that is incompatible with CPython. mp_raise_NotImplementedError(NULL); } default: return MP_OBJ_NULL; // op not supported } } #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg) { // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); if (self->free == 0) { size_t item_sz = mp_binary_get_size('@', self->typecode, NULL); // TODO: alloc policy self->free = 8; self->items = m_renew(byte, self->items, item_sz * self->len, item_sz * (self->len + self->free)); mp_seq_clear(self->items, self->len + 1, self->len + self->free, item_sz); } mp_binary_set_val_array(self->typecode, self->items, self->len, arg); // only update length/free if set succeeded self->len++; self->free--; return mp_const_none; // return None, as per CPython } STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_append_obj, array_append); STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) { // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); // allow to extend by anything that has the buffer protocol (extension to CPython) mp_buffer_info_t arg_bufinfo; mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ); size_t sz = mp_binary_get_size('@', self->typecode, NULL); // convert byte count to element count size_t len = arg_bufinfo.len / sz; // make sure we have enough room to extend // TODO: alloc policy; at the moment we go conservative if (self->free < len) { self->items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz); self->free = 0; } else { self->free -= len; } // extend mp_seq_copy((byte *)self->items + self->len * sz, arg_bufinfo.buf, len * sz, byte); self->len += len; return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_extend_obj, array_extend); #endif STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { if (value == MP_OBJ_NULL) { // delete item // TODO implement // TODO: confirmed that both bytearray and array.array support // slice deletion return MP_OBJ_NULL; // op not supported } else { mp_obj_array_t *o = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index_in, &mp_type_slice)) { mp_bound_slice_t slice; if (!mp_seq_get_fast_slice_indexes(o->len, index_in, &slice)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); } if (value != MP_OBJ_SENTINEL) { #if MICROPY_PY_ARRAY_SLICE_ASSIGN // Assign size_t src_len; void *src_items; size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); if (mp_obj_is_obj(value) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type->subscr == array_subscr) { // value is array, bytearray or memoryview mp_obj_array_t *src_slice = MP_OBJ_TO_PTR(value); if (item_sz != mp_binary_get_size('@', src_slice->typecode & TYPECODE_MASK, NULL)) { compat_error: mp_raise_ValueError(MP_ERROR_TEXT("lhs and rhs should be compatible")); } src_len = src_slice->len; src_items = src_slice->items; #if MICROPY_PY_BUILTINS_MEMORYVIEW if (mp_obj_is_type(value, &mp_type_memoryview)) { src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz); } #endif } else if (mp_obj_is_type(value, &mp_type_bytes)) { if (item_sz != 1) { goto compat_error; } mp_buffer_info_t bufinfo; mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_READ); src_len = bufinfo.len; src_items = bufinfo.buf; } else { mp_raise_NotImplementedError(MP_ERROR_TEXT("array/bytes required on right side")); } // TODO: check src/dst compat mp_int_t len_adj = src_len - (slice.stop - slice.start); uint8_t *dest_items = o->items; #if MICROPY_PY_BUILTINS_MEMORYVIEW if (o->base.type == &mp_type_memoryview) { if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { // store to read-only memoryview not allowed return MP_OBJ_NULL; } if (len_adj != 0) { goto compat_error; } dest_items += o->memview_offset * item_sz; } #endif if (len_adj > 0) { if ((size_t)len_adj > o->free) { // TODO: alloc policy; at the moment we go conservative o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz); o->free = len_adj; dest_items = o->items; } mp_seq_replace_slice_grow_inplace(dest_items, o->len, slice.start, slice.stop, src_items, src_len, len_adj, item_sz); } else { mp_seq_replace_slice_no_grow(dest_items, o->len, slice.start, slice.stop, src_items, src_len, item_sz); // Clear "freed" elements at the end of list // TODO: This is actually only needed for typecode=='O' mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz); // TODO: alloc policy after shrinking } o->free -= len_adj; o->len += len_adj; return mp_const_none; #else return MP_OBJ_NULL; // op not supported #endif } mp_obj_array_t *res; size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); assert(sz > 0); #if MICROPY_PY_BUILTINS_MEMORYVIEW if (o->base.type == &mp_type_memoryview) { res = m_new_obj(mp_obj_array_t); *res = *o; res->memview_offset += slice.start; res->len = slice.stop - slice.start; } else #endif { res = array_new(o->typecode, slice.stop - slice.start); memcpy(res->items, (uint8_t *)o->items + slice.start * sz, (slice.stop - slice.start) * sz); } return MP_OBJ_FROM_PTR(res); } else #endif { size_t index = mp_get_index(o->base.type, o->len, index_in, false); #if MICROPY_PY_BUILTINS_MEMORYVIEW if (o->base.type == &mp_type_memoryview) { index += o->memview_offset; if (value != MP_OBJ_SENTINEL && !(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { // store to read-only memoryview return MP_OBJ_NULL; } } #endif if (value == MP_OBJ_SENTINEL) { // load return mp_binary_get_val_array(o->typecode & TYPECODE_MASK, o->items, index); } else { // store mp_binary_set_val_array(o->typecode & TYPECODE_MASK, o->items, index, value); return mp_const_none; } } } } STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); bufinfo->buf = o->items; bufinfo->len = o->len * sz; bufinfo->typecode = o->typecode & TYPECODE_MASK; #if MICROPY_PY_BUILTINS_MEMORYVIEW if (o->base.type == &mp_type_memoryview) { if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW) && (flags & MP_BUFFER_WRITE)) { // read-only memoryview return 1; } bufinfo->buf = (uint8_t *)bufinfo->buf + (size_t)o->memview_offset * sz; } #else (void)flags; #endif return 0; } #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY STATIC const mp_rom_map_elem_t array_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&array_append_obj) }, { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&array_extend_obj) }, #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(array_locals_dict, array_locals_dict_table); #endif #if MICROPY_PY_ARRAY const mp_obj_type_t mp_type_array = { { &mp_type_type }, .name = MP_QSTR_array, .print = array_print, .make_new = array_make_new, .getiter = array_iterator_new, .unary_op = array_unary_op, .binary_op = array_binary_op, .subscr = array_subscr, .buffer_p = { .get_buffer = array_get_buffer }, .locals_dict = (mp_obj_dict_t *)&array_locals_dict, }; #endif #if MICROPY_PY_BUILTINS_BYTEARRAY const mp_obj_type_t mp_type_bytearray = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, .name = MP_QSTR_bytearray, .print = array_print, .make_new = bytearray_make_new, .getiter = array_iterator_new, .unary_op = array_unary_op, .binary_op = array_binary_op, .subscr = array_subscr, .buffer_p = { .get_buffer = array_get_buffer }, .locals_dict = (mp_obj_dict_t *)&array_locals_dict, }; #endif #if MICROPY_PY_BUILTINS_MEMORYVIEW const mp_obj_type_t mp_type_memoryview = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, .name = MP_QSTR_memoryview, .make_new = memoryview_make_new, .getiter = array_iterator_new, .unary_op = array_unary_op, .binary_op = array_binary_op, #if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE .attr = memoryview_attr, #endif .subscr = array_subscr, .buffer_p = { .get_buffer = array_get_buffer }, }; #endif /* unused size_t mp_obj_array_len(mp_obj_t self_in) { return ((mp_obj_array_t *)self_in)->len; } */ #if MICROPY_PY_BUILTINS_BYTEARRAY mp_obj_t mp_obj_new_bytearray(size_t n, void *items) { mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, n); memcpy(o->items, items, n); return MP_OBJ_FROM_PTR(o); } // Create bytearray which references specified memory area mp_obj_t mp_obj_new_bytearray_by_ref(size_t n, void *items) { mp_obj_array_t *o = m_new_obj(mp_obj_array_t); o->base.type = &mp_type_bytearray; o->typecode = BYTEARRAY_TYPECODE; o->free = 0; o->len = n; o->items = items; return MP_OBJ_FROM_PTR(o); } #endif /******************************************************************************/ // array iterator typedef struct _mp_obj_array_it_t { mp_obj_base_t base; mp_obj_array_t *array; size_t offset; size_t cur; } mp_obj_array_it_t; STATIC mp_obj_t array_it_iternext(mp_obj_t self_in) { mp_obj_array_it_t *self = MP_OBJ_TO_PTR(self_in); if (self->cur < self->array->len) { return mp_binary_get_val_array(self->array->typecode & TYPECODE_MASK, self->array->items, self->offset + self->cur++); } else { return MP_OBJ_STOP_ITERATION; } } STATIC const mp_obj_type_t mp_type_array_it = { { &mp_type_type }, .name = MP_QSTR_iterator, .getiter = mp_identity_getiter, .iternext = array_it_iternext, }; STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_array_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_array_t *array = MP_OBJ_TO_PTR(array_in); mp_obj_array_it_t *o = (mp_obj_array_it_t *)iter_buf; o->base.type = &mp_type_array_it; o->array = array; o->offset = 0; o->cur = 0; #if MICROPY_PY_BUILTINS_MEMORYVIEW if (array->base.type == &mp_type_memoryview) { o->offset = array->memview_offset; } #endif return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW #include "py/objtuple.h" #if MICROPY_PY_ATTRTUPLE || MICROPY_PY_COLLECTIONS // this helper function is used by collections.namedtuple #if !MICROPY_PY_COLLECTIONS STATIC #endif void mp_obj_attrtuple_print_helper(const mp_print_t *print, const qstr *fields, mp_obj_tuple_t *o) { mp_print_str(print, "("); for (size_t i = 0; i < o->len; i++) { if (i > 0) { mp_print_str(print, ", "); } mp_printf(print, "%q=", fields[i]); mp_obj_print_helper(print, o->items[i], PRINT_REPR); } mp_print_str(print, ")"); } #endif #if MICROPY_PY_ATTRTUPLE STATIC void mp_obj_attrtuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_tuple_t *o = MP_OBJ_TO_PTR(o_in); const qstr *fields = (const qstr *)MP_OBJ_TO_PTR(o->items[o->len]); mp_obj_attrtuple_print_helper(print, fields, o); } STATIC void mp_obj_attrtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { // load attribute mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); size_t len = self->len; const qstr *fields = (const qstr *)MP_OBJ_TO_PTR(self->items[len]); for (size_t i = 0; i < len; i++) { if (fields[i] == attr) { dest[0] = self->items[i]; return; } } } } mp_obj_t mp_obj_new_attrtuple(const qstr *fields, size_t n, const mp_obj_t *items) { mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n + 1); o->base.type = &mp_type_attrtuple; o->len = n; for (size_t i = 0; i < n; i++) { o->items[i] = items[i]; } o->items[n] = MP_OBJ_FROM_PTR(fields); return MP_OBJ_FROM_PTR(o); } const mp_obj_type_t mp_type_attrtuple = { { &mp_type_type }, .name = MP_QSTR_tuple, // reuse tuple to save on a qstr .print = mp_obj_attrtuple_print, .unary_op = mp_obj_tuple_unary_op, .binary_op = mp_obj_tuple_binary_op, .attr = mp_obj_attrtuple_attr, .subscr = mp_obj_tuple_subscr, .getiter = mp_obj_tuple_getiter, }; #endif // MICROPY_PY_ATTRTUPLE #include #include "py/runtime.h" #if MICROPY_OBJ_IMMEDIATE_OBJS #define BOOL_VALUE(o) ((o) == mp_const_false ? 0 : 1) #else #define BOOL_VALUE(o) (((mp_obj_bool_t *)MP_OBJ_TO_PTR(o))->value) typedef struct _mp_obj_bool_t { mp_obj_base_t base; bool value; } mp_obj_bool_t; #endif STATIC void bool_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { bool value = BOOL_VALUE(self_in); if (MICROPY_PY_UJSON && kind == PRINT_JSON) { if (value) { mp_print_str(print, "true"); } else { mp_print_str(print, "false"); } } else { if (value) { mp_print_str(print, "True"); } else { mp_print_str(print, "False"); } } } STATIC mp_obj_t bool_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 1, false); if (n_args == 0) { return mp_const_false; } else { return mp_obj_new_bool(mp_obj_is_true(args[0])); } } STATIC mp_obj_t bool_unary_op(mp_unary_op_t op, mp_obj_t o_in) { if (op == MP_UNARY_OP_LEN) { return MP_OBJ_NULL; } bool value = BOOL_VALUE(o_in); return mp_unary_op(op, MP_OBJ_NEW_SMALL_INT(value)); } STATIC mp_obj_t bool_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { bool value = BOOL_VALUE(lhs_in); return mp_binary_op(op, MP_OBJ_NEW_SMALL_INT(value), rhs_in); } const mp_obj_type_t mp_type_bool = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, // can match all numeric types .name = MP_QSTR_bool, .print = bool_print, .make_new = bool_make_new, .unary_op = bool_unary_op, .binary_op = bool_binary_op, }; #if !MICROPY_OBJ_IMMEDIATE_OBJS const mp_obj_bool_t mp_const_false_obj = {{&mp_type_bool}, false}; const mp_obj_bool_t mp_const_true_obj = {{&mp_type_bool}, true}; #endif #include #include "py/obj.h" #include "py/runtime.h" typedef struct _mp_obj_bound_meth_t { mp_obj_base_t base; mp_obj_t meth; mp_obj_t self; } mp_obj_bound_meth_t; #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED STATIC void bound_meth_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_bound_meth_t *o = MP_OBJ_TO_PTR(o_in); mp_printf(print, "self, PRINT_REPR); mp_print_str(print, "."); mp_obj_print_helper(print, o->meth, PRINT_REPR); mp_print_str(print, ">"); } #endif mp_obj_t mp_call_method_self_n_kw(mp_obj_t meth, mp_obj_t self, size_t n_args, size_t n_kw, const mp_obj_t *args) { // need to insert self before all other args and then call meth size_t n_total = n_args + 2 * n_kw; mp_obj_t *args2 = NULL; #if MICROPY_ENABLE_PYSTACK args2 = mp_pystack_alloc(sizeof(mp_obj_t) * (1 + n_total)); #else mp_obj_t *free_args2 = NULL; if (n_total > 4) { // try to use heap to allocate temporary args array args2 = m_new_maybe(mp_obj_t, 1 + n_total); free_args2 = args2; } if (args2 == NULL) { // (fallback to) use stack to allocate temporary args array args2 = alloca(sizeof(mp_obj_t) * (1 + n_total)); } #endif args2[0] = self; memcpy(args2 + 1, args, n_total * sizeof(mp_obj_t)); mp_obj_t res = mp_call_function_n_kw(meth, n_args + 1, n_kw, args2); #if MICROPY_ENABLE_PYSTACK mp_pystack_free(args2); #else if (free_args2 != NULL) { m_del(mp_obj_t, free_args2, 1 + n_total); } #endif return res; } STATIC mp_obj_t bound_meth_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_bound_meth_t *self = MP_OBJ_TO_PTR(self_in); return mp_call_method_self_n_kw(self->meth, self->self, n_args, n_kw, args); } #if MICROPY_PY_FUNCTION_ATTRS STATIC void bound_meth_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } // Delegate the load to the method object mp_obj_bound_meth_t *self = MP_OBJ_TO_PTR(self_in); mp_load_method_maybe(self->meth, attr, dest); } #endif STATIC const mp_obj_type_t mp_type_bound_meth = { { &mp_type_type }, .name = MP_QSTR_bound_method, #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED .print = bound_meth_print, #endif .call = bound_meth_call, #if MICROPY_PY_FUNCTION_ATTRS .attr = bound_meth_attr, #endif }; mp_obj_t mp_obj_new_bound_meth(mp_obj_t meth, mp_obj_t self) { mp_obj_bound_meth_t *o = m_new_obj(mp_obj_bound_meth_t); o->base.type = &mp_type_bound_meth; o->meth = meth; o->self = self; return MP_OBJ_FROM_PTR(o); } #include "py/obj.h" typedef struct _mp_obj_cell_t { mp_obj_base_t base; mp_obj_t obj; } mp_obj_cell_t; mp_obj_t mp_obj_cell_get(mp_obj_t self_in) { mp_obj_cell_t *self = MP_OBJ_TO_PTR(self_in); return self->obj; } void mp_obj_cell_set(mp_obj_t self_in, mp_obj_t obj) { mp_obj_cell_t *self = MP_OBJ_TO_PTR(self_in); self->obj = obj; } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED STATIC void cell_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_cell_t *o = MP_OBJ_TO_PTR(o_in); mp_printf(print, "obj); if (o->obj == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); } else { mp_obj_print_helper(print, o->obj, PRINT_REPR); } mp_print_str(print, ">"); } #endif STATIC const mp_obj_type_t mp_type_cell = { { &mp_type_type }, .name = MP_QSTR_, // cell representation is just value in < > #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED .print = cell_print, #endif }; mp_obj_t mp_obj_new_cell(mp_obj_t obj) { mp_obj_cell_t *o = m_new_obj(mp_obj_cell_t); o->base.type = &mp_type_cell; o->obj = obj; return MP_OBJ_FROM_PTR(o); } #include #include "py/obj.h" #include "py/runtime.h" typedef struct _mp_obj_closure_t { mp_obj_base_t base; mp_obj_t fun; size_t n_closed; mp_obj_t closed[]; } mp_obj_closure_t; STATIC mp_obj_t closure_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_closure_t *self = MP_OBJ_TO_PTR(self_in); // need to concatenate closed-over-vars and args size_t n_total = self->n_closed + n_args + 2 * n_kw; if (n_total <= 5) { // use stack to allocate temporary args array mp_obj_t args2[5]; memcpy(args2, self->closed, self->n_closed * sizeof(mp_obj_t)); memcpy(args2 + self->n_closed, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); return mp_call_function_n_kw(self->fun, self->n_closed + n_args, n_kw, args2); } else { // use heap to allocate temporary args array mp_obj_t *args2 = m_new(mp_obj_t, n_total); memcpy(args2, self->closed, self->n_closed * sizeof(mp_obj_t)); memcpy(args2 + self->n_closed, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); mp_obj_t res = mp_call_function_n_kw(self->fun, self->n_closed + n_args, n_kw, args2); m_del(mp_obj_t, args2, n_total); return res; } } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED STATIC void closure_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_closure_t *o = MP_OBJ_TO_PTR(o_in); mp_print_str(print, "fun, PRINT_REPR); mp_printf(print, " at %p, n_closed=%u ", o, (int)o->n_closed); for (size_t i = 0; i < o->n_closed; i++) { if (o->closed[i] == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); } else { mp_obj_print_helper(print, o->closed[i], PRINT_REPR); } mp_print_str(print, " "); } mp_print_str(print, ">"); } #endif const mp_obj_type_t mp_type_closure = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_closure, #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED .print = closure_print, #endif .call = closure_call, }; mp_obj_t mp_obj_new_closure(mp_obj_t fun, size_t n_closed_over, const mp_obj_t *closed) { mp_obj_closure_t *o = m_new_obj_var(mp_obj_closure_t, mp_obj_t, n_closed_over); o->base.type = &mp_type_closure; o->fun = fun; o->n_closed = n_closed_over; memcpy(o->closed, closed, n_closed_over * sizeof(mp_obj_t)); return MP_OBJ_FROM_PTR(o); } #include #ifndef __IPU__ #include #endif #include #include "py/parsenum.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_COMPLEX #include #include "py/formatfloat.h" typedef struct _mp_obj_complex_t { mp_obj_base_t base; mp_float_t real; mp_float_t imag; } mp_obj_complex_t; STATIC void complex_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_complex_t *o = MP_OBJ_TO_PTR(o_in); #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT char buf[16]; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C const int precision = 6; #else const int precision = 7; #endif #else char buf[32]; const int precision = 16; #endif if (o->real == 0) { mp_format_float(o->imag, buf, sizeof(buf), 'g', precision, '\0'); mp_printf(print, "%sj", buf); } else { mp_format_float(o->real, buf, sizeof(buf), 'g', precision, '\0'); mp_printf(print, "(%s", buf); if (o->imag >= 0 || isnan(o->imag)) { mp_print_str(print, "+"); } mp_format_float(o->imag, buf, sizeof(buf), 'g', precision, '\0'); mp_printf(print, "%sj)", buf); } } STATIC mp_obj_t complex_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 2, false); switch (n_args) { case 0: return mp_obj_new_complex(0, 0); case 1: if (mp_obj_is_str(args[0])) { // a string, parse it size_t l; const char *s = mp_obj_str_get_data(args[0], &l); return mp_parse_num_decimal(s, l, true, true, NULL); } else if (mp_obj_is_type(args[0], &mp_type_complex)) { // a complex, just return it return args[0]; } else { // something else, try to cast it to a complex return mp_obj_new_complex(mp_obj_get_float(args[0]), 0); } case 2: default: { mp_float_t real, imag; if (mp_obj_is_type(args[0], &mp_type_complex)) { mp_obj_complex_get(args[0], &real, &imag); } else { real = mp_obj_get_float(args[0]); imag = 0; } if (mp_obj_is_type(args[1], &mp_type_complex)) { mp_float_t real2, imag2; mp_obj_complex_get(args[1], &real2, &imag2); real -= imag2; imag += real2; } else { imag += mp_obj_get_float(args[1]); } return mp_obj_new_complex(real, imag); } } } STATIC mp_obj_t complex_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_obj_complex_t *o = MP_OBJ_TO_PTR(o_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(o->real != 0 || o->imag != 0); case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT(mp_float_hash(o->real) ^ mp_float_hash(o->imag)); case MP_UNARY_OP_POSITIVE: return o_in; case MP_UNARY_OP_NEGATIVE: return mp_obj_new_complex(-o->real, -o->imag); case MP_UNARY_OP_ABS: return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(o->real * o->real + o->imag * o->imag)); default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t complex_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_obj_complex_t *lhs = MP_OBJ_TO_PTR(lhs_in); return mp_obj_complex_binary_op(op, lhs->real, lhs->imag, rhs_in); } STATIC void complex_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } mp_obj_complex_t *self = MP_OBJ_TO_PTR(self_in); if (attr == MP_QSTR_real) { dest[0] = mp_obj_new_float(self->real); } else if (attr == MP_QSTR_imag) { dest[0] = mp_obj_new_float(self->imag); } } const mp_obj_type_t mp_type_complex = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_NOT_REFLEXIVE | MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, .name = MP_QSTR_complex, .print = complex_print, .make_new = complex_make_new, .unary_op = complex_unary_op, .binary_op = complex_binary_op, .attr = complex_attr, }; mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag) { mp_obj_complex_t *o = m_new_obj(mp_obj_complex_t); o->base.type = &mp_type_complex; o->real = real; o->imag = imag; return MP_OBJ_FROM_PTR(o); } void mp_obj_complex_get(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag) { assert(mp_obj_is_type(self_in, &mp_type_complex)); mp_obj_complex_t *self = MP_OBJ_TO_PTR(self_in); *real = self->real; *imag = self->imag; } mp_obj_t mp_obj_complex_binary_op(mp_binary_op_t op, mp_float_t lhs_real, mp_float_t lhs_imag, mp_obj_t rhs_in) { mp_float_t rhs_real, rhs_imag; if (!mp_obj_get_complex_maybe(rhs_in, &rhs_real, &rhs_imag)) { return MP_OBJ_NULL; // op not supported } switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: lhs_real += rhs_real; lhs_imag += rhs_imag; break; case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_real -= rhs_real; lhs_imag -= rhs_imag; break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: { mp_float_t real; multiply: real = lhs_real * rhs_real - lhs_imag * rhs_imag; lhs_imag = lhs_real * rhs_imag + lhs_imag * rhs_real; lhs_real = real; break; } case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: mp_raise_TypeError(MP_ERROR_TEXT("can't truncate-divide a complex number")); case MP_BINARY_OP_TRUE_DIVIDE: case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: if (rhs_imag == 0) { if (rhs_real == 0) { mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("complex divide by zero")); } lhs_real /= rhs_real; lhs_imag /= rhs_real; } else if (rhs_real == 0) { mp_float_t real = lhs_imag / rhs_imag; lhs_imag = -lhs_real / rhs_imag; lhs_real = real; } else { mp_float_t rhs_len_sq = rhs_real * rhs_real + rhs_imag * rhs_imag; rhs_real /= rhs_len_sq; rhs_imag /= -rhs_len_sq; goto multiply; } break; case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: { // z1**z2 = exp(z2*ln(z1)) // = exp(z2*(ln(|z1|)+i*arg(z1))) // = exp( (x2*ln1 - y2*arg1) + i*(y2*ln1 + x2*arg1) ) // = exp(x3 + i*y3) // = exp(x3)*(cos(y3) + i*sin(y3)) mp_float_t abs1 = MICROPY_FLOAT_C_FUN(sqrt)(lhs_real * lhs_real + lhs_imag * lhs_imag); if (abs1 == 0) { if (rhs_imag == 0 && rhs_real >= 0) { lhs_real = (rhs_real == 0); } else { mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("0.0 to a complex power")); } } else { mp_float_t ln1 = MICROPY_FLOAT_C_FUN(log)(abs1); mp_float_t arg1 = MICROPY_FLOAT_C_FUN(atan2)(lhs_imag, lhs_real); mp_float_t x3 = rhs_real * ln1 - rhs_imag * arg1; mp_float_t y3 = rhs_imag * ln1 + rhs_real * arg1; mp_float_t exp_x3 = MICROPY_FLOAT_C_FUN(exp)(x3); lhs_real = exp_x3 * MICROPY_FLOAT_C_FUN(cos)(y3); lhs_imag = exp_x3 * MICROPY_FLOAT_C_FUN(sin)(y3); } break; } case MP_BINARY_OP_EQUAL: return mp_obj_new_bool(lhs_real == rhs_real && lhs_imag == rhs_imag); default: return MP_OBJ_NULL; // op not supported } return mp_obj_new_complex(lhs_real, lhs_imag); } #endif #ifndef __IPU__ #include #else #include #endif // for ssize_t #include #include "py/mpconfig.h" #if MICROPY_PY_COLLECTIONS_DEQUE #include "py/runtime.h" typedef struct _mp_obj_deque_t { mp_obj_base_t base; size_t alloc; size_t i_get; size_t i_put; mp_obj_t *items; uint32_t flags; #define FLAG_CHECK_OVERFLOW 1 } mp_obj_deque_t; STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 3, false); /* Initialization from existing sequence is not supported, so an empty tuple must be passed as such. */ if (args[0] != mp_const_empty_tuple) { mp_raise_ValueError(NULL); } // Protect against -1 leading to zero-length allocation and bad array access mp_int_t maxlen = mp_obj_get_int(args[1]); if (maxlen < 0) { mp_raise_ValueError(NULL); } mp_obj_deque_t *o = m_new_obj(mp_obj_deque_t); o->base.type = type; o->alloc = maxlen + 1; o->i_get = o->i_put = 0; o->items = m_new0(mp_obj_t, o->alloc); if (n_args > 2) { o->flags = mp_obj_get_int(args[2]); } return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t deque_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->i_get != self->i_put); case MP_UNARY_OP_LEN: { ssize_t len = self->i_put - self->i_get; if (len < 0) { len += self->alloc; } return MP_OBJ_NEW_SMALL_INT(len); } #if MICROPY_PY_SYS_GETSIZEOF case MP_UNARY_OP_SIZEOF: { size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; return MP_OBJ_NEW_SMALL_INT(sz); } #endif default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); size_t new_i_put = self->i_put + 1; if (new_i_put == self->alloc) { new_i_put = 0; } if (self->flags & FLAG_CHECK_OVERFLOW && new_i_put == self->i_get) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full")); } self->items[self->i_put] = arg; self->i_put = new_i_put; if (self->i_get == new_i_put) { if (++self->i_get == self->alloc) { self->i_get = 0; } } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(deque_append_obj, mp_obj_deque_append); STATIC mp_obj_t deque_popleft(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); if (self->i_get == self->i_put) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty")); } mp_obj_t ret = self->items[self->i_get]; self->items[self->i_get] = MP_OBJ_NULL; if (++self->i_get == self->alloc) { self->i_get = 0; } return ret; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(deque_popleft_obj, deque_popleft); #if 0 STATIC mp_obj_t deque_clear(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); self->i_get = self->i_put = 0; mp_seq_clear(self->items, 0, self->alloc, sizeof(*self->items)); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(deque_clear_obj, deque_clear); #endif STATIC const mp_rom_map_elem_t deque_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&deque_append_obj) }, #if 0 { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&deque_clear_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&deque_popleft_obj) }, }; STATIC MP_DEFINE_CONST_DICT(deque_locals_dict, deque_locals_dict_table); const mp_obj_type_t mp_type_deque = { { &mp_type_type }, .name = MP_QSTR_deque, .make_new = deque_make_new, .unary_op = deque_unary_op, .locals_dict = (mp_obj_dict_t *)&deque_locals_dict, }; #endif // MICROPY_PY_COLLECTIONS_DEQUE #include #include #include "py/runtime.h" #include "py/builtin.h" #include "py/objtype.h" #include "py/objstr.h" const mp_obj_dict_t mp_const_empty_dict_obj = { .base = { .type = &mp_type_dict }, .map = { .all_keys_are_qstrs = 0, .is_fixed = 1, .is_ordered = 1, .used = 0, .alloc = 0, .table = NULL, } }; STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); // This is a helper function to iterate through a dictionary. The state of // the iteration is held in *cur and should be initialised with zero for the // first call. Will return NULL when no more elements are available. STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur) { size_t max = dict->map.alloc; mp_map_t *map = &dict->map; size_t i = *cur; for (; i < max; i++) { if (mp_map_slot_is_filled(map, i)) { *cur = i + 1; return &(map->table[i]); } } assert(map->used == 0 || i == max); return NULL; } STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); bool first = true; const char *item_separator = ", "; const char *key_separator = ": "; if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { kind = PRINT_REPR; } else { #if MICROPY_PY_UJSON_SEPARATORS item_separator = MP_PRINT_GET_EXT(print)->item_separator; key_separator = MP_PRINT_GET_EXT(print)->key_separator; #endif } if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { mp_printf(print, "%q(", self->base.type->name); } mp_print_str(print, "{"); size_t cur = 0; mp_map_elem_t *next = NULL; while ((next = dict_iter_next(self, &cur)) != NULL) { if (!first) { mp_print_str(print, item_separator); } first = false; bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key); if (add_quote) { mp_print_str(print, "\""); } mp_obj_print_helper(print, next->key, kind); if (add_quote) { mp_print_str(print, "\""); } mp_print_str(print, key_separator); mp_obj_print_helper(print, next->value, kind); } mp_print_str(print, "}"); if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { mp_print_str(print, ")"); } } mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_t dict_out = mp_obj_new_dict(0); mp_obj_dict_t *dict = MP_OBJ_TO_PTR(dict_out); dict->base.type = type; #if MICROPY_PY_COLLECTIONS_ORDEREDDICT if (type == &mp_type_ordereddict) { dict->map.is_ordered = 1; } #endif if (n_args > 0 || n_kw > 0) { mp_obj_t args2[2] = {dict_out, args[0]}; // args[0] is always valid, even if it's not a positional arg mp_map_t kwargs; mp_map_init_fixed_table(&kwargs, n_kw, args + n_args); dict_update(n_args + 1, args2, &kwargs); // dict_update will check that n_args + 1 == 1 or 2 } return dict_out; } STATIC mp_obj_t dict_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->map.used != 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(self->map.used); #if MICROPY_PY_SYS_GETSIZEOF case MP_UNARY_OP_SIZEOF: { size_t sz = sizeof(*self) + sizeof(*self->map.table) * self->map.alloc; return MP_OBJ_NEW_SMALL_INT(sz); } #endif default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_obj_dict_t *o = MP_OBJ_TO_PTR(lhs_in); switch (op) { case MP_BINARY_OP_CONTAINS: { mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP); return mp_obj_new_bool(elem != NULL); } case MP_BINARY_OP_EQUAL: { #if MICROPY_PY_COLLECTIONS_ORDEREDDICT if (MP_UNLIKELY(mp_obj_is_type(lhs_in, &mp_type_ordereddict) && mp_obj_is_type(rhs_in, &mp_type_ordereddict))) { // Iterate through both dictionaries simultaneously and compare keys and values. mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); size_t c1 = 0, c2 = 0; mp_map_elem_t *e1 = dict_iter_next(o, &c1), *e2 = dict_iter_next(rhs, &c2); for (; e1 != NULL && e2 != NULL; e1 = dict_iter_next(o, &c1), e2 = dict_iter_next(rhs, &c2)) { if (!mp_obj_equal(e1->key, e2->key) || !mp_obj_equal(e1->value, e2->value)) { return mp_const_false; } } return e1 == NULL && e2 == NULL ? mp_const_true : mp_const_false; } #endif if (mp_obj_is_type(rhs_in, &mp_type_dict)) { mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); if (o->map.used != rhs->map.used) { return mp_const_false; } size_t cur = 0; mp_map_elem_t *next = NULL; while ((next = dict_iter_next(o, &cur)) != NULL) { mp_map_elem_t *elem = mp_map_lookup(&rhs->map, next->key, MP_MAP_LOOKUP); if (elem == NULL || !mp_obj_equal(next->value, elem->value)) { return mp_const_false; } } return mp_const_true; } else { // dict is not equal to instance of any other type return mp_const_false; } } default: // op not supported return MP_OBJ_NULL; } } // Note: Make sure this is inlined in load part of dict_subscr() below. mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index) { mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); if (elem == NULL) { mp_raise_type_arg(&mp_type_KeyError, index); } else { return elem->value; } } STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (value == MP_OBJ_NULL) { // delete mp_obj_dict_delete(self_in, index); return mp_const_none; } else if (value == MP_OBJ_SENTINEL) { // load mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); if (elem == NULL) { mp_raise_type_arg(&mp_type_KeyError, index); } else { return elem->value; } } else { // store mp_obj_dict_store(self_in, index, value); return mp_const_none; } } /******************************************************************************/ /* dict methods */ STATIC void mp_ensure_not_fixed(const mp_obj_dict_t *dict) { if (dict->map.is_fixed) { mp_raise_TypeError(NULL); } } STATIC mp_obj_t dict_clear(mp_obj_t self_in) { mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); mp_map_clear(&self->map); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_clear_obj, dict_clear); mp_obj_t mp_obj_dict_copy(mp_obj_t self_in) { mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t other_out = mp_obj_new_dict(self->map.alloc); mp_obj_dict_t *other = MP_OBJ_TO_PTR(other_out); other->base.type = self->base.type; other->map.used = self->map.used; other->map.all_keys_are_qstrs = self->map.all_keys_are_qstrs; other->map.is_fixed = 0; other->map.is_ordered = self->map.is_ordered; memcpy(other->map.table, self->map.table, self->map.alloc * sizeof(mp_map_elem_t)); return other_out; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, mp_obj_dict_copy); #if MICROPY_PY_BUILTINS_DICT_FROMKEYS // this is a classmethod STATIC mp_obj_t dict_fromkeys(size_t n_args, const mp_obj_t *args) { mp_obj_t iter = mp_getiter(args[1], NULL); mp_obj_t value = mp_const_none; mp_obj_t next = MP_OBJ_NULL; if (n_args > 2) { value = args[2]; } // optimisation to allocate result based on len of argument mp_obj_t self_out; mp_obj_t len = mp_obj_len_maybe(args[1]); if (len == MP_OBJ_NULL) { /* object's type doesn't have a __len__ slot */ self_out = mp_obj_new_dict(0); } else { self_out = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len)); } mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_out); while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; } return self_out; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_fun_obj, 2, 3, dict_fromkeys); STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, MP_ROM_PTR(&dict_fromkeys_fun_obj)); #endif STATIC mp_obj_t dict_get_helper(size_t n_args, const mp_obj_t *args, mp_map_lookup_kind_t lookup_kind) { mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); if (lookup_kind != MP_MAP_LOOKUP) { mp_ensure_not_fixed(self); } mp_map_elem_t *elem = mp_map_lookup(&self->map, args[1], lookup_kind); mp_obj_t value; if (elem == NULL || elem->value == MP_OBJ_NULL) { if (n_args == 2) { if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { mp_raise_type_arg(&mp_type_KeyError, args[1]); } else { value = mp_const_none; } } else { value = args[2]; } if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { elem->value = value; } } else { value = elem->value; if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { elem->value = MP_OBJ_NULL; // so that GC can collect the deleted value } } return value; } STATIC mp_obj_t dict_get(size_t n_args, const mp_obj_t *args) { return dict_get_helper(n_args, args, MP_MAP_LOOKUP); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_get_obj, 2, 3, dict_get); STATIC mp_obj_t dict_pop(size_t n_args, const mp_obj_t *args) { return dict_get_helper(n_args, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_pop_obj, 2, 3, dict_pop); STATIC mp_obj_t dict_setdefault(size_t n_args, const mp_obj_t *args) { return dict_get_helper(n_args, args, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_setdefault_obj, 2, 3, dict_setdefault); STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); if (self->map.used == 0) { mp_raise_msg(&mp_type_KeyError, MP_ERROR_TEXT("popitem(): dictionary is empty")); } size_t cur = 0; #if MICROPY_PY_COLLECTIONS_ORDEREDDICT if (self->map.is_ordered) { cur = self->map.used - 1; } #endif mp_map_elem_t *next = dict_iter_next(self, &cur); assert(next); self->map.used--; mp_obj_t items[] = {next->key, next->value}; next->key = MP_OBJ_SENTINEL; // must mark key as sentinel to indicate that it was deleted next->value = MP_OBJ_NULL; mp_obj_t tuple = mp_obj_new_tuple(2, items); return tuple; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_popitem_obj, dict_popitem); STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); mp_ensure_not_fixed(self); mp_arg_check_num(n_args, kwargs->used, 1, 2, true); if (n_args == 2) { // given a positional argument if (mp_obj_is_dict_or_ordereddict(args[1])) { // update from other dictionary (make sure other is not self) if (args[1] != args[0]) { size_t cur = 0; mp_map_elem_t *elem = NULL; while ((elem = dict_iter_next((mp_obj_dict_t *)MP_OBJ_TO_PTR(args[1]), &cur)) != NULL) { mp_map_lookup(&self->map, elem->key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = elem->value; } } } else { // update from a generic iterable of pairs mp_obj_t iter = mp_getiter(args[1], NULL); mp_obj_t next = MP_OBJ_NULL; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_obj_t inneriter = mp_getiter(next, NULL); mp_obj_t key = mp_iternext(inneriter); mp_obj_t value = mp_iternext(inneriter); mp_obj_t stop = mp_iternext(inneriter); if (key == MP_OBJ_STOP_ITERATION || value == MP_OBJ_STOP_ITERATION || stop != MP_OBJ_STOP_ITERATION) { mp_raise_ValueError(MP_ERROR_TEXT("dict update sequence has wrong length")); } else { mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; } } } } // update the dict with any keyword args for (size_t i = 0; i < kwargs->alloc; i++) { if (mp_map_slot_is_filled(kwargs, i)) { mp_map_lookup(&self->map, kwargs->table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = kwargs->table[i].value; } } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dict_update_obj, 1, dict_update); /******************************************************************************/ /* dict views */ STATIC const mp_obj_type_t mp_type_dict_view; STATIC const mp_obj_type_t mp_type_dict_view_it; typedef enum _mp_dict_view_kind_t { MP_DICT_VIEW_ITEMS, MP_DICT_VIEW_KEYS, MP_DICT_VIEW_VALUES, } mp_dict_view_kind_t; STATIC const char *const mp_dict_view_names[] = {"dict_items", "dict_keys", "dict_values"}; typedef struct _mp_obj_dict_view_it_t { mp_obj_base_t base; mp_dict_view_kind_t kind; mp_obj_t dict; size_t cur; } mp_obj_dict_view_it_t; typedef struct _mp_obj_dict_view_t { mp_obj_base_t base; mp_obj_t dict; mp_dict_view_kind_t kind; } mp_obj_dict_view_t; STATIC mp_obj_t dict_view_it_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_dict_view_it)); mp_obj_dict_view_it_t *self = MP_OBJ_TO_PTR(self_in); mp_map_elem_t *next = dict_iter_next(MP_OBJ_TO_PTR(self->dict), &self->cur); if (next == NULL) { return MP_OBJ_STOP_ITERATION; } else { switch (self->kind) { case MP_DICT_VIEW_ITEMS: default: { mp_obj_t items[] = {next->key, next->value}; return mp_obj_new_tuple(2, items); } case MP_DICT_VIEW_KEYS: return next->key; case MP_DICT_VIEW_VALUES: return next->value; } } } STATIC const mp_obj_type_t mp_type_dict_view_it = { { &mp_type_type }, .name = MP_QSTR_iterator, .getiter = mp_identity_getiter, .iternext = dict_view_it_iternext, }; STATIC mp_obj_t dict_view_getiter(mp_obj_t view_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_check_self(mp_obj_is_type(view_in, &mp_type_dict_view)); mp_obj_dict_view_t *view = MP_OBJ_TO_PTR(view_in); mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; o->base.type = &mp_type_dict_view_it; o->kind = view->kind; o->dict = view->dict; o->cur = 0; return MP_OBJ_FROM_PTR(o); } STATIC void dict_view_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_check_self(mp_obj_is_type(self_in, &mp_type_dict_view)); mp_obj_dict_view_t *self = MP_OBJ_TO_PTR(self_in); bool first = true; mp_print_str(print, mp_dict_view_names[self->kind]); mp_print_str(print, "(["); mp_obj_iter_buf_t iter_buf; mp_obj_t self_iter = dict_view_getiter(self_in, &iter_buf); mp_obj_t next = MP_OBJ_NULL; while ((next = dict_view_it_iternext(self_iter)) != MP_OBJ_STOP_ITERATION) { if (!first) { mp_print_str(print, ", "); } first = false; mp_obj_print_helper(print, next, PRINT_REPR); } mp_print_str(print, "])"); } STATIC mp_obj_t dict_view_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { // only supported for the 'keys' kind until sets and dicts are refactored mp_obj_dict_view_t *o = MP_OBJ_TO_PTR(lhs_in); if (o->kind != MP_DICT_VIEW_KEYS) { return MP_OBJ_NULL; // op not supported } if (op != MP_BINARY_OP_CONTAINS) { return MP_OBJ_NULL; // op not supported } return dict_binary_op(op, o->dict, rhs_in); } STATIC const mp_obj_type_t mp_type_dict_view = { { &mp_type_type }, .name = MP_QSTR_dict_view, .print = dict_view_print, .binary_op = dict_view_binary_op, .getiter = dict_view_getiter, }; STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { mp_obj_dict_view_t *o = m_new_obj(mp_obj_dict_view_t); o->base.type = &mp_type_dict_view; o->dict = dict; o->kind = kind; return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); return mp_obj_new_dict_view(self_in, kind); } STATIC mp_obj_t dict_items(mp_obj_t self_in) { return dict_view(self_in, MP_DICT_VIEW_ITEMS); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_items_obj, dict_items); STATIC mp_obj_t dict_keys(mp_obj_t self_in) { return dict_view(self_in, MP_DICT_VIEW_KEYS); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_keys_obj, dict_keys); STATIC mp_obj_t dict_values(mp_obj_t self_in) { return dict_view(self_in, MP_DICT_VIEW_VALUES); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); /******************************************************************************/ /* dict iterator */ STATIC mp_obj_t dict_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; o->base.type = &mp_type_dict_view_it; o->kind = MP_DICT_VIEW_KEYS; o->dict = self_in; o->cur = 0; return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ /* dict constructors & public C API */ STATIC const mp_rom_map_elem_t dict_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&dict_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&dict_copy_obj) }, #if MICROPY_PY_BUILTINS_DICT_FROMKEYS { MP_ROM_QSTR(MP_QSTR_fromkeys), MP_ROM_PTR(&dict_fromkeys_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&dict_get_obj) }, { MP_ROM_QSTR(MP_QSTR_items), MP_ROM_PTR(&dict_items_obj) }, { MP_ROM_QSTR(MP_QSTR_keys), MP_ROM_PTR(&dict_keys_obj) }, { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&dict_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_popitem), MP_ROM_PTR(&dict_popitem_obj) }, { MP_ROM_QSTR(MP_QSTR_setdefault), MP_ROM_PTR(&dict_setdefault_obj) }, { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&dict_update_obj) }, { MP_ROM_QSTR(MP_QSTR_values), MP_ROM_PTR(&dict_values_obj) }, { MP_ROM_QSTR(MP_QSTR___getitem__), MP_ROM_PTR(&mp_op_getitem_obj) }, { MP_ROM_QSTR(MP_QSTR___setitem__), MP_ROM_PTR(&mp_op_setitem_obj) }, { MP_ROM_QSTR(MP_QSTR___delitem__), MP_ROM_PTR(&mp_op_delitem_obj) }, }; STATIC MP_DEFINE_CONST_DICT(dict_locals_dict, dict_locals_dict_table); const mp_obj_type_t mp_type_dict = { { &mp_type_type }, .name = MP_QSTR_dict, .print = dict_print, .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, .getiter = dict_getiter, .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, }; #if MICROPY_PY_COLLECTIONS_ORDEREDDICT const mp_obj_type_t mp_type_ordereddict = { { &mp_type_type }, .name = MP_QSTR_OrderedDict, .print = dict_print, .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, .getiter = dict_getiter, .parent = &mp_type_dict, .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, }; #endif void mp_obj_dict_init(mp_obj_dict_t *dict, size_t n_args) { dict->base.type = &mp_type_dict; mp_map_init(&dict->map, n_args); } mp_obj_t mp_obj_new_dict(size_t n_args) { mp_obj_dict_t *o = m_new_obj(mp_obj_dict_t); mp_obj_dict_init(o, n_args); return MP_OBJ_FROM_PTR(o); } size_t mp_obj_dict_len(mp_obj_t self_in) { mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); return self->map.used; } mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) { mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; return self_in; } mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key) { mp_obj_t args[2] = {self_in, key}; dict_get_helper(2, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND); return self_in; } #include #include #include "py/runtime.h" #if MICROPY_PY_BUILTINS_ENUMERATE typedef struct _mp_obj_enumerate_t { mp_obj_base_t base; mp_obj_t iter; mp_int_t cur; } mp_obj_enumerate_t; STATIC mp_obj_t enumerate_iternext(mp_obj_t self_in); STATIC mp_obj_t enumerate_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { #if MICROPY_CPYTHON_COMPAT static const mp_arg_t allowed_args[] = { { MP_QSTR_iterable, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_start, MP_ARG_INT, {.u_int = 0} }, }; // parse args struct { mp_arg_val_t iterable, start; } arg_vals; mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); // create enumerate object mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); o->base.type = type; o->iter = mp_getiter(arg_vals.iterable.u_obj, NULL); o->cur = arg_vals.start.u_int; #else mp_arg_check_num(n_args, n_kw, 1, 2, false); mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); o->base.type = type; o->iter = mp_getiter(args[0], NULL); o->cur = n_args > 1 ? mp_obj_get_int(args[1]) : 0; #endif return MP_OBJ_FROM_PTR(o); } const mp_obj_type_t mp_type_enumerate = { { &mp_type_type }, .name = MP_QSTR_enumerate, .make_new = enumerate_make_new, .iternext = enumerate_iternext, .getiter = mp_identity_getiter, }; STATIC mp_obj_t enumerate_iternext(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_enumerate)); mp_obj_enumerate_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next = mp_iternext(self->iter); if (next == MP_OBJ_STOP_ITERATION) { return MP_OBJ_STOP_ITERATION; } else { mp_obj_t items[] = {MP_OBJ_NEW_SMALL_INT(self->cur++), next}; return mp_obj_new_tuple(2, items); } } #endif // MICROPY_PY_BUILTINS_ENUMERATE #include #include #include #ifndef __IPU__ #include #endif #include "py/objlist.h" #include "py/objstr.h" #include "py/objtuple.h" #include "py/objtype.h" #include "py/runtime.h" #include "py/gc.h" #include "py/mperrno.h" #if MICROPY_ROM_TEXT_COMPRESSION && !defined(NO_QSTR) // Extract the MP_MAX_UNCOMPRESSED_TEXT_LEN macro from "genhdr/compressed.data.h". // Only need this if compression enabled and in a regular build (i.e. not during QSTR extraction). #define MP_MATCH_COMPRESSED(...) // Ignore #define MP_COMPRESSED_DATA(...) // Ignore #include "genhdr/compressed.data.h" #undef MP_MATCH_COMPRESSED #undef MP_COMPRESSED_DATA #endif // Number of items per traceback entry (file, line, block) #define TRACEBACK_ENTRY_LEN (3) // Optionally allocated buffer for storing some traceback, the tuple argument, // and possible string object and data, for when the heap is locked. #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF // When used the layout of the emergency exception buffer is: // - traceback entry (file, line, block) // - traceback entry (file, line, block) // - mp_obj_tuple_t object // - n_args * mp_obj_t for tuple // - mp_obj_str_t object // - string data #define EMG_BUF_TRACEBACK_OFFSET (0) #define EMG_BUF_TRACEBACK_SIZE (2 * TRACEBACK_ENTRY_LEN * sizeof(size_t)) #define EMG_BUF_TUPLE_OFFSET (EMG_BUF_TRACEBACK_OFFSET + EMG_BUF_TRACEBACK_SIZE) #define EMG_BUF_TUPLE_SIZE(n_args) (sizeof(mp_obj_tuple_t) + n_args * sizeof(mp_obj_t)) #define EMG_BUF_STR_OFFSET (EMG_BUF_TUPLE_OFFSET + EMG_BUF_TUPLE_SIZE(1)) #define EMG_BUF_STR_BUF_OFFSET (EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t)) #if MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE > 0 #define mp_emergency_exception_buf_size MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE void mp_init_emergency_exception_buf(void) { // Nothing to do since the buffer was declared statically. We put this // definition here so that the calling code can call this function // regardless of how its configured (makes the calling code a bit cleaner). } #else #define mp_emergency_exception_buf_size MP_STATE_VM(mp_emergency_exception_buf_size) void mp_init_emergency_exception_buf(void) { mp_emergency_exception_buf_size = 0; MP_STATE_VM(mp_emergency_exception_buf) = NULL; } mp_obj_t mp_alloc_emergency_exception_buf(mp_obj_t size_in) { mp_int_t size = mp_obj_get_int(size_in); void *buf = NULL; if (size > 0) { buf = m_new(byte, size); } int old_size = mp_emergency_exception_buf_size; void *old_buf = MP_STATE_VM(mp_emergency_exception_buf); // Update the 2 variables atomically so that an interrupt can't occur // between the assignments. mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); mp_emergency_exception_buf_size = size; MP_STATE_VM(mp_emergency_exception_buf) = buf; MICROPY_END_ATOMIC_SECTION(atomic_state); if (old_buf != NULL) { m_del(byte, old_buf, old_size); } return mp_const_none; } #endif #endif // MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF STATIC mp_obj_exception_t *get_native_exception(mp_obj_t self_in) { assert(mp_obj_is_exception_instance(self_in)); if (mp_obj_is_native_exception_instance(self_in)) { return MP_OBJ_TO_PTR(self_in); } else { return MP_OBJ_TO_PTR(((mp_obj_instance_t *)MP_OBJ_TO_PTR(self_in))->subobj[0]); } } STATIC void decompress_error_text_maybe(mp_obj_exception_t *o) { #if MICROPY_ROM_TEXT_COMPRESSION if (o->args->len == 1 && mp_obj_is_type(o->args->items[0], &mp_type_str)) { mp_obj_str_t *o_str = MP_OBJ_TO_PTR(o->args->items[0]); if (MP_IS_COMPRESSED_ROM_STRING(o_str->data)) { byte *buf = m_new_maybe(byte, MP_MAX_UNCOMPRESSED_TEXT_LEN + 1); if (!buf) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF // Try and use the emergency exception buf if enough space is available. buf = (byte *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_BUF_OFFSET); size_t avail = (uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size - buf; if (avail < MP_MAX_UNCOMPRESSED_TEXT_LEN + 1) { // No way to decompress, fallback to no message text. o->args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; return; } #else o->args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; return; #endif } mp_decompress_rom_string(buf, (mp_rom_error_text_t)o_str->data); o_str->data = buf; o_str->len = strlen((const char *)buf); o_str->hash = 0; } // Lazily compute the string hash. if (o_str->hash == 0) { o_str->hash = qstr_compute_hash(o_str->data, o_str->len); } } #endif } void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_exception_t *o = MP_OBJ_TO_PTR(o_in); mp_print_kind_t k = kind & ~PRINT_EXC_SUBCLASS; bool is_subclass = kind & PRINT_EXC_SUBCLASS; if (!is_subclass && (k == PRINT_REPR || k == PRINT_EXC)) { mp_print_str(print, qstr_str(o->base.type->name)); } if (k == PRINT_EXC) { mp_print_str(print, ": "); } decompress_error_text_maybe(o); if (k == PRINT_STR || k == PRINT_EXC) { if (o->args == NULL || o->args->len == 0) { mp_print_str(print, ""); return; } #if MICROPY_PY_UERRNO // try to provide a nice OSError error message if (o->base.type == &mp_type_OSError && o->args->len > 0 && o->args->len < 3 && mp_obj_is_small_int(o->args->items[0])) { qstr qst = mp_errno_to_str(o->args->items[0]); if (qst != MP_QSTRnull) { mp_printf(print, "[Errno " INT_FMT "] %q", MP_OBJ_SMALL_INT_VALUE(o->args->items[0]), qst); if (o->args->len > 1) { mp_print_str(print, ": "); mp_obj_print_helper(print, o->args->items[1], PRINT_STR); } return; } } #endif if (o->args->len == 1) { mp_obj_print_helper(print, o->args->items[0], PRINT_STR); return; } } mp_obj_tuple_print(print, MP_OBJ_FROM_PTR(o->args), kind); } mp_obj_t mp_obj_exception_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false); // Try to allocate memory for the exception, with fallback to emergency exception object mp_obj_exception_t *o_exc = m_new_obj_maybe(mp_obj_exception_t); if (o_exc == NULL) { o_exc = &MP_STATE_VM(mp_emergency_exception_obj); } // Populate the exception object o_exc->base.type = type; o_exc->traceback_data = NULL; mp_obj_tuple_t *o_tuple; if (n_args == 0) { // No args, can use the empty tuple straightaway o_tuple = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; } else { // Try to allocate memory for the tuple containing the args o_tuple = m_new_obj_var_maybe(mp_obj_tuple_t, mp_obj_t, n_args); #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF // If we are called by mp_obj_new_exception_msg_varg then it will have // reserved room (after the traceback data) for a tuple with 1 element. // Otherwise we are free to use the whole buffer after the traceback data. if (o_tuple == NULL && mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_TUPLE_OFFSET + EMG_BUF_TUPLE_SIZE(n_args))) { o_tuple = (mp_obj_tuple_t *) ((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_TUPLE_OFFSET); } #endif if (o_tuple == NULL) { // No memory for a tuple, fallback to an empty tuple o_tuple = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; } else { // Have memory for a tuple so populate it o_tuple->base.type = &mp_type_tuple; o_tuple->len = n_args; memcpy(o_tuple->items, args, n_args * sizeof(mp_obj_t)); } } // Store the tuple of args in the exception object o_exc->args = o_tuple; return MP_OBJ_FROM_PTR(o_exc); } // Get exception "value" - that is, first argument, or None mp_obj_t mp_obj_exception_get_value(mp_obj_t self_in) { mp_obj_exception_t *self = get_native_exception(self_in); if (self->args->len == 0) { return mp_const_none; } else { decompress_error_text_maybe(self); return self->args->items[0]; } } void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_exception_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] != MP_OBJ_NULL) { // store/delete attribute if (attr == MP_QSTR___traceback__ && dest[1] == mp_const_none) { // We allow 'exc.__traceback__ = None' assignment as low-level // optimization of pre-allocating exception instance and raising // it repeatedly - this avoids memory allocation during raise. // However, uPy will keep adding traceback entries to such // exception instance, so before throwing it, traceback should // be cleared like above. self->traceback_len = 0; dest[0] = MP_OBJ_NULL; // indicate success } return; } if (attr == MP_QSTR_args) { decompress_error_text_maybe(self); dest[0] = MP_OBJ_FROM_PTR(self->args); } else if (attr == MP_QSTR_value || attr == MP_QSTR_errno) { // These are aliases for args[0]: .value for StopIteration and .errno for OSError. // For efficiency let these attributes apply to all exception instances. dest[0] = mp_obj_exception_get_value(self_in); } } const mp_obj_type_t mp_type_BaseException = { { &mp_type_type }, .name = MP_QSTR_BaseException, .print = mp_obj_exception_print, .make_new = mp_obj_exception_make_new, .attr = mp_obj_exception_attr, }; // *FORMAT-OFF* // List of all exceptions, arranged as in the table at: // http://docs.python.org/3/library/exceptions.html MP_DEFINE_EXCEPTION(SystemExit, BaseException) MP_DEFINE_EXCEPTION(KeyboardInterrupt, BaseException) MP_DEFINE_EXCEPTION(GeneratorExit, BaseException) MP_DEFINE_EXCEPTION(Exception, BaseException) #if MICROPY_PY_ASYNC_AWAIT MP_DEFINE_EXCEPTION(StopAsyncIteration, Exception) #endif MP_DEFINE_EXCEPTION(StopIteration, Exception) MP_DEFINE_EXCEPTION(ArithmeticError, Exception) //MP_DEFINE_EXCEPTION(FloatingPointError, ArithmeticError) MP_DEFINE_EXCEPTION(OverflowError, ArithmeticError) MP_DEFINE_EXCEPTION(ZeroDivisionError, ArithmeticError) MP_DEFINE_EXCEPTION(AssertionError, Exception) MP_DEFINE_EXCEPTION(AttributeError, Exception) //MP_DEFINE_EXCEPTION(BufferError, Exception) MP_DEFINE_EXCEPTION(EOFError, Exception) MP_DEFINE_EXCEPTION(ImportError, Exception) MP_DEFINE_EXCEPTION(LookupError, Exception) MP_DEFINE_EXCEPTION(IndexError, LookupError) MP_DEFINE_EXCEPTION(KeyError, LookupError) MP_DEFINE_EXCEPTION(MemoryError, Exception) MP_DEFINE_EXCEPTION(NameError, Exception) /* MP_DEFINE_EXCEPTION(UnboundLocalError, NameError) */ MP_DEFINE_EXCEPTION(OSError, Exception) /* MP_DEFINE_EXCEPTION(BlockingIOError, OSError) MP_DEFINE_EXCEPTION(ChildProcessError, OSError) MP_DEFINE_EXCEPTION(ConnectionError, OSError) MP_DEFINE_EXCEPTION(BrokenPipeError, ConnectionError) MP_DEFINE_EXCEPTION(ConnectionAbortedError, ConnectionError) MP_DEFINE_EXCEPTION(ConnectionRefusedError, ConnectionError) MP_DEFINE_EXCEPTION(ConnectionResetError, ConnectionError) MP_DEFINE_EXCEPTION(InterruptedError, OSError) MP_DEFINE_EXCEPTION(IsADirectoryError, OSError) MP_DEFINE_EXCEPTION(NotADirectoryError, OSError) MP_DEFINE_EXCEPTION(PermissionError, OSError) MP_DEFINE_EXCEPTION(ProcessLookupError, OSError) MP_DEFINE_EXCEPTION(TimeoutError, OSError) MP_DEFINE_EXCEPTION(FileExistsError, OSError) MP_DEFINE_EXCEPTION(FileNotFoundError, OSError) MP_DEFINE_EXCEPTION(ReferenceError, Exception) */ MP_DEFINE_EXCEPTION(RuntimeError, Exception) MP_DEFINE_EXCEPTION(NotImplementedError, RuntimeError) MP_DEFINE_EXCEPTION(SyntaxError, Exception) MP_DEFINE_EXCEPTION(IndentationError, SyntaxError) /* MP_DEFINE_EXCEPTION(TabError, IndentationError) */ //MP_DEFINE_EXCEPTION(SystemError, Exception) MP_DEFINE_EXCEPTION(TypeError, Exception) #if MICROPY_EMIT_NATIVE MP_DEFINE_EXCEPTION(ViperTypeError, TypeError) #endif MP_DEFINE_EXCEPTION(ValueError, Exception) #if MICROPY_PY_BUILTINS_STR_UNICODE MP_DEFINE_EXCEPTION(UnicodeError, ValueError) //TODO: Implement more UnicodeError subclasses which take arguments #endif /* MP_DEFINE_EXCEPTION(Warning, Exception) MP_DEFINE_EXCEPTION(DeprecationWarning, Warning) MP_DEFINE_EXCEPTION(PendingDeprecationWarning, Warning) MP_DEFINE_EXCEPTION(RuntimeWarning, Warning) MP_DEFINE_EXCEPTION(SyntaxWarning, Warning) MP_DEFINE_EXCEPTION(UserWarning, Warning) MP_DEFINE_EXCEPTION(FutureWarning, Warning) MP_DEFINE_EXCEPTION(ImportWarning, Warning) MP_DEFINE_EXCEPTION(UnicodeWarning, Warning) MP_DEFINE_EXCEPTION(BytesWarning, Warning) MP_DEFINE_EXCEPTION(ResourceWarning, Warning) */ // *FORMAT-ON* mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) { assert(exc_type->make_new == mp_obj_exception_make_new); return mp_obj_exception_make_new(exc_type, 0, 0, NULL); } mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, size_t n_args, const mp_obj_t *args) { assert(exc_type->make_new == mp_obj_exception_make_new); return mp_obj_exception_make_new(exc_type, n_args, 0, args); } #if MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_NONE mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { // Check that the given type is an exception type assert(exc_type->make_new == mp_obj_exception_make_new); // Try to allocate memory for the message mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF // If memory allocation failed and there is an emergency buffer then try to use // that buffer to store the string object, reserving room at the start for the // traceback and 1-tuple. if (o_str == NULL && mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t))) { o_str = (mp_obj_str_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_OFFSET); } #endif if (o_str == NULL) { // No memory for the string object so create the exception with no args return mp_obj_exception_make_new(exc_type, 0, 0, NULL); } // Create the string object and call mp_obj_exception_make_new to create the exception o_str->base.type = &mp_type_str; o_str->len = strlen((const char *)msg); o_str->data = (const byte *)msg; #if MICROPY_ROM_TEXT_COMPRESSION o_str->hash = 0; // will be computed only if string object is accessed #else o_str->hash = qstr_compute_hash(o_str->data, o_str->len); #endif mp_obj_t arg = MP_OBJ_FROM_PTR(o_str); return mp_obj_exception_make_new(exc_type, 1, 0, &arg); } // The following struct and function implement a simple printer that conservatively // allocates memory and truncates the output data if no more memory can be obtained. // It leaves room for a null byte at the end of the buffer. struct _exc_printer_t { bool allow_realloc; size_t alloc; size_t len; byte *buf; }; STATIC void exc_add_strn(void *data, const char *str, size_t len) { struct _exc_printer_t *pr = data; if (pr->len + len >= pr->alloc) { // Not enough room for data plus a null byte so try to grow the buffer if (pr->allow_realloc) { size_t new_alloc = pr->alloc + len + 16; byte *new_buf = m_renew_maybe(byte, pr->buf, pr->alloc, new_alloc, true); if (new_buf == NULL) { pr->allow_realloc = false; len = pr->alloc - pr->len - 1; } else { pr->alloc = new_alloc; pr->buf = new_buf; } } else { len = pr->alloc - pr->len - 1; } } memcpy(pr->buf + pr->len, str, len); pr->len += len; } mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { va_list args; va_start(args, fmt); mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); va_end(args); return exc; } mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, va_list args) { assert(fmt != NULL); // Check that the given type is an exception type assert(exc_type->make_new == mp_obj_exception_make_new); // Try to allocate memory for the message mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); size_t o_str_alloc = strlen((const char *)fmt) + 1; byte *o_str_buf = m_new_maybe(byte, o_str_alloc); bool used_emg_buf = false; #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF // If memory allocation failed and there is an emergency buffer then try to use // that buffer to store the string object and its data (at least 16 bytes for // the string data), reserving room at the start for the traceback and 1-tuple. if ((o_str == NULL || o_str_buf == NULL) && mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t) + 16)) { used_emg_buf = true; o_str = (mp_obj_str_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_OFFSET); o_str_buf = (byte *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_BUF_OFFSET); o_str_alloc = (uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size - o_str_buf; } #endif if (o_str == NULL) { // No memory for the string object so create the exception with no args. // The exception will only have a type and no message (compression is irrelevant). return mp_obj_exception_make_new(exc_type, 0, 0, NULL); } if (o_str_buf == NULL) { // No memory for the string buffer: assume that the fmt string is in ROM // and use that data as the data of the string. // The string will point directly to the compressed data -- will need to be decompressed // prior to display (this case is identical to mp_obj_new_exception_msg above). o_str->len = o_str_alloc - 1; // will be equal to strlen(fmt) o_str->data = (const byte *)fmt; } else { // We have some memory to format the string. // TODO: Optimise this to format-while-decompressing (and not require the temp stack space). struct _exc_printer_t exc_pr = {!used_emg_buf, o_str_alloc, 0, o_str_buf}; mp_print_t print = {&exc_pr, exc_add_strn}; const char *fmt2 = (const char *)fmt; #if MICROPY_ROM_TEXT_COMPRESSION byte decompressed[MP_MAX_UNCOMPRESSED_TEXT_LEN]; if (MP_IS_COMPRESSED_ROM_STRING(fmt)) { mp_decompress_rom_string(decompressed, fmt); fmt2 = (const char *)decompressed; } #endif mp_vprintf(&print, fmt2, args); exc_pr.buf[exc_pr.len] = '\0'; o_str->len = exc_pr.len; o_str->data = exc_pr.buf; } // Create the string object and call mp_obj_exception_make_new to create the exception o_str->base.type = &mp_type_str; #if MICROPY_ROM_TEXT_COMPRESSION o_str->hash = 0; // will be computed only if string object is accessed #else o_str->hash = qstr_compute_hash(o_str->data, o_str->len); #endif mp_obj_t arg = MP_OBJ_FROM_PTR(o_str); return mp_obj_exception_make_new(exc_type, 1, 0, &arg); } #endif // return true if the given object is an exception type bool mp_obj_is_exception_type(mp_obj_t self_in) { if (mp_obj_is_type(self_in, &mp_type_type)) { // optimisation when self_in is a builtin exception mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (self->make_new == mp_obj_exception_make_new) { return true; } } return mp_obj_is_subclass_fast(self_in, MP_OBJ_FROM_PTR(&mp_type_BaseException)); } // return true if the given object is an instance of an exception type bool mp_obj_is_exception_instance(mp_obj_t self_in) { return mp_obj_is_exception_type(MP_OBJ_FROM_PTR(mp_obj_get_type(self_in))); } // Return true if exception (type or instance) is a subclass of given // exception type. Assumes exc_type is a subclass of BaseException, as // defined by mp_obj_is_exception_type(exc_type). bool mp_obj_exception_match(mp_obj_t exc, mp_const_obj_t exc_type) { // if exc is an instance of an exception, then extract and use its type if (mp_obj_is_exception_instance(exc)) { exc = MP_OBJ_FROM_PTR(mp_obj_get_type(exc)); } return mp_obj_is_subclass_fast(exc, exc_type); } // traceback handling functions void mp_obj_exception_clear_traceback(mp_obj_t self_in) { mp_obj_exception_t *self = get_native_exception(self_in); // just set the traceback to the null object // we don't want to call any memory management functions here self->traceback_data = NULL; } void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qstr block) { mp_obj_exception_t *self = get_native_exception(self_in); // append this traceback info to traceback data // if memory allocation fails (eg because gc is locked), just return #if MICROPY_PY_SYS_TRACEBACKLIMIT mp_int_t max_traceback = MP_OBJ_SMALL_INT_VALUE(MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT])); if (max_traceback <= 0) { return; } else if (self->traceback_data != NULL && self->traceback_len >= max_traceback * TRACEBACK_ENTRY_LEN) { self->traceback_len -= TRACEBACK_ENTRY_LEN; memmove(self->traceback_data, self->traceback_data + TRACEBACK_ENTRY_LEN, self->traceback_len * sizeof(self->traceback_data[0])); } #endif if (self->traceback_data == NULL) { self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); if (self->traceback_data == NULL) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF if (mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_TRACEBACK_OFFSET + EMG_BUF_TRACEBACK_SIZE)) { // There is room in the emergency buffer for traceback data size_t *tb = (size_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_TRACEBACK_OFFSET); self->traceback_data = tb; self->traceback_alloc = EMG_BUF_TRACEBACK_SIZE / sizeof(size_t); } else { // Can't allocate and no room in emergency buffer return; } #else // Can't allocate return; #endif } else { // Allocated the traceback data on the heap self->traceback_alloc = TRACEBACK_ENTRY_LEN; } self->traceback_len = 0; } else if (self->traceback_len + TRACEBACK_ENTRY_LEN > self->traceback_alloc) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF if (self->traceback_data == (size_t *)MP_STATE_VM(mp_emergency_exception_buf)) { // Can't resize the emergency buffer return; } #endif // be conservative with growing traceback data size_t *tb_data = m_renew_maybe(size_t, self->traceback_data, self->traceback_alloc, self->traceback_alloc + TRACEBACK_ENTRY_LEN, true); if (tb_data == NULL) { return; } self->traceback_data = tb_data; self->traceback_alloc += TRACEBACK_ENTRY_LEN; } size_t *tb_data = &self->traceback_data[self->traceback_len]; self->traceback_len += TRACEBACK_ENTRY_LEN; tb_data[0] = file; tb_data[1] = line; tb_data[2] = block; } void mp_obj_exception_get_traceback(mp_obj_t self_in, size_t *n, size_t **values) { mp_obj_exception_t *self = get_native_exception(self_in); if (self->traceback_data == NULL) { *n = 0; *values = NULL; } else { *n = self->traceback_len; *values = self->traceback_data; } } #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FILTER typedef struct _mp_obj_filter_t { mp_obj_base_t base; mp_obj_t fun; mp_obj_t iter; } mp_obj_filter_t; STATIC mp_obj_t filter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 2, false); mp_obj_filter_t *o = m_new_obj(mp_obj_filter_t); o->base.type = type; o->fun = args[0]; o->iter = mp_getiter(args[1], NULL); return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t filter_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_filter)); mp_obj_filter_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next; while ((next = mp_iternext(self->iter)) != MP_OBJ_STOP_ITERATION) { mp_obj_t val; if (self->fun != mp_const_none) { val = mp_call_function_n_kw(self->fun, 1, 0, &next); } else { val = next; } if (mp_obj_is_true(val)) { return next; } } return MP_OBJ_STOP_ITERATION; } const mp_obj_type_t mp_type_filter = { { &mp_type_type }, .name = MP_QSTR_filter, .make_new = filter_make_new, .getiter = mp_identity_getiter, .iternext = filter_iternext, }; #endif // MICROPY_PY_BUILTINS_FILTER #include #ifndef __IPU__ #include #endif #include #include #include "py/parsenum.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT #include #include "py/formatfloat.h" #if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D // M_E and M_PI are not part of the math.h standard and may not be defined #ifndef M_E #define M_E (2.7182818284590452354) #endif #ifndef M_PI #define M_PI (3.14159265358979323846) #endif typedef struct _mp_obj_float_t { mp_obj_base_t base; mp_float_t value; } mp_obj_float_t; const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, (mp_float_t)M_E}; const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, (mp_float_t)M_PI}; #if MICROPY_PY_MATH_CONSTANTS #ifndef NAN #error NAN macro is not defined #endif const mp_obj_float_t mp_const_float_tau_obj = {{&mp_type_float}, (mp_float_t)(2.0 * M_PI)}; const mp_obj_float_t mp_const_float_inf_obj = {{&mp_type_float}, (mp_float_t)INFINITY}; const mp_obj_float_t mp_const_float_nan_obj = {{&mp_type_float}, (mp_float_t)NAN}; #endif #endif #define MICROPY_FLOAT_ZERO MICROPY_FLOAT_CONST(0.0) #if MICROPY_FLOAT_HIGH_QUALITY_HASH // must return actual integer value if it fits in mp_int_t mp_int_t mp_float_hash(mp_float_t src) { mp_float_union_t u = {.f = src}; mp_int_t val; const int adj_exp = (int)u.p.exp - MP_FLOAT_EXP_BIAS; if (adj_exp < 0) { // value < 1; must be sure to handle 0.0 correctly (ie return 0) val = u.i; } else { // if adj_exp is max then: u.p.frc==0 indicates inf, else NaN // else: 1 <= value mp_float_uint_t frc = u.p.frc | ((mp_float_uint_t)1 << MP_FLOAT_FRAC_BITS); if (adj_exp <= MP_FLOAT_FRAC_BITS) { // number may have a fraction; xor the integer part with the fractional part val = (frc >> (MP_FLOAT_FRAC_BITS - adj_exp)) ^ (frc & (((mp_float_uint_t)1 << (MP_FLOAT_FRAC_BITS - adj_exp)) - 1)); } else if ((unsigned int)adj_exp < MP_BITS_PER_BYTE * sizeof(mp_int_t) - 1) { // the number is a (big) whole integer and will fit in val's signed-width val = (mp_int_t)frc << (adj_exp - MP_FLOAT_FRAC_BITS); } else { // integer part will overflow val's width so just use what bits we can val = frc; } } if (u.p.sgn) { val = -(mp_uint_t)val; } return val; } #endif STATIC void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_float_t o_val = mp_obj_float_get(o_in); #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT char buf[16]; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C const int precision = 6; #else const int precision = 7; #endif #else char buf[32]; const int precision = 16; #endif mp_format_float(o_val, buf, sizeof(buf), 'g', precision, '\0'); mp_print_str(print, buf); if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) { // Python floats always have decimal point (unless inf or nan) mp_print_str(print, ".0"); } } STATIC mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 1, false); switch (n_args) { case 0: return mp_obj_new_float(0); case 1: default: { mp_buffer_info_t bufinfo; if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) { // a textual representation, parse it return mp_parse_num_decimal(bufinfo.buf, bufinfo.len, false, false, NULL); } else if (mp_obj_is_float(args[0])) { // a float, just return it return args[0]; } else { // something else, try to cast it to a float return mp_obj_new_float(mp_obj_get_float(args[0])); } } } } STATIC mp_obj_t float_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_float_t val = mp_obj_float_get(o_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(val != 0); case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT(mp_float_hash(val)); case MP_UNARY_OP_POSITIVE: return o_in; case MP_UNARY_OP_NEGATIVE: return mp_obj_new_float(-val); case MP_UNARY_OP_ABS: { if (signbit(val)) { return mp_obj_new_float(-val); } else { return o_in; } } default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t float_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_float_t lhs_val = mp_obj_float_get(lhs_in); #if MICROPY_PY_BUILTINS_COMPLEX if (mp_obj_is_type(rhs_in, &mp_type_complex)) { return mp_obj_complex_binary_op(op, lhs_val, 0, rhs_in); } #endif return mp_obj_float_binary_op(op, lhs_val, rhs_in); } const mp_obj_type_t mp_type_float = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_NOT_REFLEXIVE | MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, .name = MP_QSTR_float, .print = float_print, .make_new = float_make_new, .unary_op = float_unary_op, .binary_op = float_binary_op, }; #if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D mp_obj_t mp_obj_new_float(mp_float_t value) { mp_obj_float_t *o = m_new(mp_obj_float_t, 1); o->base.type = &mp_type_float; o->value = value; return MP_OBJ_FROM_PTR(o); } mp_float_t mp_obj_float_get(mp_obj_t self_in) { assert(mp_obj_is_float(self_in)); mp_obj_float_t *self = MP_OBJ_TO_PTR(self_in); return self->value; } #endif STATIC void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y) { // logic here follows that of CPython // https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations // x == (x//y)*y + (x%y) // divmod(x, y) == (x//y, x%y) mp_float_t mod = MICROPY_FLOAT_C_FUN(fmod)(*x, *y); mp_float_t div = (*x - mod) / *y; // Python specs require that mod has same sign as second operand if (mod == MICROPY_FLOAT_ZERO) { mod = MICROPY_FLOAT_C_FUN(copysign)(MICROPY_FLOAT_ZERO, *y); } else { if ((mod < MICROPY_FLOAT_ZERO) != (*y < MICROPY_FLOAT_ZERO)) { mod += *y; div -= MICROPY_FLOAT_CONST(1.0); } } mp_float_t floordiv; if (div == MICROPY_FLOAT_ZERO) { // if division is zero, take the correct sign of zero floordiv = MICROPY_FLOAT_C_FUN(copysign)(MICROPY_FLOAT_ZERO, *x / *y); } else { // Python specs require that x == (x//y)*y + (x%y) floordiv = MICROPY_FLOAT_C_FUN(floor)(div); if (div - floordiv > MICROPY_FLOAT_CONST(0.5)) { floordiv += MICROPY_FLOAT_CONST(1.0); } } // return results *x = floordiv; *y = mod; } mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t rhs_in) { mp_float_t rhs_val; if (!mp_obj_get_float_maybe(rhs_in, &rhs_val)) { return MP_OBJ_NULL; // op not supported } switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: lhs_val += rhs_val; break; case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_val -= rhs_val; break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: lhs_val *= rhs_val; break; case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { zero_division_error: mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } // Python specs require that x == (x//y)*y + (x%y) so we must // call divmod to compute the correct floor division, which // returns the floor divide in lhs_val. mp_obj_float_divmod(&lhs_val, &rhs_val); break; case MP_BINARY_OP_TRUE_DIVIDE: case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: if (rhs_val == 0) { goto zero_division_error; } lhs_val /= rhs_val; break; case MP_BINARY_OP_MODULO: case MP_BINARY_OP_INPLACE_MODULO: if (rhs_val == MICROPY_FLOAT_ZERO) { goto zero_division_error; } lhs_val = MICROPY_FLOAT_C_FUN(fmod)(lhs_val, rhs_val); // Python specs require that mod has same sign as second operand if (lhs_val == MICROPY_FLOAT_ZERO) { lhs_val = MICROPY_FLOAT_C_FUN(copysign)(0.0, rhs_val); } else { if ((lhs_val < MICROPY_FLOAT_ZERO) != (rhs_val < MICROPY_FLOAT_ZERO)) { lhs_val += rhs_val; } } break; case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: if (lhs_val == 0 && rhs_val < 0 && !isinf(rhs_val)) { goto zero_division_error; } if (lhs_val < 0 && rhs_val != MICROPY_FLOAT_C_FUN(floor)(rhs_val) && !isnan(rhs_val)) { #if MICROPY_PY_BUILTINS_COMPLEX return mp_obj_complex_binary_op(MP_BINARY_OP_POWER, lhs_val, 0, rhs_in); #else mp_raise_ValueError(MP_ERROR_TEXT("complex values not supported")); #endif } #if MICROPY_PY_MATH_POW_FIX_NAN // Also see modmath.c. if (lhs_val == MICROPY_FLOAT_CONST(1.0) || rhs_val == MICROPY_FLOAT_CONST(0.0)) { lhs_val = MICROPY_FLOAT_CONST(1.0); break; } #endif lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break; case MP_BINARY_OP_DIVMOD: { if (rhs_val == 0) { goto zero_division_error; } mp_obj_float_divmod(&lhs_val, &rhs_val); mp_obj_t tuple[2] = { mp_obj_new_float(lhs_val), mp_obj_new_float(rhs_val), }; return mp_obj_new_tuple(2, tuple); } case MP_BINARY_OP_LESS: return mp_obj_new_bool(lhs_val < rhs_val); case MP_BINARY_OP_MORE: return mp_obj_new_bool(lhs_val > rhs_val); case MP_BINARY_OP_EQUAL: return mp_obj_new_bool(lhs_val == rhs_val); case MP_BINARY_OP_LESS_EQUAL: return mp_obj_new_bool(lhs_val <= rhs_val); case MP_BINARY_OP_MORE_EQUAL: return mp_obj_new_bool(lhs_val >= rhs_val); default: return MP_OBJ_NULL; // op not supported } return mp_obj_new_float(lhs_val); } #endif // MICROPY_PY_BUILTINS_FLOAT #include #include #include "py/objtuple.h" #include "py/objfun.h" #include "py/runtime.h" #include "py/bc.h" #include "py/stackctrl.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif // Note: the "name" entry in mp_obj_type_t for a function type must be // MP_QSTR_function because it is used to determine if an object is of generic // function type. /******************************************************************************/ /* builtin functions */ STATIC mp_obj_t fun_builtin_0_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)args; assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_0)); mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, 0, 0, false); return self->fun._0(); } const mp_obj_type_t mp_type_fun_builtin_0 = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_0_call, .unary_op = mp_generic_unary_op, }; STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_1)); mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, 1, 1, false); return self->fun._1(args[0]); } const mp_obj_type_t mp_type_fun_builtin_1 = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_1_call, .unary_op = mp_generic_unary_op, }; STATIC mp_obj_t fun_builtin_2_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_2)); mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, 2, 2, false); return self->fun._2(args[0], args[1]); } const mp_obj_type_t mp_type_fun_builtin_2 = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_2_call, .unary_op = mp_generic_unary_op, }; STATIC mp_obj_t fun_builtin_3_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_3)); mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, 3, 3, false); return self->fun._3(args[0], args[1], args[2]); } const mp_obj_type_t mp_type_fun_builtin_3 = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_3_call, .unary_op = mp_generic_unary_op, }; STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_var)); mp_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in); // check number of arguments mp_arg_check_num_sig(n_args, n_kw, self->sig); if (self->sig & 1) { // function allows keywords // we create a map directly from the given args array mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); return self->fun.kw(n_args, args, &kw_args); } else { // function takes a variable number of arguments, but no keywords return self->fun.var(n_args, args); } } const mp_obj_type_t mp_type_fun_builtin_var = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_var_call, .unary_op = mp_generic_unary_op, }; /******************************************************************************/ /* byte code functions */ STATIC qstr mp_obj_code_get_name(const mp_obj_fun_bc_t *fun, const byte *code_info) { MP_BC_PRELUDE_SIZE_DECODE(code_info); mp_uint_t name = mp_decode_uint_value(code_info); #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE name = fun->context->constants.qstr_table[name]; #endif return name; } #if MICROPY_EMIT_NATIVE STATIC const mp_obj_type_t mp_type_fun_native; #endif qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { const mp_obj_fun_bc_t *fun = MP_OBJ_TO_PTR(fun_in); #if MICROPY_EMIT_NATIVE if (fun->base.type == &mp_type_fun_native || fun->base.type == &mp_type_native_gen_wrap) { // TODO native functions don't have name stored return MP_QSTR_; } #endif const byte *bc = fun->bytecode; MP_BC_PRELUDE_SIG_DECODE(bc); return mp_obj_code_get_name(fun, bc); } #if MICROPY_CPYTHON_COMPAT STATIC void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(o_in); mp_printf(print, "", mp_obj_fun_get_name(o_in), o); } #endif #if DEBUG_PRINT STATIC void dump_args(const mp_obj_t *a, size_t sz) { DEBUG_printf("%p: ", a); for (size_t i = 0; i < sz; i++) { DEBUG_printf("%p ", a[i]); } DEBUG_printf("\n"); } #else #define dump_args(...) (void)0 #endif // With this macro you can tune the maximum number of function state bytes // that will be allocated on the stack. Any function that needs more // than this will try to use the heap, with fallback to stack allocation. #define VM_MAX_STATE_ON_STACK (sizeof(mp_uint_t) * 11) #define DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ { \ const uint8_t *ip = bytecode; \ size_t n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args; \ MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state_out_var, n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args); \ (void)scope_flags; (void)n_pos_args; (void)n_kwonly_args; (void)n_def_args; \ \ /* state size in bytes */ \ state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + n_exc_stack * sizeof(mp_exc_stack_t); \ } #define INIT_CODESTATE(code_state, _fun_bc, _n_state, n_args, n_kw, args) \ code_state->fun_bc = _fun_bc; \ code_state->ip = _fun_bc->bytecode; \ code_state->n_state = _n_state; \ mp_setup_code_state(code_state, n_args, n_kw, args); \ code_state->old_globals = mp_globals_get(); #if MICROPY_STACKLESS mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { MP_STACK_CHECK(); mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); size_t n_state, state_size; DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); mp_code_state_t *code_state; #if MICROPY_ENABLE_PYSTACK code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); #else // If we use m_new_obj_var(), then on no memory, MemoryError will be // raised. But this is not correct exception for a function call, // RuntimeError should be raised instead. So, we use m_new_obj_var_maybe(), // return NULL, then vm.c takes the needed action (either raise // RuntimeError or fallback to stack allocation). code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); if (!code_state) { return NULL; } #endif INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args); // execute the byte code with the correct globals context mp_globals_set(self->context->module.globals); return code_state; } #endif STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { MP_STACK_CHECK(); DEBUG_printf("Input n_args: " UINT_FMT ", n_kw: " UINT_FMT "\n", n_args, n_kw); DEBUG_printf("Input pos args: "); dump_args(args, n_args); DEBUG_printf("Input kw args: "); dump_args(args + n_args, n_kw * 2); mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); size_t n_state, state_size; DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); // allocate state for locals and stack mp_code_state_t *code_state = NULL; #if MICROPY_ENABLE_PYSTACK code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); #else if (state_size > VM_MAX_STATE_ON_STACK) { code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); #if MICROPY_DEBUG_VM_STACK_OVERFLOW if (code_state != NULL) { memset(code_state->state, 0, state_size); } #endif } if (code_state == NULL) { code_state = alloca(sizeof(mp_code_state_t) + state_size); #if MICROPY_DEBUG_VM_STACK_OVERFLOW memset(code_state->state, 0, state_size); #endif state_size = 0; // indicate that we allocated using alloca } #endif INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args); // execute the byte code with the correct globals context mp_globals_set(self->context->module.globals); mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); mp_globals_set(code_state->old_globals); #if MICROPY_DEBUG_VM_STACK_OVERFLOW if (vm_return_kind == MP_VM_RETURN_NORMAL) { if (code_state->sp < code_state->state) { mp_printf(MICROPY_DEBUG_PRINTER, "VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state); assert(0); } } const byte *bytecode_ptr = self->bytecode; size_t n_state_unused, n_exc_stack_unused, scope_flags_unused; size_t n_pos_args, n_kwonly_args, n_def_args_unused; MP_BC_PRELUDE_SIG_DECODE_INTO(bytecode_ptr, n_state_unused, n_exc_stack_unused, scope_flags_unused, n_pos_args, n_kwonly_args, n_def_args_unused); // We can't check the case when an exception is returned in state[0] // and there are no arguments, because in this case our detection slot may have // been overwritten by the returned exception (which is allowed). if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && n_pos_args + n_kwonly_args == 0)) { // Just check to see that we have at least 1 null object left in the state. bool overflow = true; for (size_t i = 0; i < n_state - n_pos_args - n_kwonly_args; ++i) { if (code_state->state[i] == MP_OBJ_NULL) { overflow = false; break; } } if (overflow) { mp_printf(MICROPY_DEBUG_PRINTER, "VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state); assert(0); } } #endif mp_obj_t result; if (vm_return_kind == MP_VM_RETURN_NORMAL) { // return value is in *sp result = *code_state->sp; } else { // must be an exception because normal functions can't yield assert(vm_return_kind == MP_VM_RETURN_EXCEPTION); // returned exception is in state[0] result = code_state->state[0]; } #if MICROPY_ENABLE_PYSTACK mp_pystack_free(code_state); #else // free the state if it was allocated on the heap if (state_size != 0) { m_del_var(mp_code_state_t, byte, state_size, code_state); } #endif if (vm_return_kind == MP_VM_RETURN_NORMAL) { return result; } else { // MP_VM_RETURN_EXCEPTION nlr_raise(result); } } #if MICROPY_PY_FUNCTION_ATTRS void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } if (attr == MP_QSTR___name__) { dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(self_in)); } if (attr == MP_QSTR___globals__) { mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals); } } #endif const mp_obj_type_t mp_type_fun_bc = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, #if MICROPY_CPYTHON_COMPAT .print = fun_bc_print, #endif .call = fun_bc_call, .unary_op = mp_generic_unary_op, #if MICROPY_PY_FUNCTION_ATTRS .attr = mp_obj_fun_bc_attr, #endif }; mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_module_context_t *context, struct _mp_raw_code_t *const *child_table) { size_t n_def_args = 0; size_t n_extra_args = 0; mp_obj_tuple_t *def_pos_args = NULL; mp_obj_t def_kw_args = MP_OBJ_NULL; if (def_args != NULL && def_args[0] != MP_OBJ_NULL) { assert(mp_obj_is_type(def_args[0], &mp_type_tuple)); def_pos_args = MP_OBJ_TO_PTR(def_args[0]); n_def_args = def_pos_args->len; n_extra_args = def_pos_args->len; } if (def_args != NULL && def_args[1] != MP_OBJ_NULL) { assert(mp_obj_is_type(def_args[1], &mp_type_dict)); def_kw_args = def_args[1]; n_extra_args += 1; } mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args); o->base.type = &mp_type_fun_bc; o->bytecode = code; o->context = context; o->child_table = child_table; if (def_pos_args != NULL) { memcpy(o->extra_args, def_pos_args->items, n_def_args * sizeof(mp_obj_t)); } if (def_kw_args != MP_OBJ_NULL) { o->extra_args[n_def_args] = def_kw_args; } return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ /* native functions */ #if MICROPY_EMIT_NATIVE STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { MP_STACK_CHECK(); mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); mp_call_fun_t fun = MICROPY_MAKE_POINTER_CALLABLE((void *)self->bytecode); return fun(self_in, n_args, n_kw, args); } STATIC const mp_obj_type_t mp_type_fun_native = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = fun_native_call, .unary_op = mp_generic_unary_op, }; mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) { mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(mp_obj_new_fun_bc(def_args, (const byte *)fun_data, mc, child_table)); o->base.type = &mp_type_fun_native; return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_EMIT_NATIVE /******************************************************************************/ /* inline assembler functions */ #if MICROPY_EMIT_INLINE_ASM typedef struct _mp_obj_fun_asm_t { mp_obj_base_t base; size_t n_args; const void *fun_data; // GC must be able to trace this pointer mp_uint_t type_sig; } mp_obj_fun_asm_t; typedef mp_uint_t (*inline_asm_fun_0_t)(void); typedef mp_uint_t (*inline_asm_fun_1_t)(mp_uint_t); typedef mp_uint_t (*inline_asm_fun_2_t)(mp_uint_t, mp_uint_t); typedef mp_uint_t (*inline_asm_fun_3_t)(mp_uint_t, mp_uint_t, mp_uint_t); typedef mp_uint_t (*inline_asm_fun_4_t)(mp_uint_t, mp_uint_t, mp_uint_t, mp_uint_t); // convert a MicroPython object to a sensible value for inline asm STATIC mp_uint_t convert_obj_for_inline_asm(mp_obj_t obj) { // TODO for byte_array, pass pointer to the array if (mp_obj_is_small_int(obj)) { return MP_OBJ_SMALL_INT_VALUE(obj); } else if (obj == mp_const_none) { return 0; } else if (obj == mp_const_false) { return 0; } else if (obj == mp_const_true) { return 1; } else if (mp_obj_is_type(obj, &mp_type_int)) { return mp_obj_int_get_truncated(obj); } else if (mp_obj_is_str(obj)) { // pointer to the string (it's probably constant though!) size_t l; return (mp_uint_t)mp_obj_str_get_data(obj, &l); } else { const mp_obj_type_t *type = mp_obj_get_type(obj); #if MICROPY_PY_BUILTINS_FLOAT if (type == &mp_type_float) { // convert float to int (could also pass in float registers) return (mp_int_t)mp_obj_float_get(obj); } #endif if (type == &mp_type_tuple || type == &mp_type_list) { // pointer to start of tuple (could pass length, but then could use len(x) for that) size_t len; mp_obj_t *items; mp_obj_get_array(obj, &len, &items); return (mp_uint_t)items; } else { mp_buffer_info_t bufinfo; if (mp_get_buffer(obj, &bufinfo, MP_BUFFER_READ)) { // supports the buffer protocol, return a pointer to the data return (mp_uint_t)bufinfo.buf; } else { // just pass along a pointer to the object return (mp_uint_t)obj; } } } } STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_fun_asm_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, self->n_args, self->n_args, false); const void *fun = MICROPY_MAKE_POINTER_CALLABLE(self->fun_data); mp_uint_t ret; if (n_args == 0) { ret = ((inline_asm_fun_0_t)fun)(); } else if (n_args == 1) { ret = ((inline_asm_fun_1_t)fun)(convert_obj_for_inline_asm(args[0])); } else if (n_args == 2) { ret = ((inline_asm_fun_2_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1])); } else if (n_args == 3) { ret = ((inline_asm_fun_3_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1]), convert_obj_for_inline_asm(args[2])); } else { // compiler allows at most 4 arguments assert(n_args == 4); ret = ((inline_asm_fun_4_t)fun)( convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1]), convert_obj_for_inline_asm(args[2]), convert_obj_for_inline_asm(args[3]) ); } return mp_native_to_obj(ret, self->type_sig); } STATIC const mp_obj_type_t mp_type_fun_asm = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = fun_asm_call, .unary_op = mp_generic_unary_op, }; mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) { mp_obj_fun_asm_t *o = m_new_obj(mp_obj_fun_asm_t); o->base.type = &mp_type_fun_asm; o->n_args = n_args; o->fun_data = fun_data; o->type_sig = type_sig; return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_EMIT_INLINE_ASM #include #include #include "py/runtime.h" #include "py/bc.h" #include "py/objstr.h" #include "py/objgenerator.h" #include "py/objfun.h" #include "py/stackctrl.h" // Instance of GeneratorExit exception - needed by generator.close() const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, 0, 0, NULL, (mp_obj_tuple_t *)&mp_const_empty_tuple_obj}; /******************************************************************************/ /* generator wrapper */ typedef struct _mp_obj_gen_instance_t { mp_obj_base_t base; // mp_const_none: Not-running, no exception. // MP_OBJ_NULL: Running, no exception. // other: Not running, pending exception. mp_obj_t pend_exc; mp_code_state_t code_state; } mp_obj_gen_instance_t; STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // A generating function is just a bytecode function with type mp_type_gen_wrap mp_obj_fun_bc_t *self_fun = MP_OBJ_TO_PTR(self_in); // bytecode prelude: get state size and exception stack size const uint8_t *ip = self_fun->bytecode; MP_BC_PRELUDE_SIG_DECODE(ip); // allocate the generator object, with room for local stack and exception stack mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); o->base.type = &mp_type_gen_instance; o->pend_exc = mp_const_none; o->code_state.fun_bc = self_fun; o->code_state.ip = self_fun->bytecode; o->code_state.n_state = n_state; mp_setup_code_state(&o->code_state, n_args, n_kw, args); return MP_OBJ_FROM_PTR(o); } const mp_obj_type_t mp_type_gen_wrap = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_generator, .call = gen_wrap_call, .unary_op = mp_generic_unary_op, #if MICROPY_PY_FUNCTION_ATTRS .attr = mp_obj_fun_bc_attr, #endif }; /******************************************************************************/ // native generator wrapper #if MICROPY_EMIT_NATIVE STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // The state for a native generating function is held in the same struct as a bytecode function mp_obj_fun_bc_t *self_fun = MP_OBJ_TO_PTR(self_in); // Determine start of prelude. uintptr_t prelude_offset = ((uintptr_t *)self_fun->bytecode)[0]; #if MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ // Prelude is in bytes object in const_table, at index prelude_offset mp_obj_str_t *prelude_bytes = MP_OBJ_TO_PTR(self_fun->context->constants.obj_table[prelude_offset]); const uint8_t *prelude_ptr = prelude_bytes->data; #else const uint8_t *prelude_ptr = self_fun->bytecode + prelude_offset; #endif // Extract n_state from the prelude. const uint8_t *ip = prelude_ptr; size_t n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args; MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args); size_t n_exc_stack = 0; // Allocate the generator object, with room for local stack and exception stack mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); o->base.type = &mp_type_gen_instance; // Parse the input arguments and set up the code state o->pend_exc = mp_const_none; o->code_state.fun_bc = self_fun; o->code_state.ip = prelude_ptr; o->code_state.n_state = n_state; mp_setup_code_state(&o->code_state, n_args, n_kw, args); // Indicate we are a native function, which doesn't use this variable o->code_state.exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_SENTINEL; // Prepare the generator instance for execution uintptr_t start_offset = ((uintptr_t *)self_fun->bytecode)[1]; o->code_state.ip = MICROPY_MAKE_POINTER_CALLABLE((void *)(self_fun->bytecode + start_offset)); return MP_OBJ_FROM_PTR(o); } const mp_obj_type_t mp_type_native_gen_wrap = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_generator, .call = native_gen_wrap_call, .unary_op = mp_generic_unary_op, #if MICROPY_PY_FUNCTION_ATTRS .attr = mp_obj_fun_bc_attr, #endif }; #endif // MICROPY_EMIT_NATIVE /******************************************************************************/ /* generator instance */ STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "", mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); } mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { MP_STACK_CHECK(); mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance)); mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->code_state.ip == 0) { // Trying to resume an already stopped generator. // This is an optimised "raise StopIteration(None)". *ret_val = mp_const_none; return MP_VM_RETURN_NORMAL; } // Ensure the generator cannot be reentered during execution if (self->pend_exc == MP_OBJ_NULL) { mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); } #if MICROPY_PY_GENERATOR_PEND_THROW // If exception is pending (set using .pend_throw()), process it now. if (self->pend_exc != mp_const_none) { throw_value = self->pend_exc; } #endif // If the generator is started, allow sending a value. if (self->code_state.sp == self->code_state.state - 1) { if (send_value != mp_const_none) { mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); } } else { *self->code_state.sp = send_value; } // Mark as running self->pend_exc = MP_OBJ_NULL; // Set up the correct globals context for the generator and execute it self->code_state.old_globals = mp_globals_get(); mp_globals_set(self->code_state.fun_bc->context->module.globals); mp_vm_return_kind_t ret_kind; #if MICROPY_EMIT_NATIVE if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { // A native generator, with entry point 2 words into the "bytecode" pointer typedef uintptr_t (*mp_fun_native_gen_t)(void *, mp_obj_t); mp_fun_native_gen_t fun = MICROPY_MAKE_POINTER_CALLABLE((const void *)(self->code_state.fun_bc->bytecode + 2 * sizeof(uintptr_t))); ret_kind = fun((void *)&self->code_state, throw_value); } else #endif { // A bytecode generator ret_kind = mp_execute_bytecode(&self->code_state, throw_value); } mp_globals_set(self->code_state.old_globals); // Mark as not running self->pend_exc = mp_const_none; switch (ret_kind) { case MP_VM_RETURN_NORMAL: default: // Explicitly mark generator as completed. If we don't do this, // subsequent next() may re-execute statements after last yield // again and again, leading to side effects. self->code_state.ip = 0; // This is an optimised "raise StopIteration(*ret_val)". *ret_val = *self->code_state.sp; break; case MP_VM_RETURN_YIELD: *ret_val = *self->code_state.sp; #if MICROPY_PY_GENERATOR_PEND_THROW *self->code_state.sp = mp_const_none; #endif break; case MP_VM_RETURN_EXCEPTION: { self->code_state.ip = 0; *ret_val = self->code_state.state[0]; // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); } break; } } return ret_kind; } STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, bool raise_stop_iteration) { mp_obj_t ret; switch (mp_obj_gen_resume(self_in, send_value, throw_value, &ret)) { case MP_VM_RETURN_NORMAL: default: // A normal return is a StopIteration, either raise it or return // MP_OBJ_STOP_ITERATION as an optimisation. if (ret == mp_const_none) { ret = MP_OBJ_NULL; } if (raise_stop_iteration) { mp_raise_StopIteration(ret); } else { return mp_make_stop_iteration(ret); } case MP_VM_RETURN_YIELD: return ret; case MP_VM_RETURN_EXCEPTION: nlr_raise(ret); } } STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL, false); } STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { return gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL, true); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send); STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) { // The signature of this function is: throw(type[, value[, traceback]]) // CPython will pass all given arguments through the call chain and process them // at the point they are used (native generators will handle them differently to // user-defined generators with a throw() method). To save passing multiple // values, MicroPython instead does partial processing here to reduce it down to // one argument and passes that through: // - if only args[1] is given, or args[2] is given but is None, args[1] is // passed through (in the standard case it is an exception class or instance) // - if args[2] is given and not None it is passed through (in the standard // case it would be an exception instance and args[1] its corresponding class) // - args[3] is always ignored mp_obj_t exc = args[1]; if (n_args > 2 && args[2] != mp_const_none) { exc = args[2]; } return gen_resume_and_raise(args[0], mp_const_none, exc, true); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gen_instance_throw_obj, 2, 4, gen_instance_throw); STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) { mp_obj_t ret; switch (mp_obj_gen_resume(self_in, mp_const_none, MP_OBJ_FROM_PTR(&mp_const_GeneratorExit_obj), &ret)) { case MP_VM_RETURN_YIELD: mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); // Swallow GeneratorExit (== successful close), and re-raise any other case MP_VM_RETURN_EXCEPTION: // ret should always be an instance of an exception class if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { return mp_const_none; } nlr_raise(ret); default: // The only choice left is MP_VM_RETURN_NORMAL which is successful close return mp_const_none; } } STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close); #if MICROPY_PY_GENERATOR_PEND_THROW STATIC mp_obj_t gen_instance_pend_throw(mp_obj_t self_in, mp_obj_t exc_in) { mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->pend_exc == MP_OBJ_NULL) { mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); } mp_obj_t prev = self->pend_exc; self->pend_exc = exc_in; return prev; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_pend_throw_obj, gen_instance_pend_throw); #endif STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&gen_instance_close_obj) }, { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&gen_instance_send_obj) }, { MP_ROM_QSTR(MP_QSTR_throw), MP_ROM_PTR(&gen_instance_throw_obj) }, #if MICROPY_PY_GENERATOR_PEND_THROW { MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table); const mp_obj_type_t mp_type_gen_instance = { { &mp_type_type }, .name = MP_QSTR_generator, .print = gen_instance_print, .unary_op = mp_generic_unary_op, .getiter = mp_identity_getiter, .iternext = gen_instance_iternext, .locals_dict = (mp_obj_dict_t *)&gen_instance_locals_dict, }; #include #include "py/runtime.h" // this is a wrapper object that turns something that has a __getitem__ method into an iterator typedef struct _mp_obj_getitem_iter_t { mp_obj_base_t base; mp_obj_t args[3]; } mp_obj_getitem_iter_t; STATIC mp_obj_t it_iternext(mp_obj_t self_in) { mp_obj_getitem_iter_t *self = MP_OBJ_TO_PTR(self_in); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { // try to get next item mp_obj_t value = mp_call_method_n_kw(1, 0, self->args); self->args[2] = MP_OBJ_NEW_SMALL_INT(MP_OBJ_SMALL_INT_VALUE(self->args[2]) + 1); nlr_pop(); return value; } else { // an exception was raised mp_obj_type_t *t = (mp_obj_type_t *)((mp_obj_base_t *)nlr.ret_val)->type; if (t == &mp_type_StopIteration || t == &mp_type_IndexError) { return MP_OBJ_STOP_ITERATION; } else { // re-raise exception nlr_jump(nlr.ret_val); } } } STATIC const mp_obj_type_t mp_type_it = { { &mp_type_type }, .name = MP_QSTR_iterator, .getiter = mp_identity_getiter, .iternext = it_iternext, }; // args are those returned from mp_load_method_maybe (ie either an attribute or a method) mp_obj_t mp_obj_new_getitem_iter(mp_obj_t *args, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_getitem_iter_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_getitem_iter_t *o = (mp_obj_getitem_iter_t *)iter_buf; o->base.type = &mp_type_it; o->args[0] = args[0]; o->args[1] = args[1]; o->args[2] = MP_OBJ_NEW_SMALL_INT(0); return MP_OBJ_FROM_PTR(o); } #include #include #include #include "py/parsenum.h" #include "py/smallint.h" #include "py/objint.h" #include "py/objstr.h" #include "py/runtime.h" #include "py/binary.h" #if MICROPY_PY_BUILTINS_FLOAT #include #endif // This dispatcher function is expected to be independent of the implementation of long int STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 2, false); switch (n_args) { case 0: return MP_OBJ_NEW_SMALL_INT(0); case 1: if (mp_obj_is_int(args[0])) { // already an int (small or long), just return it return args[0]; } else if (mp_obj_is_str_or_bytes(args[0])) { // a string, parse it size_t l; const char *s = mp_obj_str_get_data(args[0], &l); return mp_parse_num_integer(s, l, 0, NULL); #if MICROPY_PY_BUILTINS_FLOAT } else if (mp_obj_is_float(args[0])) { return mp_obj_new_int_from_float(mp_obj_float_get(args[0])); #endif } else { return mp_unary_op(MP_UNARY_OP_INT, args[0]); } case 2: default: { // should be a string, parse it size_t l; const char *s = mp_obj_str_get_data(args[0], &l); return mp_parse_num_integer(s, l, mp_obj_get_int(args[1]), NULL); } } } #if MICROPY_PY_BUILTINS_FLOAT typedef enum { MP_FP_CLASS_FIT_SMALLINT, MP_FP_CLASS_FIT_LONGINT, MP_FP_CLASS_OVERFLOW } mp_fp_as_int_class_t; STATIC mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { union { mp_float_t f; #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT uint32_t i; #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE uint32_t i[2]; #endif } u = {val}; uint32_t e; #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT e = u.i; #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE e = u.i[MP_ENDIANNESS_LITTLE]; #endif #define MP_FLOAT_SIGN_SHIFT_I32 ((MP_FLOAT_FRAC_BITS + MP_FLOAT_EXP_BITS) % 32) #define MP_FLOAT_EXP_SHIFT_I32 (MP_FLOAT_FRAC_BITS % 32) if (e & (1U << MP_FLOAT_SIGN_SHIFT_I32)) { #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE e |= u.i[MP_ENDIANNESS_BIG] != 0; #endif if ((e & ~(1U << MP_FLOAT_SIGN_SHIFT_I32)) == 0) { // handle case of -0 (when sign is set but rest of bits are zero) e = 0; } else { e += ((1U << MP_FLOAT_EXP_BITS) - 1) << MP_FLOAT_EXP_SHIFT_I32; } } else { e &= ~((1U << MP_FLOAT_EXP_SHIFT_I32) - 1); } // 8 * sizeof(uintptr_t) counts the number of bits for a small int // TODO provide a way to configure this properly if (e <= ((8 * sizeof(uintptr_t) + MP_FLOAT_EXP_BIAS - 3) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_SMALLINT; } #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG if (e <= (((sizeof(long long) * MP_BITS_PER_BYTE) + MP_FLOAT_EXP_BIAS - 2) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_LONGINT; } #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ return MP_FP_CLASS_FIT_LONGINT; #else return MP_FP_CLASS_OVERFLOW; #endif } #undef MP_FLOAT_SIGN_SHIFT_I32 #undef MP_FLOAT_EXP_SHIFT_I32 mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { mp_float_union_t u = {val}; // IEEE-754: if biased exponent is all 1 bits... if (u.p.exp == ((1 << MP_FLOAT_EXP_BITS) - 1)) { // ...then number is Inf (positive or negative) if fraction is 0, else NaN. if (u.p.frc == 0) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("can't convert inf to int")); } else { mp_raise_ValueError(MP_ERROR_TEXT("can't convert NaN to int")); } } else { mp_fp_as_int_class_t icl = mp_classify_fp_as_int(val); if (icl == MP_FP_CLASS_FIT_SMALLINT) { return MP_OBJ_NEW_SMALL_INT((mp_int_t)val); #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ } else { mp_obj_int_t *o = mp_obj_int_new_mpz(); mpz_set_from_float(&o->mpz, val); return MP_OBJ_FROM_PTR(o); } #else #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG } else if (icl == MP_FP_CLASS_FIT_LONGINT) { return mp_obj_new_int_from_ll((long long)val); #endif } else { mp_raise_ValueError(MP_ERROR_TEXT("float too big")); } #endif } } #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG typedef mp_longint_impl_t fmt_int_t; typedef unsigned long long fmt_uint_t; #else typedef mp_int_t fmt_int_t; typedef mp_uint_t fmt_uint_t; #endif void mp_obj_int_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; // The size of this buffer is rather arbitrary. If it's not large // enough, a dynamic one will be allocated. char stack_buf[sizeof(fmt_int_t) * 4]; char *buf = stack_buf; size_t buf_size = sizeof(stack_buf); size_t fmt_size; char *str = mp_obj_int_formatted(&buf, &buf_size, &fmt_size, self_in, 10, NULL, '\0', '\0'); mp_print_str(print, str); if (buf != stack_buf) { m_del(char, buf, buf_size); } } STATIC const uint8_t log_base2_floor[] = { 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, /* if needed, these are the values for higher bases 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5 */ }; size_t mp_int_format_size(size_t num_bits, int base, const char *prefix, char comma) { assert(2 <= base && base <= 16); size_t num_digits = num_bits / log_base2_floor[base - 1] + 1; size_t num_commas = comma ? num_digits / 3 : 0; size_t prefix_len = prefix ? strlen(prefix) : 0; return num_digits + num_commas + prefix_len + 2; // +1 for sign, +1 for null byte } // This routine expects you to pass in a buffer and size (in *buf and *buf_size). // If, for some reason, this buffer is too small, then it will allocate a // buffer and return the allocated buffer and size in *buf and *buf_size. It // is the callers responsibility to free this allocated buffer. // // The resulting formatted string will be returned from this function and the // formatted size will be in *fmt_size. char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_const_obj_t self_in, int base, const char *prefix, char base_char, char comma) { fmt_int_t num; #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE // Only have small ints; get the integer value to format. num = MP_OBJ_SMALL_INT_VALUE(self_in); #else if (mp_obj_is_small_int(self_in)) { // A small int; get the integer value to format. num = MP_OBJ_SMALL_INT_VALUE(self_in); } else { assert(mp_obj_is_type(self_in, &mp_type_int)); // Not a small int. #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG const mp_obj_int_t *self = self_in; // Get the value to format; mp_obj_get_int truncates to mp_int_t. num = self->val; #else // Delegate to the implementation for the long int. return mp_obj_int_formatted_impl(buf, buf_size, fmt_size, self_in, base, prefix, base_char, comma); #endif } #endif char sign = '\0'; if (num < 0) { num = -num; sign = '-'; } size_t needed_size = mp_int_format_size(sizeof(fmt_int_t) * 8, base, prefix, comma); if (needed_size > *buf_size) { *buf = m_new(char, needed_size); *buf_size = needed_size; } char *str = *buf; char *b = str + needed_size; *(--b) = '\0'; char *last_comma = b; if (num == 0) { *(--b) = '0'; } else { do { // The cast to fmt_uint_t is because num is positive and we want unsigned arithmetic int c = (fmt_uint_t)num % base; num = (fmt_uint_t)num / base; if (c >= 10) { c += base_char - 10; } else { c += '0'; } *(--b) = c; if (comma && num != 0 && b > str && (last_comma - b) == 3) { *(--b) = comma; last_comma = b; } } while (b > str && num != 0); } if (prefix) { size_t prefix_len = strlen(prefix); char *p = b - prefix_len; if (p > str) { b = p; while (*prefix) { *p++ = *prefix++; } } } if (sign && b > str) { *(--b) = sign; } *fmt_size = *buf + needed_size - b - 1; return b; } #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE int mp_obj_int_sign(mp_obj_t self_in) { mp_int_t val = mp_obj_get_int(self_in); if (val < 0) { return -1; } else if (val > 0) { return 1; } else { return 0; } } // This is called for operations on SMALL_INT that are not handled by mp_unary_op mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) { return MP_OBJ_NULL; // op not supported } // This is called for operations on SMALL_INT that are not handled by mp_binary_op mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { return mp_obj_int_binary_op_extra_cases(op, lhs_in, rhs_in); } // This is called only with strings whose value doesn't fit in SMALL_INT mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("long int not supported in this build")); return mp_const_none; } // This is called when an integer larger than a SMALL_INT is needed (although val might still fit in a SMALL_INT) mp_obj_t mp_obj_new_int_from_ll(long long val) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } // This is called when an integer larger than a SMALL_INT is needed (although val might still fit in a SMALL_INT) mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { // SMALL_INT accepts only signed numbers, so make sure the input // value fits completely in the small-int positive range. if ((value & ~MP_SMALL_INT_POSITIVE_MASK) == 0) { return MP_OBJ_NEW_SMALL_INT(value); } mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } mp_obj_t mp_obj_new_int(mp_int_t value) { if (MP_SMALL_INT_FITS(value)) { return MP_OBJ_NEW_SMALL_INT(value); } mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { return MP_OBJ_SMALL_INT_VALUE(self_in); } mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return MP_OBJ_SMALL_INT_VALUE(self_in); } #endif // MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE // This dispatcher function is expected to be independent of the implementation of long int // It handles the extra cases for integer-like arithmetic mp_obj_t mp_obj_int_binary_op_extra_cases(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { if (rhs_in == mp_const_false) { // false acts as 0 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(0)); } else if (rhs_in == mp_const_true) { // true acts as 0 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(1)); } else if (op == MP_BINARY_OP_MULTIPLY) { if (mp_obj_is_str_or_bytes(rhs_in) || mp_obj_is_type(rhs_in, &mp_type_tuple) || mp_obj_is_type(rhs_in, &mp_type_list)) { // multiply is commutative for these types, so delegate to them return mp_binary_op(op, rhs_in, lhs_in); } } return MP_OBJ_NULL; // op not supported } // this is a classmethod STATIC mp_obj_t int_from_bytes(size_t n_args, const mp_obj_t *args) { // TODO: Support signed param (assumes signed=False at the moment) (void)n_args; // get the buffer info mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); const byte *buf = (const byte *)bufinfo.buf; int delta = 1; if (args[2] == MP_OBJ_NEW_QSTR(MP_QSTR_little)) { buf += bufinfo.len - 1; delta = -1; } mp_uint_t value = 0; size_t len = bufinfo.len; for (; len--; buf += delta) { #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (value > (MP_SMALL_INT_MAX >> 8)) { // Result will overflow a small-int so construct a big-int return mp_obj_int_from_bytes_impl(args[2] != MP_OBJ_NEW_QSTR(MP_QSTR_little), bufinfo.len, bufinfo.buf); } #endif value = (value << 8) | *buf; } return mp_obj_new_int_from_uint(value); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(int_from_bytes_fun_obj, 3, 4, int_from_bytes); STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(int_from_bytes_obj, MP_ROM_PTR(&int_from_bytes_fun_obj)); STATIC mp_obj_t int_to_bytes(size_t n_args, const mp_obj_t *args) { // TODO: Support signed param (assumes signed=False) (void)n_args; mp_int_t len = mp_obj_get_int(args[1]); if (len < 0) { mp_raise_ValueError(NULL); } bool big_endian = args[2] != MP_OBJ_NEW_QSTR(MP_QSTR_little); vstr_t vstr; vstr_init_len(&vstr, len); byte *data = (byte *)vstr.buf; memset(data, 0, len); #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (!mp_obj_is_small_int(args[0])) { mp_obj_int_to_bytes_impl(args[0], big_endian, len, data); } else #endif { mp_int_t val = MP_OBJ_SMALL_INT_VALUE(args[0]); size_t l = MIN((size_t)len, sizeof(val)); mp_binary_set_int(l, big_endian, data + (big_endian ? (len - l) : 0), val); } return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(int_to_bytes_obj, 3, 4, int_to_bytes); STATIC const mp_rom_map_elem_t int_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_from_bytes), MP_ROM_PTR(&int_from_bytes_obj) }, { MP_ROM_QSTR(MP_QSTR_to_bytes), MP_ROM_PTR(&int_to_bytes_obj) }, }; STATIC MP_DEFINE_CONST_DICT(int_locals_dict, int_locals_dict_table); const mp_obj_type_t mp_type_int = { { &mp_type_type }, .name = MP_QSTR_int, .print = mp_obj_int_print, .make_new = mp_obj_int_make_new, .unary_op = mp_obj_int_unary_op, .binary_op = mp_obj_int_binary_op, .locals_dict = (mp_obj_dict_t *)&int_locals_dict, }; #include #include #include "py/smallint.h" #include "py/objint.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT #include #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG #if MICROPY_PY_SYS_MAXSIZE // Export value for sys.maxsize const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; #endif mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { int delta = 1; if (!big_endian) { buf += len - 1; delta = -1; } mp_longint_impl_t value = 0; for (; len--; buf += delta) { value = (value << 8) | *buf; } return mp_obj_new_int_from_ll(value); } void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { assert(mp_obj_is_type(self_in, &mp_type_int)); mp_obj_int_t *self = self_in; long long val = self->val; if (big_endian) { byte *b = buf + len; while (b > buf) { *--b = val; val >>= 8; } } else { for (; len > 0; --len) { *buf++ = val; val >>= 8; } } } int mp_obj_int_sign(mp_obj_t self_in) { mp_longint_impl_t val; if (mp_obj_is_small_int(self_in)) { val = MP_OBJ_SMALL_INT_VALUE(self_in); } else { mp_obj_int_t *self = self_in; val = self->val; } if (val < 0) { return -1; } else if (val > 0) { return 1; } else { return 0; } } mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_obj_int_t *o = o_in; switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(o->val != 0); // truncate value to fit in mp_int_t, which gives the same hash as // small int if the value fits without truncation case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT((mp_int_t)o->val); case MP_UNARY_OP_POSITIVE: return o_in; case MP_UNARY_OP_NEGATIVE: return mp_obj_new_int_from_ll(-o->val); case MP_UNARY_OP_INVERT: return mp_obj_new_int_from_ll(~o->val); case MP_UNARY_OP_ABS: { mp_obj_int_t *self = MP_OBJ_TO_PTR(o_in); if (self->val >= 0) { return o_in; } self = mp_obj_new_int_from_ll(self->val); // TODO could overflow long long self->val = -self->val; return MP_OBJ_FROM_PTR(self); } default: return MP_OBJ_NULL; // op not supported } } mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { long long lhs_val; long long rhs_val; if (mp_obj_is_small_int(lhs_in)) { lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs_in); } else { assert(mp_obj_is_type(lhs_in, &mp_type_int)); lhs_val = ((mp_obj_int_t *)lhs_in)->val; } if (mp_obj_is_small_int(rhs_in)) { rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs_in); } else if (mp_obj_is_type(rhs_in, &mp_type_int)) { rhs_val = ((mp_obj_int_t *)rhs_in)->val; } else { // delegate to generic function to check for extra cases return mp_obj_int_binary_op_extra_cases(op, lhs_in, rhs_in); } switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: return mp_obj_new_int_from_ll(lhs_val + rhs_val); case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: return mp_obj_new_int_from_ll(lhs_val - rhs_val); case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: return mp_obj_new_int_from_ll(lhs_val * rhs_val); case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { goto zero_division; } return mp_obj_new_int_from_ll(lhs_val / rhs_val); case MP_BINARY_OP_MODULO: case MP_BINARY_OP_INPLACE_MODULO: if (rhs_val == 0) { goto zero_division; } return mp_obj_new_int_from_ll(lhs_val % rhs_val); case MP_BINARY_OP_AND: case MP_BINARY_OP_INPLACE_AND: return mp_obj_new_int_from_ll(lhs_val & rhs_val); case MP_BINARY_OP_OR: case MP_BINARY_OP_INPLACE_OR: return mp_obj_new_int_from_ll(lhs_val | rhs_val); case MP_BINARY_OP_XOR: case MP_BINARY_OP_INPLACE_XOR: return mp_obj_new_int_from_ll(lhs_val ^ rhs_val); case MP_BINARY_OP_LSHIFT: case MP_BINARY_OP_INPLACE_LSHIFT: return mp_obj_new_int_from_ll(lhs_val << (int)rhs_val); case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: return mp_obj_new_int_from_ll(lhs_val >> (int)rhs_val); case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: { if (rhs_val < 0) { #if MICROPY_PY_BUILTINS_FLOAT return mp_obj_float_binary_op(op, lhs_val, rhs_in); #else mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } long long ans = 1; while (rhs_val > 0) { if (rhs_val & 1) { ans *= lhs_val; } if (rhs_val == 1) { break; } rhs_val /= 2; lhs_val *= lhs_val; } return mp_obj_new_int_from_ll(ans); } case MP_BINARY_OP_LESS: return mp_obj_new_bool(lhs_val < rhs_val); case MP_BINARY_OP_MORE: return mp_obj_new_bool(lhs_val > rhs_val); case MP_BINARY_OP_LESS_EQUAL: return mp_obj_new_bool(lhs_val <= rhs_val); case MP_BINARY_OP_MORE_EQUAL: return mp_obj_new_bool(lhs_val >= rhs_val); case MP_BINARY_OP_EQUAL: return mp_obj_new_bool(lhs_val == rhs_val); default: return MP_OBJ_NULL; // op not supported } zero_division: mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } mp_obj_t mp_obj_new_int(mp_int_t value) { if (MP_SMALL_INT_FITS(value)) { return MP_OBJ_NEW_SMALL_INT(value); } return mp_obj_new_int_from_ll(value); } mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { // SMALL_INT accepts only signed numbers, so make sure the input // value fits completely in the small-int positive range. if ((value & ~MP_SMALL_INT_POSITIVE_MASK) == 0) { return MP_OBJ_NEW_SMALL_INT(value); } return mp_obj_new_int_from_ll(value); } mp_obj_t mp_obj_new_int_from_ll(long long val) { mp_obj_int_t *o = m_new_obj(mp_obj_int_t); o->base.type = &mp_type_int; o->val = val; return o; } mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { // TODO raise an exception if the unsigned long long won't fit if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ulonglong too large")); } mp_obj_int_t *o = m_new_obj(mp_obj_int_t); o->base.type = &mp_type_int; o->val = val; return o; } mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) { // TODO this does not honor the given length of the string, but it all cases it should anyway be null terminated // TODO check overflow mp_obj_int_t *o = m_new_obj(mp_obj_int_t); o->base.type = &mp_type_int; char *endptr; o->val = strtoll(*str, &endptr, base); *str = endptr; return o; } mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); } else { const mp_obj_int_t *self = self_in; return self->val; } } mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { // TODO: Check overflow return mp_obj_int_get_truncated(self_in); } #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_int)); mp_obj_int_t *self = self_in; return self->val; } #endif #endif #include #ifndef __IPU__ #include #endif #include #include "py/parsenumbase.h" #include "py/smallint.h" #include "py/objint.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT #include #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ #if MICROPY_PY_SYS_MAXSIZE // Export value for sys.maxsize // *FORMAT-OFF* #define DIG_MASK ((MPZ_LONG_1 << MPZ_DIG_SIZE) - 1) STATIC const mpz_dig_t maxsize_dig[] = { #define NUM_DIG 1 (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 0) & DIG_MASK, #if (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 0) > DIG_MASK #undef NUM_DIG #define NUM_DIG 2 (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 1) & DIG_MASK, #if (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 1) > DIG_MASK #undef NUM_DIG #define NUM_DIG 3 (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 2) & DIG_MASK, #if (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 2) > DIG_MASK #undef NUM_DIG #define NUM_DIG 4 (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 3) & DIG_MASK, #if (MP_SSIZE_MAX >> MPZ_DIG_SIZE * 3) > DIG_MASK #error cannot encode MP_SSIZE_MAX as mpz #endif #endif #endif #endif }; // *FORMAT-ON* const mp_obj_int_t mp_sys_maxsize_obj = { {&mp_type_int}, {.fixed_dig = 1, .len = NUM_DIG, .alloc = NUM_DIG, .dig = (mpz_dig_t *)maxsize_dig} }; #undef DIG_MASK #undef NUM_DIG #endif mp_obj_int_t *mp_obj_int_new_mpz(void) { mp_obj_int_t *o = m_new_obj(mp_obj_int_t); o->base.type = &mp_type_int; mpz_init_zero(&o->mpz); return o; } // This routine expects you to pass in a buffer and size (in *buf and buf_size). // If, for some reason, this buffer is too small, then it will allocate a // buffer and return the allocated buffer and size in *buf and *buf_size. It // is the callers responsibility to free this allocated buffer. // // The resulting formatted string will be returned from this function and the // formatted size will be in *fmt_size. // // This particular routine should only be called for the mpz representation of the int. char *mp_obj_int_formatted_impl(char **buf, size_t *buf_size, size_t *fmt_size, mp_const_obj_t self_in, int base, const char *prefix, char base_char, char comma) { assert(mp_obj_is_type(self_in, &mp_type_int)); const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); size_t needed_size = mp_int_format_size(mpz_max_num_bits(&self->mpz), base, prefix, comma); if (needed_size > *buf_size) { *buf = m_new(char, needed_size); *buf_size = needed_size; } char *str = *buf; *fmt_size = mpz_as_str_inpl(&self->mpz, base, prefix, base_char, comma, str); return str; } mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { mp_obj_int_t *o = mp_obj_int_new_mpz(); mpz_set_from_bytes(&o->mpz, big_endian, len, buf); return MP_OBJ_FROM_PTR(o); } void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { assert(mp_obj_is_type(self_in, &mp_type_int)); mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); mpz_as_bytes(&self->mpz, big_endian, len, buf); } int mp_obj_int_sign(mp_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { mp_int_t val = MP_OBJ_SMALL_INT_VALUE(self_in); if (val < 0) { return -1; } else if (val > 0) { return 1; } else { return 0; } } mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); if (self->mpz.len == 0) { return 0; } else if (self->mpz.neg == 0) { return 1; } else { return -1; } } mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_obj_int_t *o = MP_OBJ_TO_PTR(o_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(!mpz_is_zero(&o->mpz)); case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT(mpz_hash(&o->mpz)); case MP_UNARY_OP_POSITIVE: return o_in; case MP_UNARY_OP_NEGATIVE: { mp_obj_int_t *o2 = mp_obj_int_new_mpz(); mpz_neg_inpl(&o2->mpz, &o->mpz); return MP_OBJ_FROM_PTR(o2); } case MP_UNARY_OP_INVERT: { mp_obj_int_t *o2 = mp_obj_int_new_mpz(); mpz_not_inpl(&o2->mpz, &o->mpz); return MP_OBJ_FROM_PTR(o2); } case MP_UNARY_OP_ABS: { mp_obj_int_t *self = MP_OBJ_TO_PTR(o_in); if (self->mpz.neg == 0) { return o_in; } mp_obj_int_t *self2 = mp_obj_int_new_mpz(); mpz_abs_inpl(&self2->mpz, &self->mpz); return MP_OBJ_FROM_PTR(self2); } default: return MP_OBJ_NULL; // op not supported } } mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { const mpz_t *zlhs; const mpz_t *zrhs; mpz_t z_int; mpz_dig_t z_int_dig[MPZ_NUM_DIG_FOR_INT]; // lhs could be a small int (eg small-int + mpz) if (mp_obj_is_small_int(lhs_in)) { mpz_init_fixed_from_int(&z_int, z_int_dig, MPZ_NUM_DIG_FOR_INT, MP_OBJ_SMALL_INT_VALUE(lhs_in)); zlhs = &z_int; } else { assert(mp_obj_is_type(lhs_in, &mp_type_int)); zlhs = &((mp_obj_int_t *)MP_OBJ_TO_PTR(lhs_in))->mpz; } // if rhs is small int, then lhs was not (otherwise mp_binary_op handles it) if (mp_obj_is_small_int(rhs_in)) { mpz_init_fixed_from_int(&z_int, z_int_dig, MPZ_NUM_DIG_FOR_INT, MP_OBJ_SMALL_INT_VALUE(rhs_in)); zrhs = &z_int; } else if (mp_obj_is_type(rhs_in, &mp_type_int)) { zrhs = &((mp_obj_int_t *)MP_OBJ_TO_PTR(rhs_in))->mpz; #if MICROPY_PY_BUILTINS_FLOAT } else if (mp_obj_is_float(rhs_in)) { return mp_obj_float_binary_op(op, mpz_as_float(zlhs), rhs_in); #endif #if MICROPY_PY_BUILTINS_COMPLEX } else if (mp_obj_is_type(rhs_in, &mp_type_complex)) { return mp_obj_complex_binary_op(op, mpz_as_float(zlhs), 0, rhs_in); #endif } else { // delegate to generic function to check for extra cases return mp_obj_int_binary_op_extra_cases(op, lhs_in, rhs_in); } #if MICROPY_PY_BUILTINS_FLOAT if (op == MP_BINARY_OP_TRUE_DIVIDE || op == MP_BINARY_OP_INPLACE_TRUE_DIVIDE) { if (mpz_is_zero(zrhs)) { goto zero_division_error; } mp_float_t flhs = mpz_as_float(zlhs); mp_float_t frhs = mpz_as_float(zrhs); return mp_obj_new_float(flhs / frhs); } #endif if (op >= MP_BINARY_OP_INPLACE_OR && op < MP_BINARY_OP_CONTAINS) { mp_obj_int_t *res = mp_obj_int_new_mpz(); switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: mpz_add_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: mpz_sub_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: mpz_mul_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: { if (mpz_is_zero(zrhs)) { zero_division_error: mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } mpz_t rem; mpz_init_zero(&rem); mpz_divmod_inpl(&res->mpz, &rem, zlhs, zrhs); mpz_deinit(&rem); break; } case MP_BINARY_OP_MODULO: case MP_BINARY_OP_INPLACE_MODULO: { if (mpz_is_zero(zrhs)) { goto zero_division_error; } mpz_t quo; mpz_init_zero(&quo); mpz_divmod_inpl(&quo, &res->mpz, zlhs, zrhs); mpz_deinit(&quo); break; } case MP_BINARY_OP_AND: case MP_BINARY_OP_INPLACE_AND: mpz_and_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_OR: case MP_BINARY_OP_INPLACE_OR: mpz_or_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_XOR: case MP_BINARY_OP_INPLACE_XOR: mpz_xor_inpl(&res->mpz, zlhs, zrhs); break; case MP_BINARY_OP_LSHIFT: case MP_BINARY_OP_INPLACE_LSHIFT: case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: { mp_int_t irhs = mp_obj_int_get_checked(rhs_in); if (irhs < 0) { mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } if (op == MP_BINARY_OP_LSHIFT || op == MP_BINARY_OP_INPLACE_LSHIFT) { mpz_shl_inpl(&res->mpz, zlhs, irhs); } else { mpz_shr_inpl(&res->mpz, zlhs, irhs); } break; } case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: if (mpz_is_neg(zrhs)) { #if MICROPY_PY_BUILTINS_FLOAT return mp_obj_float_binary_op(op, mpz_as_float(zlhs), rhs_in); #else mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } mpz_pow_inpl(&res->mpz, zlhs, zrhs); break; default: { assert(op == MP_BINARY_OP_DIVMOD); if (mpz_is_zero(zrhs)) { goto zero_division_error; } mp_obj_int_t *quo = mp_obj_int_new_mpz(); mpz_divmod_inpl(&quo->mpz, &res->mpz, zlhs, zrhs); mp_obj_t tuple[2] = {MP_OBJ_FROM_PTR(quo), MP_OBJ_FROM_PTR(res)}; return mp_obj_new_tuple(2, tuple); } } return MP_OBJ_FROM_PTR(res); } else { int cmp = mpz_cmp(zlhs, zrhs); switch (op) { case MP_BINARY_OP_LESS: return mp_obj_new_bool(cmp < 0); case MP_BINARY_OP_MORE: return mp_obj_new_bool(cmp > 0); case MP_BINARY_OP_LESS_EQUAL: return mp_obj_new_bool(cmp <= 0); case MP_BINARY_OP_MORE_EQUAL: return mp_obj_new_bool(cmp >= 0); case MP_BINARY_OP_EQUAL: return mp_obj_new_bool(cmp == 0); default: return MP_OBJ_NULL; // op not supported } } } #if MICROPY_PY_BUILTINS_POW3 STATIC mpz_t *mp_mpz_for_int(mp_obj_t arg, mpz_t *temp) { if (mp_obj_is_small_int(arg)) { mpz_init_from_int(temp, MP_OBJ_SMALL_INT_VALUE(arg)); return temp; } else { mp_obj_int_t *arp_p = MP_OBJ_TO_PTR(arg); return &(arp_p->mpz); } } mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { if (!mp_obj_is_int(base) || !mp_obj_is_int(exponent) || !mp_obj_is_int(modulus)) { mp_raise_TypeError(MP_ERROR_TEXT("pow() with 3 arguments requires integers")); } else { mp_obj_t result = mp_obj_new_int_from_ull(0); // Use the _from_ull version as this forces an mpz int mp_obj_int_t *res_p = (mp_obj_int_t *)MP_OBJ_TO_PTR(result); mpz_t l_temp, r_temp, m_temp; mpz_t *lhs = mp_mpz_for_int(base, &l_temp); mpz_t *rhs = mp_mpz_for_int(exponent, &r_temp); mpz_t *mod = mp_mpz_for_int(modulus, &m_temp); mpz_pow3_inpl(&(res_p->mpz), lhs, rhs, mod); if (lhs == &l_temp) { mpz_deinit(lhs); } if (rhs == &r_temp) { mpz_deinit(rhs); } if (mod == &m_temp) { mpz_deinit(mod); } return result; } } #endif mp_obj_t mp_obj_new_int(mp_int_t value) { if (MP_SMALL_INT_FITS(value)) { return MP_OBJ_NEW_SMALL_INT(value); } return mp_obj_new_int_from_ll(value); } mp_obj_t mp_obj_new_int_from_ll(long long val) { mp_obj_int_t *o = mp_obj_int_new_mpz(); mpz_set_from_ll(&o->mpz, val, true); return MP_OBJ_FROM_PTR(o); } mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { mp_obj_int_t *o = mp_obj_int_new_mpz(); mpz_set_from_ll(&o->mpz, val, false); return MP_OBJ_FROM_PTR(o); } mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { // SMALL_INT accepts only signed numbers, so make sure the input // value fits completely in the small-int positive range. if ((value & ~MP_SMALL_INT_POSITIVE_MASK) == 0) { return MP_OBJ_NEW_SMALL_INT(value); } return mp_obj_new_int_from_ull(value); } mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) { mp_obj_int_t *o = mp_obj_int_new_mpz(); size_t n = mpz_set_from_str(&o->mpz, *str, len, neg, base); *str += n; return MP_OBJ_FROM_PTR(o); } mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); } else { const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); // hash returns actual int value if it fits in mp_int_t return mpz_hash(&self->mpz); } } mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); } else { const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t value; if (mpz_as_int_checked(&self->mpz, &value)) { return value; } else { // overflow mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); } } } mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { if (MP_OBJ_SMALL_INT_VALUE(self_in) >= 0) { return MP_OBJ_SMALL_INT_VALUE(self_in); } } else { const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); mp_uint_t value; if (mpz_as_uint_checked(&self->mpz, &value)) { return value; } } mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); } #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_int)); mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); return mpz_as_float(&self->mpz); } #endif #endif #include #include #include "py/objlist.h" #include "py/runtime.h" #include "py/stackctrl.h" STATIC mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf); STATIC mp_obj_list_t *list_new(size_t n); STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in); STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); // TODO: Move to mpconfig.h #define LIST_MIN_ALLOC 4 /******************************************************************************/ /* list */ STATIC void list_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_list_t *o = MP_OBJ_TO_PTR(o_in); const char *item_separator = ", "; if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { kind = PRINT_REPR; } else { #if MICROPY_PY_UJSON_SEPARATORS item_separator = MP_PRINT_GET_EXT(print)->item_separator; #endif } mp_print_str(print, "["); for (size_t i = 0; i < o->len; i++) { if (i > 0) { mp_print_str(print, item_separator); } mp_obj_print_helper(print, o->items[i], kind); } mp_print_str(print, "]"); } STATIC mp_obj_t list_extend_from_iter(mp_obj_t list, mp_obj_t iterable) { mp_obj_t iter = mp_getiter(iterable, NULL); mp_obj_t item; while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_obj_list_append(list, item); } return list; } STATIC mp_obj_t list_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 1, false); switch (n_args) { case 0: // return a new, empty list return mp_obj_new_list(0, NULL); case 1: default: { // make list from iterable // TODO: optimize list/tuple mp_obj_t list = mp_obj_new_list(0, NULL); return list_extend_from_iter(list, args[0]); } } } STATIC mp_obj_t list_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->len != 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(self->len); #if MICROPY_PY_SYS_GETSIZEOF case MP_UNARY_OP_SIZEOF: { size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; return MP_OBJ_NEW_SMALL_INT(sz); } #endif default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t list_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { mp_obj_list_t *o = MP_OBJ_TO_PTR(lhs); switch (op) { case MP_BINARY_OP_ADD: { if (!mp_obj_is_type(rhs, &mp_type_list)) { return MP_OBJ_NULL; // op not supported } mp_obj_list_t *p = MP_OBJ_TO_PTR(rhs); mp_obj_list_t *s = list_new(o->len + p->len); mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); return MP_OBJ_FROM_PTR(s); } case MP_BINARY_OP_INPLACE_ADD: { list_extend(lhs, rhs); return lhs; } case MP_BINARY_OP_MULTIPLY: { mp_int_t n; if (!mp_obj_get_int_maybe(rhs, &n)) { return MP_OBJ_NULL; // op not supported } if (n < 0) { n = 0; } mp_obj_list_t *s = list_new(o->len * n); mp_seq_multiply(o->items, sizeof(*o->items), o->len, n, s->items); return MP_OBJ_FROM_PTR(s); } case MP_BINARY_OP_EQUAL: case MP_BINARY_OP_LESS: case MP_BINARY_OP_LESS_EQUAL: case MP_BINARY_OP_MORE: case MP_BINARY_OP_MORE_EQUAL: { if (!mp_obj_is_type(rhs, &mp_type_list)) { if (op == MP_BINARY_OP_EQUAL) { return mp_const_false; } return MP_OBJ_NULL; // op not supported } mp_obj_list_t *another = MP_OBJ_TO_PTR(rhs); bool res = mp_seq_cmp_objs(op, o->items, o->len, another->items, another->len); return mp_obj_new_bool(res); } default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (value == MP_OBJ_NULL) { // delete #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); mp_bound_slice_t slice; if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice)) { mp_raise_NotImplementedError(NULL); } mp_int_t len_adj = slice.start - slice.stop; assert(len_adj <= 0); mp_seq_replace_slice_no_grow(self->items, self->len, slice.start, slice.stop, self->items /*NULL*/, 0, sizeof(*self->items)); // Clear "freed" elements at the end of list mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); self->len += len_adj; return mp_const_none; } #endif mp_obj_t args[2] = {self_in, index}; list_pop(2, args); return mp_const_none; } else if (value == MP_OBJ_SENTINEL) { // load mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice)) { return mp_seq_extract_slice(self->len, self->items, &slice); } mp_obj_list_t *res = list_new(slice.stop - slice.start); mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); return MP_OBJ_FROM_PTR(res); } #endif size_t index_val = mp_get_index(self->base.type, self->len, index, false); return self->items[index_val]; } else { #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); size_t value_len; mp_obj_t *value_items; mp_obj_get_array(value, &value_len, &value_items); mp_bound_slice_t slice_out; if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice_out)) { mp_raise_NotImplementedError(NULL); } mp_int_t len_adj = value_len - (slice_out.stop - slice_out.start); if (len_adj > 0) { if (self->len + len_adj > self->alloc) { // TODO: Might optimize memory copies here by checking if block can // be grown inplace or not self->items = m_renew(mp_obj_t, self->items, self->alloc, self->len + len_adj); self->alloc = self->len + len_adj; } mp_seq_replace_slice_grow_inplace(self->items, self->len, slice_out.start, slice_out.stop, value_items, value_len, len_adj, sizeof(*self->items)); } else { mp_seq_replace_slice_no_grow(self->items, self->len, slice_out.start, slice_out.stop, value_items, value_len, sizeof(*self->items)); // Clear "freed" elements at the end of list mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); // TODO: apply allocation policy re: alloc_size } self->len += len_adj; return mp_const_none; } #endif mp_obj_list_store(self_in, index, value); return mp_const_none; } } STATIC mp_obj_t list_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { return mp_obj_new_list_iterator(o_in, 0, iter_buf); } mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); if (self->len >= self->alloc) { self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc * 2); self->alloc *= 2; mp_seq_clear(self->items, self->len + 1, self->alloc, sizeof(*self->items)); } self->items[self->len++] = arg; return mp_const_none; // return None, as per CPython } STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); if (mp_obj_is_type(arg_in, &mp_type_list)) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_list_t *arg = MP_OBJ_TO_PTR(arg_in); if (self->len + arg->len > self->alloc) { // TODO: use alloc policy for "4" self->items = m_renew(mp_obj_t, self->items, self->alloc, self->len + arg->len + 4); self->alloc = self->len + arg->len + 4; mp_seq_clear(self->items, self->len + arg->len, self->alloc, sizeof(*self->items)); } memcpy(self->items + self->len, arg->items, sizeof(mp_obj_t) * arg->len); self->len += arg->len; } else { list_extend_from_iter(self_in, arg_in); } return mp_const_none; // return None, as per CPython } STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args) { mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); if (self->len == 0) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("pop from empty list")); } size_t index = mp_get_index(self->base.type, self->len, n_args == 1 ? MP_OBJ_NEW_SMALL_INT(-1) : args[1], false); mp_obj_t ret = self->items[index]; self->len -= 1; memmove(self->items + index, self->items + index + 1, (self->len - index) * sizeof(mp_obj_t)); // Clear stale pointer from slot which just got freed to prevent GC issues self->items[self->len] = MP_OBJ_NULL; if (self->alloc > LIST_MIN_ALLOC && self->alloc > 2 * self->len) { self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc / 2); self->alloc /= 2; } return ret; } STATIC void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj_t binop_less_result) { MP_STACK_CHECK(); while (head < tail) { mp_obj_t *h = head - 1; mp_obj_t *t = tail; mp_obj_t v = key_fn == MP_OBJ_NULL ? tail[0] : mp_call_function_1(key_fn, tail[0]); // get pivot using key_fn for (;;) { do {++h; } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, key_fn == MP_OBJ_NULL ? h[0] : mp_call_function_1(key_fn, h[0]), v) == binop_less_result); do {--t; } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, v, key_fn == MP_OBJ_NULL ? t[0] : mp_call_function_1(key_fn, t[0])) == binop_less_result); if (h >= t) { break; } mp_obj_t x = h[0]; h[0] = t[0]; t[0] = x; } mp_obj_t x = h[0]; h[0] = tail[0]; tail[0] = x; // do the smaller recursive call first, to keep stack within O(log(N)) if (t - head < tail - h - 1) { mp_quicksort(head, t, key_fn, binop_less_result); head = h + 1; } else { mp_quicksort(h + 1, tail, key_fn, binop_less_result); tail = t; } } } // TODO Python defines sort to be stable but ours is not mp_obj_t mp_obj_list_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_reverse, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; // parse args struct { mp_arg_val_t key, reverse; } args; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); mp_check_self(mp_obj_is_type(pos_args[0], &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(pos_args[0]); if (self->len > 1) { mp_quicksort(self->items, self->items + self->len - 1, args.key.u_obj == mp_const_none ? MP_OBJ_NULL : args.key.u_obj, args.reverse.u_bool ? mp_const_false : mp_const_true); } return mp_const_none; } STATIC mp_obj_t list_clear(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); self->len = 0; self->items = m_renew(mp_obj_t, self->items, self->alloc, LIST_MIN_ALLOC); self->alloc = LIST_MIN_ALLOC; mp_seq_clear(self->items, 0, self->alloc, sizeof(*self->items)); return mp_const_none; } STATIC mp_obj_t list_copy(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); return mp_obj_new_list(self->len, self->items); } STATIC mp_obj_t list_count(mp_obj_t self_in, mp_obj_t value) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); return mp_seq_count_obj(self->items, self->len, value); } STATIC mp_obj_t list_index(size_t n_args, const mp_obj_t *args) { mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); return mp_seq_index_obj(self->items, self->len, n_args, args); } STATIC mp_obj_t list_insert(mp_obj_t self_in, mp_obj_t idx, mp_obj_t obj) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); // insert has its own strange index logic mp_int_t index = MP_OBJ_SMALL_INT_VALUE(idx); if (index < 0) { index += self->len; } if (index < 0) { index = 0; } if ((size_t)index > self->len) { index = self->len; } mp_obj_list_append(self_in, mp_const_none); for (mp_int_t i = self->len - 1; i > index; i--) { self->items[i] = self->items[i - 1]; } self->items[index] = obj; return mp_const_none; } mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_t args[] = {self_in, value}; args[1] = list_index(2, args); list_pop(2, args); return mp_const_none; } STATIC mp_obj_t list_reverse(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t len = self->len; for (mp_int_t i = 0; i < len / 2; i++) { mp_obj_t a = self->items[i]; self->items[i] = self->items[len - i - 1]; self->items[len - i - 1] = a; } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_append_obj, mp_obj_list_append); STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_extend_obj, list_extend); STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_clear_obj, list_clear); STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_copy_obj, list_copy); STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_count_obj, list_count); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_index_obj, 2, 4, list_index); STATIC MP_DEFINE_CONST_FUN_OBJ_3(list_insert_obj, list_insert); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_pop_obj, 1, 2, list_pop); STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_remove_obj, mp_obj_list_remove); STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_reverse_obj, list_reverse); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(list_sort_obj, 1, mp_obj_list_sort); STATIC const mp_rom_map_elem_t list_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&list_append_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&list_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&list_copy_obj) }, { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&list_count_obj) }, { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&list_extend_obj) }, { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&list_index_obj) }, { MP_ROM_QSTR(MP_QSTR_insert), MP_ROM_PTR(&list_insert_obj) }, { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&list_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&list_remove_obj) }, { MP_ROM_QSTR(MP_QSTR_reverse), MP_ROM_PTR(&list_reverse_obj) }, { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&list_sort_obj) }, }; STATIC MP_DEFINE_CONST_DICT(list_locals_dict, list_locals_dict_table); const mp_obj_type_t mp_type_list = { { &mp_type_type }, .name = MP_QSTR_list, .print = list_print, .make_new = list_make_new, .unary_op = list_unary_op, .binary_op = list_binary_op, .subscr = list_subscr, .getiter = list_getiter, .locals_dict = (mp_obj_dict_t *)&list_locals_dict, }; void mp_obj_list_init(mp_obj_list_t *o, size_t n) { o->base.type = &mp_type_list; o->alloc = n < LIST_MIN_ALLOC ? LIST_MIN_ALLOC : n; o->len = n; o->items = m_new(mp_obj_t, o->alloc); mp_seq_clear(o->items, n, o->alloc, sizeof(*o->items)); } STATIC mp_obj_list_t *list_new(size_t n) { mp_obj_list_t *o = m_new_obj(mp_obj_list_t); mp_obj_list_init(o, n); return o; } mp_obj_t mp_obj_new_list(size_t n, mp_obj_t *items) { mp_obj_list_t *o = list_new(n); if (items != NULL) { for (size_t i = 0; i < n; i++) { o->items[i] = items[i]; } } return MP_OBJ_FROM_PTR(o); } void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); *len = self->len; *items = self->items; } void mp_obj_list_set_len(mp_obj_t self_in, size_t len) { // trust that the caller knows what it's doing // TODO realloc if len got much smaller than alloc mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); self->len = len; } void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); size_t i = mp_get_index(self->base.type, self->len, index, false); self->items[i] = value; } /******************************************************************************/ /* list iterator */ typedef struct _mp_obj_list_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t list; size_t cur; } mp_obj_list_it_t; STATIC mp_obj_t list_it_iternext(mp_obj_t self_in) { mp_obj_list_it_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); if (self->cur < list->len) { mp_obj_t o_out = list->items[self->cur]; self->cur += 1; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_list_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_list_it_t *o = (mp_obj_list_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = list_it_iternext; o->list = list; o->cur = cur; return MP_OBJ_FROM_PTR(o); } #include #include #include "py/runtime.h" typedef struct _mp_obj_map_t { mp_obj_base_t base; size_t n_iters; mp_obj_t fun; mp_obj_t iters[]; } mp_obj_map_t; STATIC mp_obj_t map_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, MP_OBJ_FUN_ARGS_MAX, false); mp_obj_map_t *o = m_new_obj_var(mp_obj_map_t, mp_obj_t, n_args - 1); o->base.type = type; o->n_iters = n_args - 1; o->fun = args[0]; for (size_t i = 0; i < n_args - 1; i++) { o->iters[i] = mp_getiter(args[i + 1], NULL); } return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t map_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_map)); mp_obj_map_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t *nextses = m_new(mp_obj_t, self->n_iters); for (size_t i = 0; i < self->n_iters; i++) { mp_obj_t next = mp_iternext(self->iters[i]); if (next == MP_OBJ_STOP_ITERATION) { m_del(mp_obj_t, nextses, self->n_iters); return MP_OBJ_STOP_ITERATION; } nextses[i] = next; } return mp_call_function_n_kw(self->fun, self->n_iters, 0, nextses); } const mp_obj_type_t mp_type_map = { { &mp_type_type }, .name = MP_QSTR_map, .make_new = map_make_new, .getiter = mp_identity_getiter, .iternext = map_iternext, }; #include #include #include #include "py/objmodule.h" #include "py/runtime.h" #include "py/builtin.h" #include "genhdr/moduledefs.h" #if MICROPY_MODULE_BUILTIN_INIT STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj); #endif STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); const char *module_name = ""; mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_MAP_LOOKUP); if (elem != NULL) { module_name = mp_obj_str_get_str(elem->value); } #if MICROPY_PY___FILE__ // If we store __file__ to imported modules then try to lookup this // symbol to give more information about the module. elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_MAP_LOOKUP); if (elem != NULL) { mp_printf(print, "", module_name, mp_obj_str_get_str(elem->value)); return; } #endif mp_printf(print, "", module_name); } STATIC void module_attr_try_delegation(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { #if MICROPY_MODULE_ATTR_DELEGATION // Delegate lookup to a module's custom attr method (found in last lot of globals dict). mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); mp_map_t *map = &self->globals->map; if (map->table[map->alloc - 1].key == MP_OBJ_NEW_QSTR(MP_QSTRnull)) { ((mp_attr_fun_t)MP_OBJ_TO_PTR(map->table[map->alloc - 1].value))(self_in, attr, dest); } #else (void)self_in; (void)attr; (void)dest; #endif } STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { // load attribute mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { dest[0] = elem->value; #if MICROPY_MODULE_GETATTR } else if (attr != MP_QSTR___getattr__) { elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___getattr__), MP_MAP_LOOKUP); if (elem != NULL) { dest[0] = mp_call_function_1(elem->value, MP_OBJ_NEW_QSTR(attr)); } else { module_attr_try_delegation(self_in, attr, dest); } #endif } else { module_attr_try_delegation(self_in, attr, dest); } } else { // delete/store attribute mp_obj_dict_t *dict = self->globals; if (dict->map.is_fixed) { #if MICROPY_CAN_OVERRIDE_BUILTINS if (dict == &mp_module_builtins_globals) { if (MP_STATE_VM(mp_module_builtins_override_dict) == NULL) { MP_STATE_VM(mp_module_builtins_override_dict) = MP_OBJ_TO_PTR(mp_obj_new_dict(1)); } dict = MP_STATE_VM(mp_module_builtins_override_dict); } else #endif { // can't delete or store to fixed map module_attr_try_delegation(self_in, attr, dest); return; } } if (dest[1] == MP_OBJ_NULL) { // delete attribute mp_obj_dict_delete(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(attr)); } else { // store attribute mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(attr), dest[1]); } dest[0] = MP_OBJ_NULL; // indicate success } } const mp_obj_type_t mp_type_module = { { &mp_type_type }, .name = MP_QSTR_module, .print = module_print, .attr = module_attr, }; #include "py/bc.h" mp_obj_t mp_obj_new_module(qstr module_name) { mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); // We could error out if module already exists, but let C extensions // add new members to existing modules. if (el->value != MP_OBJ_NULL) { return el->value; } // create new module object mp_module_context_t *o = m_new_obj(mp_module_context_t); o->module.base.type = &mp_type_module; o->module.globals = MP_OBJ_TO_PTR(mp_obj_new_dict(MICROPY_MODULE_DICT_SIZE)); // store __name__ entry in the module mp_obj_dict_store(MP_OBJ_FROM_PTR(o->module.globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(module_name)); // store the new module into the slot in the global dict holding all modules el->value = MP_OBJ_FROM_PTR(o); // return the new module return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ // Global module table and related functions STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { { MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) }, { MP_ROM_QSTR(MP_QSTR_builtins), MP_ROM_PTR(&mp_module_builtins) }, { MP_ROM_QSTR(MP_QSTR_micropython), MP_ROM_PTR(&mp_module_micropython) }, #if MICROPY_PY_IO { MP_ROM_QSTR(MP_QSTR_uio), MP_ROM_PTR(&mp_module_io) }, #endif #if MICROPY_PY_COLLECTIONS { MP_ROM_QSTR(MP_QSTR_ucollections), MP_ROM_PTR(&mp_module_collections) }, #endif #if MICROPY_PY_STRUCT { MP_ROM_QSTR(MP_QSTR_ustruct), MP_ROM_PTR(&mp_module_ustruct) }, #endif #if MICROPY_PY_BUILTINS_FLOAT #if MICROPY_PY_MATH { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) }, #endif #if MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH { MP_ROM_QSTR(MP_QSTR_cmath), MP_ROM_PTR(&mp_module_cmath) }, #endif #endif #if MICROPY_PY_SYS { MP_ROM_QSTR(MP_QSTR_usys), MP_ROM_PTR(&mp_module_sys) }, #endif #if MICROPY_PY_GC && MICROPY_ENABLE_GC { MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) }, #endif #if MICROPY_PY_THREAD { MP_ROM_QSTR(MP_QSTR__thread), MP_ROM_PTR(&mp_module_thread) }, #endif // extmod modules #if MICROPY_PY_UASYNCIO { MP_ROM_QSTR(MP_QSTR__uasyncio), MP_ROM_PTR(&mp_module_uasyncio) }, #endif #if MICROPY_PY_UERRNO { MP_ROM_QSTR(MP_QSTR_uerrno), MP_ROM_PTR(&mp_module_uerrno) }, #endif #if MICROPY_PY_UCTYPES { MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) }, #endif #if MICROPY_PY_UZLIB { MP_ROM_QSTR(MP_QSTR_uzlib), MP_ROM_PTR(&mp_module_uzlib) }, #endif #if MICROPY_PY_UJSON { MP_ROM_QSTR(MP_QSTR_ujson), MP_ROM_PTR(&mp_module_ujson) }, #endif #if MICROPY_PY_UOS { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos) }, #endif #if MICROPY_PY_URE { MP_ROM_QSTR(MP_QSTR_ure), MP_ROM_PTR(&mp_module_ure) }, #endif #if MICROPY_PY_UHEAPQ { MP_ROM_QSTR(MP_QSTR_uheapq), MP_ROM_PTR(&mp_module_uheapq) }, #endif #if MICROPY_PY_UTIMEQ { MP_ROM_QSTR(MP_QSTR_utimeq), MP_ROM_PTR(&mp_module_utimeq) }, #endif #if MICROPY_PY_UHASHLIB { MP_ROM_QSTR(MP_QSTR_uhashlib), MP_ROM_PTR(&mp_module_uhashlib) }, #endif #if MICROPY_PY_UCRYPTOLIB { MP_ROM_QSTR(MP_QSTR_ucryptolib), MP_ROM_PTR(&mp_module_ucryptolib) }, #endif #if MICROPY_PY_UBINASCII { MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) }, #endif #if MICROPY_PY_URANDOM { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&mp_module_urandom) }, #endif #if MICROPY_PY_USELECT { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, #endif #if MICROPY_PY_USSL { MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) }, #endif #if MICROPY_PY_LWIP { MP_ROM_QSTR(MP_QSTR_lwip), MP_ROM_PTR(&mp_module_lwip) }, #endif #if MICROPY_PY_MACHINE { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&mp_module_machine) }, #endif #if MICROPY_PY_UWEBSOCKET { MP_ROM_QSTR(MP_QSTR_uwebsocket), MP_ROM_PTR(&mp_module_uwebsocket) }, #endif #if MICROPY_PY_WEBREPL { MP_ROM_QSTR(MP_QSTR__webrepl), MP_ROM_PTR(&mp_module_webrepl) }, #endif #if MICROPY_PY_FRAMEBUF { MP_ROM_QSTR(MP_QSTR_framebuf), MP_ROM_PTR(&mp_module_framebuf) }, #endif #if MICROPY_PY_BTREE { MP_ROM_QSTR(MP_QSTR_btree), MP_ROM_PTR(&mp_module_btree) }, #endif #if MICROPY_PY_BLUETOOTH { MP_ROM_QSTR(MP_QSTR_ubluetooth), MP_ROM_PTR(&mp_module_ubluetooth) }, #endif #if MICROPY_PY_UPLATFORM { MP_ROM_QSTR(MP_QSTR_uplatform), MP_ROM_PTR(&mp_module_uplatform) }, #endif // extra builtin modules as defined by a port MICROPY_PORT_BUILTIN_MODULES #ifdef MICROPY_REGISTERED_MODULES // builtin modules declared with MP_REGISTER_MODULE() MICROPY_REGISTERED_MODULES #endif }; MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table); // Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL. mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name) { // First try loaded modules. mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); if (!elem) { #if MICROPY_MODULE_WEAK_LINKS return mp_module_get_builtin(module_name); #else // Otherwise try builtin. elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); if (!elem) { return MP_OBJ_NULL; } #if MICROPY_MODULE_BUILTIN_INIT // If found, it's a newly loaded built-in, so init it. mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), elem->value); #endif #endif } return elem->value; } #if MICROPY_MODULE_WEAK_LINKS // Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL. mp_obj_t mp_module_get_builtin(qstr module_name) { // Try builtin. mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); if (!elem) { return MP_OBJ_NULL; } #if MICROPY_MODULE_BUILTIN_INIT // If found, it's a newly loaded built-in, so init it. mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), elem->value); #endif return elem->value; } #endif #if MICROPY_MODULE_BUILTIN_INIT STATIC void mp_module_register(mp_obj_t module_name, mp_obj_t module) { mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; mp_map_lookup(mp_loaded_modules_map, module_name, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module; } STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj) { // Look for __init__ and call it if it exists mp_obj_t dest[2]; mp_load_method_maybe(module_obj, MP_QSTR___init__, dest); if (dest[0] != MP_OBJ_NULL) { mp_call_method_n_kw(0, 0, dest); // Register module so __init__ is not called again. // If a module can be referenced by more than one name (eg due to weak links) // then __init__ will still be called for each distinct import, and it's then // up to the particular module to make sure it's __init__ code only runs once. mp_module_register(module_name, module_obj); } } #endif void mp_module_generic_attr(qstr attr, mp_obj_t *dest, const uint16_t *keys, mp_obj_t *values) { for (size_t i = 0; keys[i] != MP_QSTRnull; ++i) { if (attr == keys[i]) { if (dest[0] == MP_OBJ_NULL) { // load attribute (MP_OBJ_NULL returned for deleted items) dest[0] = values[i]; } else { // delete or store (delete stores MP_OBJ_NULL) values[i] = dest[1]; dest[0] = MP_OBJ_NULL; // indicate success } return; } } } #include #include "py/objtype.h" #include "py/runtime.h" typedef struct _mp_obj_object_t { mp_obj_base_t base; } mp_obj_object_t; STATIC mp_obj_t object_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)args; mp_arg_check_num(n_args, n_kw, 0, 0, false); mp_obj_object_t *o = m_new_obj(mp_obj_object_t); o->base.type = type; return MP_OBJ_FROM_PTR(o); } #if MICROPY_CPYTHON_COMPAT STATIC mp_obj_t object___init__(mp_obj_t self) { (void)self; return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(object___init___obj, object___init__); STATIC mp_obj_t object___new__(mp_obj_t cls) { if (!mp_obj_is_type(cls, &mp_type_type) || !mp_obj_is_instance_type((mp_obj_type_t *)MP_OBJ_TO_PTR(cls))) { mp_raise_TypeError(MP_ERROR_TEXT("arg must be user-type")); } // This executes only "__new__" part of instance creation. // TODO: This won't work well for classes with native bases. // TODO: This is a hack, should be resolved along the lines of // https://github.com/micropython/micropython/issues/606#issuecomment-43685883 const mp_obj_type_t *native_base; return MP_OBJ_FROM_PTR(mp_obj_new_instance(MP_OBJ_TO_PTR(cls), &native_base)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(object___new___fun_obj, object___new__); STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(object___new___obj, MP_ROM_PTR(&object___new___fun_obj)); #if MICROPY_PY_DELATTR_SETATTR STATIC mp_obj_t object___setattr__(mp_obj_t self_in, mp_obj_t attr, mp_obj_t value) { if (!mp_obj_is_instance_type(mp_obj_get_type(self_in))) { mp_raise_TypeError(MP_ERROR_TEXT("arg must be user-type")); } if (!mp_obj_is_str(attr)) { mp_raise_TypeError(NULL); } mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); mp_map_lookup(&self->members, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_3(object___setattr___obj, object___setattr__); STATIC mp_obj_t object___delattr__(mp_obj_t self_in, mp_obj_t attr) { if (!mp_obj_is_instance_type(mp_obj_get_type(self_in))) { mp_raise_TypeError(MP_ERROR_TEXT("arg must be user-type")); } if (!mp_obj_is_str(attr)) { mp_raise_TypeError(NULL); } mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); if (mp_map_lookup(&self->members, attr, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == NULL) { mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(object___delattr___obj, object___delattr__); #endif STATIC const mp_rom_map_elem_t object_locals_dict_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&object___init___obj) }, #endif #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR___new__), MP_ROM_PTR(&object___new___obj) }, #endif #if MICROPY_PY_DELATTR_SETATTR { MP_ROM_QSTR(MP_QSTR___setattr__), MP_ROM_PTR(&object___setattr___obj) }, { MP_ROM_QSTR(MP_QSTR___delattr__), MP_ROM_PTR(&object___delattr___obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(object_locals_dict, object_locals_dict_table); #endif const mp_obj_type_t mp_type_object = { { &mp_type_type }, .name = MP_QSTR_object, .make_new = object_make_new, #if MICROPY_CPYTHON_COMPAT .locals_dict = (mp_obj_dict_t *)&object_locals_dict, #endif }; #include #include "py/runtime.h" // This is universal iterator type which calls "iternext" method stored in // particular object instance. (So, each instance of this time can have its // own iteration behavior.) Having this type saves to define type objects // for various internal iterator objects. // Any instance should have these 2 fields at the beginning typedef struct _mp_obj_polymorph_iter_t { mp_obj_base_t base; mp_fun_1_t iternext; } mp_obj_polymorph_iter_t; STATIC mp_obj_t polymorph_it_iternext(mp_obj_t self_in) { mp_obj_polymorph_iter_t *self = MP_OBJ_TO_PTR(self_in); // Redirect call to object instance's iternext method return self->iternext(self_in); } const mp_obj_type_t mp_type_polymorph_iter = { { &mp_type_type }, .name = MP_QSTR_iterator, .getiter = mp_identity_getiter, .iternext = polymorph_it_iternext, }; #include #include #include "py/runtime.h" #if MICROPY_PY_BUILTINS_PROPERTY typedef struct _mp_obj_property_t { mp_obj_base_t base; mp_obj_t proxy[3]; // getter, setter, deleter } mp_obj_property_t; STATIC mp_obj_t property_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { enum { ARG_fget, ARG_fset, ARG_fdel, ARG_doc }; static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_doc, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, }; mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); mp_obj_property_t *o = m_new_obj(mp_obj_property_t); o->base.type = type; o->proxy[0] = vals[ARG_fget].u_obj; o->proxy[1] = vals[ARG_fset].u_obj; o->proxy[2] = vals[ARG_fdel].u_obj; // vals[ARG_doc] is silently discarded return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t property_getter(mp_obj_t self_in, mp_obj_t getter) { mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); *p2 = *(mp_obj_property_t *)MP_OBJ_TO_PTR(self_in); p2->proxy[0] = getter; return MP_OBJ_FROM_PTR(p2); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_getter_obj, property_getter); STATIC mp_obj_t property_setter(mp_obj_t self_in, mp_obj_t setter) { mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); *p2 = *(mp_obj_property_t *)MP_OBJ_TO_PTR(self_in); p2->proxy[1] = setter; return MP_OBJ_FROM_PTR(p2); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_setter_obj, property_setter); STATIC mp_obj_t property_deleter(mp_obj_t self_in, mp_obj_t deleter) { mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); *p2 = *(mp_obj_property_t *)MP_OBJ_TO_PTR(self_in); p2->proxy[2] = deleter; return MP_OBJ_FROM_PTR(p2); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_deleter_obj, property_deleter); STATIC const mp_rom_map_elem_t property_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_getter), MP_ROM_PTR(&property_getter_obj) }, { MP_ROM_QSTR(MP_QSTR_setter), MP_ROM_PTR(&property_setter_obj) }, { MP_ROM_QSTR(MP_QSTR_deleter), MP_ROM_PTR(&property_deleter_obj) }, }; STATIC MP_DEFINE_CONST_DICT(property_locals_dict, property_locals_dict_table); const mp_obj_type_t mp_type_property = { { &mp_type_type }, .name = MP_QSTR_property, .make_new = property_make_new, .locals_dict = (mp_obj_dict_t *)&property_locals_dict, }; const mp_obj_t *mp_obj_property_get(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_property)); mp_obj_property_t *self = MP_OBJ_TO_PTR(self_in); return self->proxy; } #endif // MICROPY_PY_BUILTINS_PROPERTY #include #include "py/obj.h" #if !MICROPY_OBJ_IMMEDIATE_OBJS typedef struct _mp_obj_none_t { mp_obj_base_t base; } mp_obj_none_t; #endif STATIC void none_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)self_in; if (MICROPY_PY_UJSON && kind == PRINT_JSON) { mp_print_str(print, "null"); } else { mp_print_str(print, "None"); } } const mp_obj_type_t mp_type_NoneType = { { &mp_type_type }, .name = MP_QSTR_NoneType, .print = none_print, .unary_op = mp_generic_unary_op, }; #if !MICROPY_OBJ_IMMEDIATE_OBJS const mp_obj_none_t mp_const_none_obj = {{&mp_type_NoneType}}; #endif #include #include "py/objtuple.h" #include "py/runtime.h" #include "py/objstr.h" #include "py/objnamedtuple.h" #if MICROPY_PY_COLLECTIONS size_t mp_obj_namedtuple_find_field(const mp_obj_namedtuple_type_t *type, qstr name) { for (size_t i = 0; i < type->n_fields; i++) { if (type->fields[i] == name) { return i; } } return (size_t)-1; } #if MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT STATIC mp_obj_t namedtuple_asdict(mp_obj_t self_in) { mp_obj_namedtuple_t *self = MP_OBJ_TO_PTR(self_in); const qstr *fields = ((mp_obj_namedtuple_type_t *)self->tuple.base.type)->fields; mp_obj_t dict = mp_obj_new_dict(self->tuple.len); // make it an OrderedDict mp_obj_dict_t *dictObj = MP_OBJ_TO_PTR(dict); dictObj->base.type = &mp_type_ordereddict; dictObj->map.is_ordered = 1; for (size_t i = 0; i < self->tuple.len; ++i) { mp_obj_dict_store(dict, MP_OBJ_NEW_QSTR(fields[i]), self->tuple.items[i]); } return dict; } MP_DEFINE_CONST_FUN_OBJ_1(namedtuple_asdict_obj, namedtuple_asdict); #endif STATIC void namedtuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_namedtuple_t *o = MP_OBJ_TO_PTR(o_in); mp_printf(print, "%q", o->tuple.base.type->name); const qstr *fields = ((mp_obj_namedtuple_type_t *)o->tuple.base.type)->fields; mp_obj_attrtuple_print_helper(print, fields, &o->tuple); } STATIC void namedtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { // load attribute mp_obj_namedtuple_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT if (attr == MP_QSTR__asdict) { dest[0] = MP_OBJ_FROM_PTR(&namedtuple_asdict_obj); dest[1] = self_in; return; } #endif size_t id = mp_obj_namedtuple_find_field((mp_obj_namedtuple_type_t *)self->tuple.base.type, attr); if (id == (size_t)-1) { return; } dest[0] = self->tuple.items[id]; } else { // delete/store attribute // provide more detailed error message than we'd get by just returning mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't set attribute")); } } STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { const mp_obj_namedtuple_type_t *type = (const mp_obj_namedtuple_type_t *)type_in; size_t num_fields = type->n_fields; if (n_args + n_kw != num_fields) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), num_fields, n_args + n_kw); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("%q() takes %d positional arguments but %d were given"), type->base.name, num_fields, n_args + n_kw); #endif } // Create a tuple and set the type to this namedtuple mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_fields, NULL)); tuple->base.type = type_in; // Copy the positional args into the first slots of the namedtuple memcpy(&tuple->items[0], args, sizeof(mp_obj_t) * n_args); // Fill in the remaining slots with the keyword args memset(&tuple->items[n_args], 0, sizeof(mp_obj_t) * n_kw); for (size_t i = n_args; i < n_args + 2 * n_kw; i += 2) { qstr kw = mp_obj_str_get_qstr(args[i]); size_t id = mp_obj_namedtuple_find_field(type, kw); if (id == (size_t)-1) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw); #endif } if (tuple->items[id] != MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw); #endif } tuple->items[id] = args[i + 1]; } return MP_OBJ_FROM_PTR(tuple); } mp_obj_namedtuple_type_t *mp_obj_new_namedtuple_base(size_t n_fields, mp_obj_t *fields) { mp_obj_namedtuple_type_t *o = m_new_obj_var(mp_obj_namedtuple_type_t, qstr, n_fields); memset(&o->base, 0, sizeof(o->base)); o->n_fields = n_fields; for (size_t i = 0; i < n_fields; i++) { o->fields[i] = mp_obj_str_get_qstr(fields[i]); } return o; } STATIC mp_obj_t mp_obj_new_namedtuple_type(qstr name, size_t n_fields, mp_obj_t *fields) { mp_obj_namedtuple_type_t *o = mp_obj_new_namedtuple_base(n_fields, fields); o->base.base.type = &mp_type_type; o->base.flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE; // can match tuple o->base.name = name; o->base.print = namedtuple_print; o->base.make_new = namedtuple_make_new; o->base.unary_op = mp_obj_tuple_unary_op; o->base.binary_op = mp_obj_tuple_binary_op; o->base.attr = namedtuple_attr; o->base.subscr = mp_obj_tuple_subscr; o->base.getiter = mp_obj_tuple_getiter; o->base.parent = &mp_type_tuple; return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t new_namedtuple_type(mp_obj_t name_in, mp_obj_t fields_in) { qstr name = mp_obj_str_get_qstr(name_in); size_t n_fields; mp_obj_t *fields; #if MICROPY_CPYTHON_COMPAT if (mp_obj_is_str(fields_in)) { fields_in = mp_obj_str_split(1, &fields_in); } #endif mp_obj_get_array(fields_in, &n_fields, &fields); return mp_obj_new_namedtuple_type(name, n_fields, fields); } MP_DEFINE_CONST_FUN_OBJ_2(mp_namedtuple_obj, new_namedtuple_type); #endif // MICROPY_PY_COLLECTIONS #include #include "py/runtime.h" /******************************************************************************/ /* range iterator */ typedef struct _mp_obj_range_it_t { mp_obj_base_t base; // TODO make these values generic objects or something mp_int_t cur; mp_int_t stop; mp_int_t step; } mp_obj_range_it_t; STATIC mp_obj_t range_it_iternext(mp_obj_t o_in) { mp_obj_range_it_t *o = MP_OBJ_TO_PTR(o_in); if ((o->step > 0 && o->cur < o->stop) || (o->step < 0 && o->cur > o->stop)) { mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(o->cur); o->cur += o->step; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } STATIC const mp_obj_type_t mp_type_range_it = { { &mp_type_type }, .name = MP_QSTR_iterator, .getiter = mp_identity_getiter, .iternext = range_it_iternext, }; STATIC mp_obj_t mp_obj_new_range_iterator(mp_int_t cur, mp_int_t stop, mp_int_t step, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_range_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_range_it_t *o = (mp_obj_range_it_t *)iter_buf; o->base.type = &mp_type_range_it; o->cur = cur; o->stop = stop; o->step = step; return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ /* range */ typedef struct _mp_obj_range_t { mp_obj_base_t base; // TODO make these values generic objects or something mp_int_t start; mp_int_t stop; mp_int_t step; } mp_obj_range_t; STATIC void range_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_range_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "range(" INT_FMT ", " INT_FMT "", self->start, self->stop); if (self->step == 1) { mp_print_str(print, ")"); } else { mp_printf(print, ", " INT_FMT ")", self->step); } } STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 3, false); mp_obj_range_t *o = m_new_obj(mp_obj_range_t); o->base.type = type; o->start = 0; o->step = 1; if (n_args == 1) { o->stop = mp_obj_get_int(args[0]); } else { o->start = mp_obj_get_int(args[0]); o->stop = mp_obj_get_int(args[1]); if (n_args == 3) { o->step = mp_obj_get_int(args[2]); if (o->step == 0) { mp_raise_ValueError(MP_ERROR_TEXT("zero step")); } } } return MP_OBJ_FROM_PTR(o); } STATIC mp_int_t range_len(mp_obj_range_t *self) { // When computing length, need to take into account step!=1 and step<0. mp_int_t len = self->stop - self->start + self->step; if (self->step > 0) { len -= 1; } else { len += 1; } len = len / self->step; if (len < 0) { len = 0; } return len; } STATIC mp_obj_t range_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_range_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t len = range_len(self); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(len > 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(len); default: return MP_OBJ_NULL; // op not supported } } #if MICROPY_PY_BUILTINS_RANGE_BINOP STATIC mp_obj_t range_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { if (!mp_obj_is_type(rhs_in, &mp_type_range) || op != MP_BINARY_OP_EQUAL) { return MP_OBJ_NULL; // op not supported } mp_obj_range_t *lhs = MP_OBJ_TO_PTR(lhs_in); mp_obj_range_t *rhs = MP_OBJ_TO_PTR(rhs_in); mp_int_t lhs_len = range_len(lhs); mp_int_t rhs_len = range_len(rhs); return mp_obj_new_bool( lhs_len == rhs_len && (lhs_len == 0 || (lhs->start == rhs->start && (lhs_len == 1 || lhs->step == rhs->step))) ); } #endif STATIC mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (value == MP_OBJ_SENTINEL) { // load mp_obj_range_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t len = range_len(self); #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; mp_seq_get_fast_slice_indexes(len, index, &slice); mp_obj_range_t *o = m_new_obj(mp_obj_range_t); o->base.type = &mp_type_range; o->start = self->start + slice.start * self->step; o->stop = self->start + slice.stop * self->step; o->step = slice.step * self->step; if (slice.step < 0) { // Negative slice steps have inclusive stop, so adjust for exclusive o->stop -= self->step; } return MP_OBJ_FROM_PTR(o); } #endif size_t index_val = mp_get_index(self->base.type, len, index, false); return MP_OBJ_NEW_SMALL_INT(self->start + index_val * self->step); } else { return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t range_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { mp_obj_range_t *o = MP_OBJ_TO_PTR(o_in); return mp_obj_new_range_iterator(o->start, o->stop, o->step, iter_buf); } #if MICROPY_PY_BUILTINS_RANGE_ATTRS STATIC void range_attr(mp_obj_t o_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } mp_obj_range_t *o = MP_OBJ_TO_PTR(o_in); if (attr == MP_QSTR_start) { dest[0] = mp_obj_new_int(o->start); } else if (attr == MP_QSTR_stop) { dest[0] = mp_obj_new_int(o->stop); } else if (attr == MP_QSTR_step) { dest[0] = mp_obj_new_int(o->step); } } #endif const mp_obj_type_t mp_type_range = { { &mp_type_type }, .name = MP_QSTR_range, .print = range_print, .make_new = range_make_new, .unary_op = range_unary_op, #if MICROPY_PY_BUILTINS_RANGE_BINOP .binary_op = range_binary_op, #endif .subscr = range_subscr, .getiter = range_getiter, #if MICROPY_PY_BUILTINS_RANGE_ATTRS .attr = range_attr, #endif }; #include #include #include "py/runtime.h" #if MICROPY_PY_BUILTINS_REVERSED typedef struct _mp_obj_reversed_t { mp_obj_base_t base; mp_obj_t seq; // sequence object that we are reversing mp_uint_t cur_index; // current index, plus 1; 0=no more, 1=last one (index 0) } mp_obj_reversed_t; STATIC mp_obj_t reversed_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 1, false); // check if __reversed__ exists, and if so delegate to it mp_obj_t dest[2]; mp_load_method_maybe(args[0], MP_QSTR___reversed__, dest); if (dest[0] != MP_OBJ_NULL) { return mp_call_method_n_kw(0, 0, dest); } mp_obj_reversed_t *o = m_new_obj(mp_obj_reversed_t); o->base.type = type; o->seq = args[0]; o->cur_index = mp_obj_get_int(mp_obj_len(args[0])); // start at the end of the sequence return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t reversed_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_reversed)); mp_obj_reversed_t *self = MP_OBJ_TO_PTR(self_in); // "raise" stop iteration if we are at the end (the start) of the sequence if (self->cur_index == 0) { return MP_OBJ_STOP_ITERATION; } // pre-decrement and index sequence self->cur_index -= 1; return mp_obj_subscr(self->seq, MP_OBJ_NEW_SMALL_INT(self->cur_index), MP_OBJ_SENTINEL); } const mp_obj_type_t mp_type_reversed = { { &mp_type_type }, .name = MP_QSTR_reversed, .make_new = reversed_make_new, .getiter = mp_identity_getiter, .iternext = reversed_iternext, }; #endif // MICROPY_PY_BUILTINS_REVERSED #include #include #include #include "py/runtime.h" #include "py/builtin.h" #if MICROPY_PY_BUILTINS_SET typedef struct _mp_obj_set_t { mp_obj_base_t base; mp_set_t set; } mp_obj_set_t; typedef struct _mp_obj_set_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_set_t *set; size_t cur; } mp_obj_set_it_t; STATIC bool is_set_or_frozenset(mp_obj_t o) { return mp_obj_is_type(o, &mp_type_set) #if MICROPY_PY_BUILTINS_FROZENSET || mp_obj_is_type(o, &mp_type_frozenset) #endif ; } // This macro is shorthand for mp_check_self to verify the argument is a set. #define check_set(o) mp_check_self(mp_obj_is_type(o, &mp_type_set)) // This macro is shorthand for mp_check_self to verify the argument is a // set or frozenset for methods that operate on both of these types. #define check_set_or_frozenset(o) mp_check_self(is_set_or_frozenset(o)) STATIC void set_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_BUILTINS_FROZENSET bool is_frozen = mp_obj_is_type(self_in, &mp_type_frozenset); #endif if (self->set.used == 0) { #if MICROPY_PY_BUILTINS_FROZENSET if (is_frozen) { mp_print_str(print, "frozen"); } #endif mp_print_str(print, "set()"); return; } bool first = true; #if MICROPY_PY_BUILTINS_FROZENSET if (is_frozen) { mp_print_str(print, "frozenset("); } #endif mp_print_str(print, "{"); for (size_t i = 0; i < self->set.alloc; i++) { if (mp_set_slot_is_filled(&self->set, i)) { if (!first) { mp_print_str(print, ", "); } first = false; mp_obj_print_helper(print, self->set.table[i], PRINT_REPR); } } mp_print_str(print, "}"); #if MICROPY_PY_BUILTINS_FROZENSET if (is_frozen) { mp_print_str(print, ")"); } #endif } STATIC mp_obj_t set_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 0, 1, false); switch (n_args) { case 0: { // create a new, empty set mp_obj_set_t *set = MP_OBJ_TO_PTR(mp_obj_new_set(0, NULL)); // set actual set/frozenset type set->base.type = type; return MP_OBJ_FROM_PTR(set); } case 1: default: { // can only be 0 or 1 arg // 1 argument, an iterable from which we make a new set mp_obj_t set = mp_obj_new_set(0, NULL); mp_obj_t iterable = mp_getiter(args[0], NULL); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { mp_obj_set_store(set, item); } // Set actual set/frozenset type ((mp_obj_set_t *)MP_OBJ_TO_PTR(set))->base.type = type; return set; } } } STATIC mp_obj_t set_it_iternext(mp_obj_t self_in) { mp_obj_set_it_t *self = MP_OBJ_TO_PTR(self_in); size_t max = self->set->set.alloc; mp_set_t *set = &self->set->set; for (size_t i = self->cur; i < max; i++) { if (mp_set_slot_is_filled(set, i)) { self->cur = i + 1; return set->table[i]; } } return MP_OBJ_STOP_ITERATION; } STATIC mp_obj_t set_getiter(mp_obj_t set_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_set_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_set_it_t *o = (mp_obj_set_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = set_it_iternext; o->set = (mp_obj_set_t *)MP_OBJ_TO_PTR(set_in); o->cur = 0; return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ /* set methods */ STATIC mp_obj_t set_add(mp_obj_t self_in, mp_obj_t item) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_add_obj, set_add); STATIC mp_obj_t set_clear(mp_obj_t self_in) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_set_clear(&self->set); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(set_clear_obj, set_clear); STATIC mp_obj_t set_copy(mp_obj_t self_in) { check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_set_t *other = m_new_obj(mp_obj_set_t); other->base.type = self->base.type; mp_set_init(&other->set, self->set.alloc); other->set.used = self->set.used; memcpy(other->set.table, self->set.table, self->set.alloc * sizeof(mp_obj_t)); return MP_OBJ_FROM_PTR(other); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(set_copy_obj, set_copy); STATIC mp_obj_t set_discard(mp_obj_t self_in, mp_obj_t item) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_discard_obj, set_discard); STATIC mp_obj_t set_diff_int(size_t n_args, const mp_obj_t *args, bool update) { mp_obj_t self; if (update) { check_set(args[0]); self = args[0]; } else { self = set_copy(args[0]); } for (size_t i = 1; i < n_args; i++) { mp_obj_t other = args[i]; if (self == other) { set_clear(self); } else { mp_set_t *self_set = &((mp_obj_set_t *)MP_OBJ_TO_PTR(self))->set; mp_obj_t iter = mp_getiter(other, NULL); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_set_lookup(self_set, next, MP_MAP_LOOKUP_REMOVE_IF_FOUND); } } } return self; } STATIC mp_obj_t set_diff(size_t n_args, const mp_obj_t *args) { return set_diff_int(n_args, args, false); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(set_diff_obj, 1, set_diff); STATIC mp_obj_t set_diff_update(size_t n_args, const mp_obj_t *args) { set_diff_int(n_args, args, true); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(set_diff_update_obj, 1, set_diff_update); STATIC mp_obj_t set_intersect_int(mp_obj_t self_in, mp_obj_t other, bool update) { if (update) { check_set(self_in); } else { check_set_or_frozenset(self_in); } if (self_in == other) { return update ? mp_const_none : set_copy(self_in); } mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_set_t *out = MP_OBJ_TO_PTR(mp_obj_new_set(0, NULL)); mp_obj_t iter = mp_getiter(other, NULL); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { if (mp_set_lookup(&self->set, next, MP_MAP_LOOKUP)) { set_add(MP_OBJ_FROM_PTR(out), next); } } if (update) { m_del(mp_obj_t, self->set.table, self->set.alloc); self->set.alloc = out->set.alloc; self->set.used = out->set.used; self->set.table = out->set.table; } return update ? mp_const_none : MP_OBJ_FROM_PTR(out); } STATIC mp_obj_t set_intersect(mp_obj_t self_in, mp_obj_t other) { return set_intersect_int(self_in, other, false); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_intersect_obj, set_intersect); STATIC mp_obj_t set_intersect_update(mp_obj_t self_in, mp_obj_t other) { return set_intersect_int(self_in, other, true); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_intersect_update_obj, set_intersect_update); STATIC mp_obj_t set_isdisjoint(mp_obj_t self_in, mp_obj_t other) { check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_iter_buf_t iter_buf; mp_obj_t iter = mp_getiter(other, &iter_buf); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { if (mp_set_lookup(&self->set, next, MP_MAP_LOOKUP)) { return mp_const_false; } } return mp_const_true; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_isdisjoint_obj, set_isdisjoint); STATIC mp_obj_t set_issubset_internal(mp_obj_t self_in, mp_obj_t other_in, bool proper) { mp_obj_set_t *self; bool cleanup_self = false; if (is_set_or_frozenset(self_in)) { self = MP_OBJ_TO_PTR(self_in); } else { self = MP_OBJ_TO_PTR(set_make_new(&mp_type_set, 1, 0, &self_in)); cleanup_self = true; } mp_obj_set_t *other; bool cleanup_other = false; if (is_set_or_frozenset(other_in)) { other = MP_OBJ_TO_PTR(other_in); } else { other = MP_OBJ_TO_PTR(set_make_new(&mp_type_set, 1, 0, &other_in)); cleanup_other = true; } mp_obj_t out = mp_const_true; if (proper && self->set.used == other->set.used) { out = mp_const_false; } else { mp_obj_iter_buf_t iter_buf; mp_obj_t iter = set_getiter(MP_OBJ_FROM_PTR(self), &iter_buf); mp_obj_t next; while ((next = set_it_iternext(iter)) != MP_OBJ_STOP_ITERATION) { if (!mp_set_lookup(&other->set, next, MP_MAP_LOOKUP)) { out = mp_const_false; break; } } } // TODO: Should free objects altogether if (cleanup_self) { set_clear(MP_OBJ_FROM_PTR(self)); } if (cleanup_other) { set_clear(MP_OBJ_FROM_PTR(other)); } return out; } STATIC mp_obj_t set_issubset(mp_obj_t self_in, mp_obj_t other_in) { return set_issubset_internal(self_in, other_in, false); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_issubset_obj, set_issubset); STATIC mp_obj_t set_issubset_proper(mp_obj_t self_in, mp_obj_t other_in) { return set_issubset_internal(self_in, other_in, true); } STATIC mp_obj_t set_issuperset(mp_obj_t self_in, mp_obj_t other_in) { return set_issubset_internal(other_in, self_in, false); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_issuperset_obj, set_issuperset); STATIC mp_obj_t set_issuperset_proper(mp_obj_t self_in, mp_obj_t other_in) { return set_issubset_internal(other_in, self_in, true); } STATIC mp_obj_t set_equal(mp_obj_t self_in, mp_obj_t other_in) { assert(is_set_or_frozenset(other_in)); check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_set_t *other = MP_OBJ_TO_PTR(other_in); if (self->set.used != other->set.used) { return mp_const_false; } return set_issubset(self_in, other_in); } STATIC mp_obj_t set_pop(mp_obj_t self_in) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t obj = mp_set_remove_first(&self->set); if (obj == MP_OBJ_NULL) { mp_raise_msg(&mp_type_KeyError, MP_ERROR_TEXT("pop from an empty set")); } return obj; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(set_pop_obj, set_pop); STATIC mp_obj_t set_remove(mp_obj_t self_in, mp_obj_t item) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); if (mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) { mp_raise_type_arg(&mp_type_KeyError, item); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_remove_obj, set_remove); STATIC mp_obj_t set_symmetric_difference_update(mp_obj_t self_in, mp_obj_t other_in) { check_set_or_frozenset(self_in); // can be frozenset due to call from set_symmetric_difference mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t iter = mp_getiter(other_in, NULL); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_set_lookup(&self->set, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_symmetric_difference_update_obj, set_symmetric_difference_update); STATIC mp_obj_t set_symmetric_difference(mp_obj_t self_in, mp_obj_t other_in) { mp_obj_t self_out = set_copy(self_in); set_symmetric_difference_update(self_out, other_in); return self_out; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_symmetric_difference_obj, set_symmetric_difference); STATIC void set_update_int(mp_obj_set_t *self, mp_obj_t other_in) { mp_obj_t iter = mp_getiter(other_in, NULL); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_set_lookup(&self->set, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); } } STATIC mp_obj_t set_update(size_t n_args, const mp_obj_t *args) { check_set(args[0]); for (size_t i = 1; i < n_args; i++) { set_update_int(MP_OBJ_TO_PTR(args[0]), args[i]); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(set_update_obj, 1, set_update); STATIC mp_obj_t set_union(mp_obj_t self_in, mp_obj_t other_in) { check_set_or_frozenset(self_in); mp_obj_t self = set_copy(self_in); set_update_int(MP_OBJ_TO_PTR(self), other_in); return self; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_union_obj, set_union); STATIC mp_obj_t set_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->set.used != 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(self->set.used); #if MICROPY_PY_BUILTINS_FROZENSET case MP_UNARY_OP_HASH: if (mp_obj_is_type(self_in, &mp_type_frozenset)) { // start hash with unique value mp_int_t hash = (mp_int_t)(uintptr_t)&mp_type_frozenset; size_t max = self->set.alloc; mp_set_t *set = &self->set; for (size_t i = 0; i < max; i++) { if (mp_set_slot_is_filled(set, i)) { hash += MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, set->table[i])); } } return MP_OBJ_NEW_SMALL_INT(hash); } MP_FALLTHROUGH #endif default: return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t set_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { mp_obj_t args[] = {lhs, rhs}; #if MICROPY_PY_BUILTINS_FROZENSET bool update = mp_obj_is_type(lhs, &mp_type_set); #else bool update = true; #endif if (op != MP_BINARY_OP_CONTAINS && !is_set_or_frozenset(rhs)) { // For all ops except containment the RHS must be a set/frozenset return MP_OBJ_NULL; } switch (op) { case MP_BINARY_OP_OR: return set_union(lhs, rhs); case MP_BINARY_OP_XOR: return set_symmetric_difference(lhs, rhs); case MP_BINARY_OP_AND: return set_intersect(lhs, rhs); case MP_BINARY_OP_SUBTRACT: return set_diff(2, args); case MP_BINARY_OP_INPLACE_OR: if (update) { set_update(2, args); return lhs; } else { return set_union(lhs, rhs); } case MP_BINARY_OP_INPLACE_XOR: if (update) { set_symmetric_difference_update(lhs, rhs); return lhs; } else { return set_symmetric_difference(lhs, rhs); } case MP_BINARY_OP_INPLACE_AND: rhs = set_intersect_int(lhs, rhs, update); if (update) { return lhs; } else { return rhs; } case MP_BINARY_OP_INPLACE_SUBTRACT: return set_diff_int(2, args, update); case MP_BINARY_OP_LESS: return set_issubset_proper(lhs, rhs); case MP_BINARY_OP_MORE: return set_issuperset_proper(lhs, rhs); case MP_BINARY_OP_EQUAL: return set_equal(lhs, rhs); case MP_BINARY_OP_LESS_EQUAL: return set_issubset(lhs, rhs); case MP_BINARY_OP_MORE_EQUAL: return set_issuperset(lhs, rhs); case MP_BINARY_OP_CONTAINS: { mp_obj_set_t *o = MP_OBJ_TO_PTR(lhs); mp_obj_t elem = mp_set_lookup(&o->set, rhs, MP_MAP_LOOKUP); return mp_obj_new_bool(elem != MP_OBJ_NULL); } default: return MP_OBJ_NULL; // op not supported } } /******************************************************************************/ /* set constructors & public C API */ STATIC const mp_rom_map_elem_t set_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&set_add_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&set_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&set_copy_obj) }, { MP_ROM_QSTR(MP_QSTR_discard), MP_ROM_PTR(&set_discard_obj) }, { MP_ROM_QSTR(MP_QSTR_difference), MP_ROM_PTR(&set_diff_obj) }, { MP_ROM_QSTR(MP_QSTR_difference_update), MP_ROM_PTR(&set_diff_update_obj) }, { MP_ROM_QSTR(MP_QSTR_intersection), MP_ROM_PTR(&set_intersect_obj) }, { MP_ROM_QSTR(MP_QSTR_intersection_update), MP_ROM_PTR(&set_intersect_update_obj) }, { MP_ROM_QSTR(MP_QSTR_isdisjoint), MP_ROM_PTR(&set_isdisjoint_obj) }, { MP_ROM_QSTR(MP_QSTR_issubset), MP_ROM_PTR(&set_issubset_obj) }, { MP_ROM_QSTR(MP_QSTR_issuperset), MP_ROM_PTR(&set_issuperset_obj) }, { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&set_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&set_remove_obj) }, { MP_ROM_QSTR(MP_QSTR_symmetric_difference), MP_ROM_PTR(&set_symmetric_difference_obj) }, { MP_ROM_QSTR(MP_QSTR_symmetric_difference_update), MP_ROM_PTR(&set_symmetric_difference_update_obj) }, { MP_ROM_QSTR(MP_QSTR_union), MP_ROM_PTR(&set_union_obj) }, { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&set_update_obj) }, { MP_ROM_QSTR(MP_QSTR___contains__), MP_ROM_PTR(&mp_op_contains_obj) }, }; STATIC MP_DEFINE_CONST_DICT(set_locals_dict, set_locals_dict_table); const mp_obj_type_t mp_type_set = { { &mp_type_type }, .name = MP_QSTR_set, .print = set_print, .make_new = set_make_new, .unary_op = set_unary_op, .binary_op = set_binary_op, .getiter = set_getiter, .locals_dict = (mp_obj_dict_t *)&set_locals_dict, }; #if MICROPY_PY_BUILTINS_FROZENSET STATIC const mp_rom_map_elem_t frozenset_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&set_copy_obj) }, { MP_ROM_QSTR(MP_QSTR_difference), MP_ROM_PTR(&set_diff_obj) }, { MP_ROM_QSTR(MP_QSTR_intersection), MP_ROM_PTR(&set_intersect_obj) }, { MP_ROM_QSTR(MP_QSTR_isdisjoint), MP_ROM_PTR(&set_isdisjoint_obj) }, { MP_ROM_QSTR(MP_QSTR_issubset), MP_ROM_PTR(&set_issubset_obj) }, { MP_ROM_QSTR(MP_QSTR_issuperset), MP_ROM_PTR(&set_issuperset_obj) }, { MP_ROM_QSTR(MP_QSTR_symmetric_difference), MP_ROM_PTR(&set_symmetric_difference_obj) }, { MP_ROM_QSTR(MP_QSTR_union), MP_ROM_PTR(&set_union_obj) }, { MP_ROM_QSTR(MP_QSTR___contains__), MP_ROM_PTR(&mp_op_contains_obj) }, }; STATIC MP_DEFINE_CONST_DICT(frozenset_locals_dict, frozenset_locals_dict_table); const mp_obj_type_t mp_type_frozenset = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, .name = MP_QSTR_frozenset, .print = set_print, .make_new = set_make_new, .unary_op = set_unary_op, .binary_op = set_binary_op, .getiter = set_getiter, .locals_dict = (mp_obj_dict_t *)&frozenset_locals_dict, }; #endif mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { mp_obj_set_t *o = m_new_obj(mp_obj_set_t); o->base.type = &mp_type_set; mp_set_init(&o->set, n_args); for (size_t i = 0; i < n_args; i++) { mp_set_lookup(&o->set, items[i], MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); } return MP_OBJ_FROM_PTR(o); } void mp_obj_set_store(mp_obj_t self_in, mp_obj_t item) { mp_check_self(mp_obj_is_type(self_in, &mp_type_set)); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); } #endif // MICROPY_PY_BUILTINS_SET #include #include #include "py/obj.h" /******************************************************************************/ /* singleton objects defined by Python */ typedef struct _mp_obj_singleton_t { mp_obj_base_t base; qstr name; } mp_obj_singleton_t; STATIC void singleton_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_singleton_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "%q", self->name); } const mp_obj_type_t mp_type_singleton = { { &mp_type_type }, .name = MP_QSTR_, .print = singleton_print, .unary_op = mp_generic_unary_op, }; const mp_obj_singleton_t mp_const_ellipsis_obj = {{&mp_type_singleton}, MP_QSTR_Ellipsis}; #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED const mp_obj_singleton_t mp_const_notimplemented_obj = {{&mp_type_singleton}, MP_QSTR_NotImplemented}; #endif #include #include #include "py/runtime.h" /******************************************************************************/ /* slice object */ #if MICROPY_PY_BUILTINS_SLICE STATIC void slice_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_slice_t *o = MP_OBJ_TO_PTR(o_in); mp_print_str(print, "slice("); mp_obj_print_helper(print, o->start, PRINT_REPR); mp_print_str(print, ", "); mp_obj_print_helper(print, o->stop, PRINT_REPR); mp_print_str(print, ", "); mp_obj_print_helper(print, o->step, PRINT_REPR); mp_print_str(print, ")"); } #if MICROPY_PY_BUILTINS_SLICE_INDICES STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { mp_int_t length = mp_obj_int_get_checked(length_obj); mp_bound_slice_t bound_indices; mp_obj_slice_indices(self_in, length, &bound_indices); mp_obj_t results[3] = { MP_OBJ_NEW_SMALL_INT(bound_indices.start), MP_OBJ_NEW_SMALL_INT(bound_indices.stop), MP_OBJ_NEW_SMALL_INT(bound_indices.step), }; return mp_obj_new_tuple(3, results); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices); #endif #if MICROPY_PY_BUILTINS_SLICE_ATTRS STATIC void slice_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); if (attr == MP_QSTR_start) { dest[0] = self->start; } else if (attr == MP_QSTR_stop) { dest[0] = self->stop; } else if (attr == MP_QSTR_step) { dest[0] = self->step; #if MICROPY_PY_BUILTINS_SLICE_INDICES } else if (attr == MP_QSTR_indices) { dest[0] = MP_OBJ_FROM_PTR(&slice_indices_obj); dest[1] = self_in; #endif } } #endif #if MICROPY_PY_BUILTINS_SLICE_INDICES && !MICROPY_PY_BUILTINS_SLICE_ATTRS STATIC const mp_rom_map_elem_t slice_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_indices), MP_ROM_PTR(&slice_indices_obj) }, }; STATIC MP_DEFINE_CONST_DICT(slice_locals_dict, slice_locals_dict_table); #endif const mp_obj_type_t mp_type_slice = { { &mp_type_type }, .name = MP_QSTR_slice, .print = slice_print, #if MICROPY_PY_BUILTINS_SLICE_ATTRS .attr = slice_attr, #elif MICROPY_PY_BUILTINS_SLICE_INDICES .locals_dict = (mp_obj_dict_t *)&slice_locals_dict, #endif }; mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) { mp_obj_slice_t *o = m_new_obj(mp_obj_slice_t); o->base.type = &mp_type_slice; o->start = ostart; o->stop = ostop; o->step = ostep; return MP_OBJ_FROM_PTR(o); } // Return the real index and step values for a slice when applied to a sequence of // the given length, resolving missing components, negative values and values off // the end of the sequence. void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) { mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t start, stop, step; if (self->step == mp_const_none) { step = 1; } else { step = mp_obj_get_int(self->step); if (step == 0) { mp_raise_ValueError(MP_ERROR_TEXT("slice step can't be zero")); } } if (step > 0) { // Positive step if (self->start == mp_const_none) { start = 0; } else { start = mp_obj_get_int(self->start); if (start < 0) { start += length; } start = MIN(length, MAX(start, 0)); } if (self->stop == mp_const_none) { stop = length; } else { stop = mp_obj_get_int(self->stop); if (stop < 0) { stop += length; } stop = MIN(length, MAX(stop, 0)); } } else { // Negative step if (self->start == mp_const_none) { start = length - 1; } else { start = mp_obj_get_int(self->start); if (start < 0) { start += length; } start = MIN(length - 1, MAX(start, -1)); } if (self->stop == mp_const_none) { stop = -1; } else { stop = mp_obj_get_int(self->stop); if (stop < 0) { stop += length; } stop = MIN(length - 1, MAX(stop, -1)); } } result->start = start; result->stop = stop; result->step = step; } #endif #include #include #include "py/unicode.h" #include "py/objstr.h" #include "py/objlist.h" #include "py/runtime.h" #include "py/stackctrl.h" #if MICROPY_PY_BUILTINS_STR_OP_MODULO STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict); #endif STATIC mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); STATIC NORETURN void bad_implicit_conversion(mp_obj_t self_in); /******************************************************************************/ /* str */ void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, size_t str_len, bool is_bytes) { // this escapes characters, but it will be very slow to print (calling print many times) bool has_single_quote = false; bool has_double_quote = false; for (const byte *s = str_data, *top = str_data + str_len; !has_double_quote && s < top; s++) { if (*s == '\'') { has_single_quote = true; } else if (*s == '"') { has_double_quote = true; } } int quote_char = '\''; if (has_single_quote && !has_double_quote) { quote_char = '"'; } mp_printf(print, "%c", quote_char); for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { if (*s == quote_char) { mp_printf(print, "\\%c", quote_char); } else if (*s == '\\') { mp_print_str(print, "\\\\"); } else if (*s >= 0x20 && *s != 0x7f && (!is_bytes || *s < 0x80)) { // In strings, anything which is not ascii control character // is printed as is, this includes characters in range 0x80-0xff // (which can be non-Latin letters, etc.) mp_printf(print, "%c", *s); } else if (*s == '\n') { mp_print_str(print, "\\n"); } else if (*s == '\r') { mp_print_str(print, "\\r"); } else if (*s == '\t') { mp_print_str(print, "\\t"); } else { mp_printf(print, "\\x%02x", *s); } } mp_printf(print, "%c", quote_char); } #if MICROPY_PY_UJSON void mp_str_print_json(const mp_print_t *print, const byte *str_data, size_t str_len) { // for JSON spec, see http://www.ietf.org/rfc/rfc4627.txt // if we are given a valid utf8-encoded string, we will print it in a JSON-conforming way mp_print_str(print, "\""); for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { if (*s == '"' || *s == '\\') { mp_printf(print, "\\%c", *s); } else if (*s >= 32) { // this will handle normal and utf-8 encoded chars mp_printf(print, "%c", *s); } else if (*s == '\n') { mp_print_str(print, "\\n"); } else if (*s == '\r') { mp_print_str(print, "\\r"); } else if (*s == '\t') { mp_print_str(print, "\\t"); } else { // this will handle control chars mp_printf(print, "\\u%04x", *s); } } mp_print_str(print, "\""); } #endif STATIC void str_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { GET_STR_DATA_LEN(self_in, str_data, str_len); #if MICROPY_PY_UJSON if (kind == PRINT_JSON) { mp_str_print_json(print, str_data, str_len); return; } #endif #if !MICROPY_PY_BUILTINS_STR_UNICODE bool is_bytes = mp_obj_is_type(self_in, &mp_type_bytes); #else bool is_bytes = true; #endif if (kind == PRINT_RAW || (!MICROPY_PY_BUILTINS_STR_UNICODE && kind == PRINT_STR && !is_bytes)) { print->print_strn(print->data, (const char *)str_data, str_len); } else { if (is_bytes) { print->print_strn(print->data, "b", 1); } mp_str_print_quoted(print, str_data, str_len, is_bytes); } } mp_obj_t mp_obj_str_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { #if MICROPY_CPYTHON_COMPAT if (n_kw != 0) { mp_arg_error_unimpl_kw(); } #endif mp_arg_check_num(n_args, n_kw, 0, 3, false); switch (n_args) { case 0: return MP_OBJ_NEW_QSTR(MP_QSTR_); case 1: { vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 16, &print); mp_obj_print_helper(&print, args[0], PRINT_STR); return mp_obj_new_str_from_vstr(type, &vstr); } default: // 2 or 3 args // TODO: validate 2nd/3rd args if (mp_obj_is_type(args[0], &mp_type_bytes)) { GET_STR_DATA_LEN(args[0], str_data, str_len); GET_STR_HASH(args[0], str_hash); if (str_hash == 0) { str_hash = qstr_compute_hash(str_data, str_len); } #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK if (!utf8_check(str_data, str_len)) { mp_raise_msg(&mp_type_UnicodeError, NULL); } #endif // Check if a qstr with this data already exists qstr q = qstr_find_strn((const char *)str_data, str_len); if (q != MP_QSTRnull) { return MP_OBJ_NEW_QSTR(q); } mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(type, NULL, str_len)); o->data = str_data; o->hash = str_hash; return MP_OBJ_FROM_PTR(o); } else { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK if (!utf8_check(bufinfo.buf, bufinfo.len)) { mp_raise_msg(&mp_type_UnicodeError, NULL); } #endif return mp_obj_new_str(bufinfo.buf, bufinfo.len); } } } STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; #if MICROPY_CPYTHON_COMPAT if (n_kw != 0) { mp_arg_error_unimpl_kw(); } #else (void)n_kw; #endif if (n_args == 0) { return mp_const_empty_bytes; } if (mp_obj_is_type(args[0], &mp_type_bytes)) { return args[0]; } if (mp_obj_is_str(args[0])) { if (n_args < 2 || n_args > 3) { goto wrong_args; } GET_STR_DATA_LEN(args[0], str_data, str_len); GET_STR_HASH(args[0], str_hash); if (str_hash == 0) { str_hash = qstr_compute_hash(str_data, str_len); } mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(&mp_type_bytes, NULL, str_len)); o->data = str_data; o->hash = str_hash; return MP_OBJ_FROM_PTR(o); } if (n_args > 1) { goto wrong_args; } if (mp_obj_is_small_int(args[0])) { mp_int_t len = MP_OBJ_SMALL_INT_VALUE(args[0]); if (len < 0) { mp_raise_ValueError(NULL); } vstr_t vstr; vstr_init_len(&vstr, len); memset(vstr.buf, 0, len); return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } // check if argument has the buffer protocol mp_buffer_info_t bufinfo; if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) { return mp_obj_new_bytes(bufinfo.buf, bufinfo.len); } vstr_t vstr; // Try to create array of exact len if initializer len is known mp_obj_t len_in = mp_obj_len_maybe(args[0]); if (len_in == MP_OBJ_NULL) { vstr_init(&vstr, 16); } else { mp_int_t len = MP_OBJ_SMALL_INT_VALUE(len_in); vstr_init(&vstr, len); } mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(args[0], &iter_buf); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { mp_int_t val = mp_obj_get_int(item); #if MICROPY_FULL_CHECKS if (val < 0 || val > 255) { mp_raise_ValueError(MP_ERROR_TEXT("bytes value out of range")); } #endif vstr_add_byte(&vstr, val); } return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); wrong_args: mp_raise_TypeError(MP_ERROR_TEXT("wrong number of arguments")); } // like strstr but with specified length and allows \0 bytes // TODO replace with something more efficient/standard const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction) { if (hlen >= nlen) { size_t str_index, str_index_end; if (direction > 0) { str_index = 0; str_index_end = hlen - nlen; } else { str_index = hlen - nlen; str_index_end = 0; } for (;;) { if (memcmp(&haystack[str_index], needle, nlen) == 0) { // found return haystack + str_index; } if (str_index == str_index_end) { // not found break; } str_index += direction; } } return NULL; } // Note: this function is used to check if an object is a str or bytes, which // works because both those types use it as their binary_op method. Revisit // mp_obj_is_str_or_bytes if this fact changes. mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { // check for modulo if (op == MP_BINARY_OP_MODULO) { #if MICROPY_PY_BUILTINS_STR_OP_MODULO mp_obj_t *args = &rhs_in; size_t n_args = 1; mp_obj_t dict = MP_OBJ_NULL; if (mp_obj_is_type(rhs_in, &mp_type_tuple)) { // TODO: Support tuple subclasses? mp_obj_tuple_get(rhs_in, &n_args, &args); } else if (mp_obj_is_type(rhs_in, &mp_type_dict)) { dict = rhs_in; } return str_modulo_format(lhs_in, n_args, args, dict); #else return MP_OBJ_NULL; #endif } // from now on we need lhs type and data, so extract them const mp_obj_type_t *lhs_type = mp_obj_get_type(lhs_in); GET_STR_DATA_LEN(lhs_in, lhs_data, lhs_len); // check for multiply if (op == MP_BINARY_OP_MULTIPLY) { mp_int_t n; if (!mp_obj_get_int_maybe(rhs_in, &n)) { return MP_OBJ_NULL; // op not supported } if (n <= 0) { if (lhs_type == &mp_type_str) { return MP_OBJ_NEW_QSTR(MP_QSTR_); // empty str } else { return mp_const_empty_bytes; } } vstr_t vstr; vstr_init_len(&vstr, lhs_len * n); mp_seq_multiply(lhs_data, sizeof(*lhs_data), lhs_len, n, vstr.buf); return mp_obj_new_str_from_vstr(lhs_type, &vstr); } // From now on all operations allow: // - str with str // - bytes with bytes // - bytes with bytearray // - bytes with array.array // To do this efficiently we use the buffer protocol to extract the raw // data for the rhs, but only if the lhs is a bytes object. // // NOTE: CPython does not allow comparison between bytes ard array.array // (even if the array is of type 'b'), even though it allows addition of // such types. We are not compatible with this (we do allow comparison // of bytes with anything that has the buffer protocol). It would be // easy to "fix" this with a bit of extra logic below, but it costs code // size and execution time so we don't. const byte *rhs_data; size_t rhs_len; if (lhs_type == mp_obj_get_type(rhs_in)) { GET_STR_DATA_LEN(rhs_in, rhs_data_, rhs_len_); rhs_data = rhs_data_; rhs_len = rhs_len_; } else if (lhs_type == &mp_type_bytes) { mp_buffer_info_t bufinfo; if (!mp_get_buffer(rhs_in, &bufinfo, MP_BUFFER_READ)) { return MP_OBJ_NULL; // op not supported } rhs_data = bufinfo.buf; rhs_len = bufinfo.len; } else { // LHS is str and RHS has an incompatible type // (except if operation is EQUAL, but that's handled by mp_obj_equal) bad_implicit_conversion(rhs_in); } switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: { if (lhs_len == 0 && mp_obj_get_type(rhs_in) == lhs_type) { return rhs_in; } if (rhs_len == 0) { return lhs_in; } vstr_t vstr; vstr_init_len(&vstr, lhs_len + rhs_len); memcpy(vstr.buf, lhs_data, lhs_len); memcpy(vstr.buf + lhs_len, rhs_data, rhs_len); return mp_obj_new_str_from_vstr(lhs_type, &vstr); } case MP_BINARY_OP_CONTAINS: return mp_obj_new_bool(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL); // case MP_BINARY_OP_NOT_EQUAL: // This is never passed here case MP_BINARY_OP_EQUAL: // This will be passed only for bytes, str is dealt with in mp_obj_equal() case MP_BINARY_OP_LESS: case MP_BINARY_OP_LESS_EQUAL: case MP_BINARY_OP_MORE: case MP_BINARY_OP_MORE_EQUAL: return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_data, lhs_len, rhs_data, rhs_len)); default: return MP_OBJ_NULL; // op not supported } } #if !MICROPY_PY_BUILTINS_STR_UNICODE // objstrunicode defines own version const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, size_t self_len, mp_obj_t index, bool is_slice) { size_t index_val = mp_get_index(type, self_len, index, is_slice); return self_data + index_val; } #endif // This is used for both bytes and 8-bit strings. This is not used for unicode strings. STATIC mp_obj_t bytes_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { const mp_obj_type_t *type = mp_obj_get_type(self_in); GET_STR_DATA_LEN(self_in, self_data, self_len); if (value == MP_OBJ_SENTINEL) { // load #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; if (!mp_seq_get_fast_slice_indexes(self_len, index, &slice)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); } return mp_obj_new_str_of_type(type, self_data + slice.start, slice.stop - slice.start); } #endif size_t index_val = mp_get_index(type, self_len, index, false); // If we have unicode enabled the type will always be bytes, so take the short cut. if (MICROPY_PY_BUILTINS_STR_UNICODE || type == &mp_type_bytes) { return MP_OBJ_NEW_SMALL_INT(self_data[index_val]); } else { return mp_obj_new_str_via_qstr((char *)&self_data[index_val], 1); } } else { return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) { mp_check_self(mp_obj_is_str_or_bytes(self_in)); const mp_obj_type_t *self_type = mp_obj_get_type(self_in); // get separation string GET_STR_DATA_LEN(self_in, sep_str, sep_len); // process args size_t seq_len; mp_obj_t *seq_items; if (!mp_obj_is_type(arg, &mp_type_list) && !mp_obj_is_type(arg, &mp_type_tuple)) { // arg is not a list nor a tuple, try to convert it to a list // TODO: Try to optimize? arg = mp_type_list.make_new(&mp_type_list, 1, 0, &arg); } mp_obj_get_array(arg, &seq_len, &seq_items); // count required length size_t required_len = 0; for (size_t i = 0; i < seq_len; i++) { if (mp_obj_get_type(seq_items[i]) != self_type) { mp_raise_TypeError( MP_ERROR_TEXT("join expects a list of str/bytes objects consistent with self object")); } if (i > 0) { required_len += sep_len; } GET_STR_LEN(seq_items[i], l); required_len += l; } // make joined string vstr_t vstr; vstr_init_len(&vstr, required_len); byte *data = (byte *)vstr.buf; for (size_t i = 0; i < seq_len; i++) { if (i > 0) { memcpy(data, sep_str, sep_len); data += sep_len; } GET_STR_DATA_LEN(seq_items[i], s, l); memcpy(data, s, l); data += l; } // return joined string return mp_obj_new_str_from_vstr(self_type, &vstr); } MP_DEFINE_CONST_FUN_OBJ_2(str_join_obj, str_join); mp_obj_t mp_obj_str_split(size_t n_args, const mp_obj_t *args) { const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); mp_int_t splits = -1; mp_obj_t sep = mp_const_none; if (n_args > 1) { sep = args[1]; if (n_args > 2) { splits = mp_obj_get_int(args[2]); } } mp_obj_t res = mp_obj_new_list(0, NULL); GET_STR_DATA_LEN(args[0], s, len); const byte *top = s + len; if (sep == mp_const_none) { // sep not given, so separate on whitespace // Initial whitespace is not counted as split, so we pre-do it while (s < top && unichar_isspace(*s)) { s++; } while (s < top && splits != 0) { const byte *start = s; while (s < top && !unichar_isspace(*s)) { s++; } mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); if (s >= top) { break; } while (s < top && unichar_isspace(*s)) { s++; } if (splits > 0) { splits--; } } if (s < top) { mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, s, top - s)); } } else { // sep given if (mp_obj_get_type(sep) != self_type) { bad_implicit_conversion(sep); } size_t sep_len; const char *sep_str = mp_obj_str_get_data(sep, &sep_len); if (sep_len == 0) { mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } for (;;) { const byte *start = s; for (;;) { if (splits == 0 || s + sep_len > top) { s = top; break; } else if (memcmp(s, sep_str, sep_len) == 0) { break; } s++; } mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); if (s >= top) { break; } s += sep_len; if (splits > 0) { splits--; } } } return res; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_split_obj, 1, 3, mp_obj_str_split); #if MICROPY_PY_BUILTINS_STR_SPLITLINES STATIC mp_obj_t str_splitlines(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_keepends }; static const mp_arg_t allowed_args[] = { { MP_QSTR_keepends, MP_ARG_BOOL, {.u_bool = false} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const mp_obj_type_t *self_type = mp_obj_get_type(pos_args[0]); mp_obj_t res = mp_obj_new_list(0, NULL); GET_STR_DATA_LEN(pos_args[0], s, len); const byte *top = s + len; while (s < top) { const byte *start = s; size_t match = 0; while (s < top) { if (*s == '\n') { match = 1; break; } else if (*s == '\r') { if (s[1] == '\n') { match = 2; } else { match = 1; } break; } s++; } size_t sub_len = s - start; if (args[ARG_keepends].u_bool) { sub_len += match; } mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, sub_len)); s += match; } return res; } MP_DEFINE_CONST_FUN_OBJ_KW(str_splitlines_obj, 1, str_splitlines); #endif STATIC mp_obj_t str_rsplit(size_t n_args, const mp_obj_t *args) { if (n_args < 3) { // If we don't have split limit, it doesn't matter from which side // we split. return mp_obj_str_split(n_args, args); } const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); mp_obj_t sep = args[1]; GET_STR_DATA_LEN(args[0], s, len); mp_int_t splits = mp_obj_get_int(args[2]); if (splits < 0) { // Negative limit means no limit, so delegate to split(). return mp_obj_str_split(n_args, args); } mp_int_t org_splits = splits; // Preallocate list to the max expected # of elements, as we // will fill it from the end. mp_obj_list_t *res = MP_OBJ_TO_PTR(mp_obj_new_list(splits + 1, NULL)); mp_int_t idx = splits; if (sep == mp_const_none) { mp_raise_NotImplementedError(MP_ERROR_TEXT("rsplit(None,n)")); } else { size_t sep_len; const char *sep_str = mp_obj_str_get_data(sep, &sep_len); if (sep_len == 0) { mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } const byte *beg = s; const byte *last = s + len; for (;;) { s = last - sep_len; for (;;) { if (splits == 0 || s < beg) { break; } else if (memcmp(s, sep_str, sep_len) == 0) { break; } s--; } if (s < beg || splits == 0) { res->items[idx] = mp_obj_new_str_of_type(self_type, beg, last - beg); break; } res->items[idx--] = mp_obj_new_str_of_type(self_type, s + sep_len, last - s - sep_len); last = s; splits--; } if (idx != 0) { // We split less parts than split limit, now go cleanup surplus size_t used = org_splits + 1 - idx; memmove(res->items, &res->items[idx], used * sizeof(mp_obj_t)); mp_seq_clear(res->items, used, res->alloc, sizeof(*res->items)); res->len = used; } } return MP_OBJ_FROM_PTR(res); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rsplit_obj, 1, 3, str_rsplit); STATIC mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, bool is_index) { const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); mp_check_self(mp_obj_is_str_or_bytes(args[0])); // check argument type if (mp_obj_get_type(args[1]) != self_type) { bad_implicit_conversion(args[1]); } GET_STR_DATA_LEN(args[0], haystack, haystack_len); GET_STR_DATA_LEN(args[1], needle, needle_len); const byte *start = haystack; const byte *end = haystack + haystack_len; if (n_args >= 3 && args[2] != mp_const_none) { start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); } if (n_args >= 4 && args[3] != mp_const_none) { end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); } if (end < start) { goto out_error; } const byte *p = find_subbytes(start, end - start, needle, needle_len, direction); if (p == NULL) { out_error: // not found if (is_index) { mp_raise_ValueError(MP_ERROR_TEXT("substring not found")); } else { return MP_OBJ_NEW_SMALL_INT(-1); } } else { // found #if MICROPY_PY_BUILTINS_STR_UNICODE if (self_type == &mp_type_str) { return MP_OBJ_NEW_SMALL_INT(utf8_ptr_to_index(haystack, p)); } #endif return MP_OBJ_NEW_SMALL_INT(p - haystack); } } STATIC mp_obj_t str_find(size_t n_args, const mp_obj_t *args) { return str_finder(n_args, args, 1, false); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_find_obj, 2, 4, str_find); STATIC mp_obj_t str_rfind(size_t n_args, const mp_obj_t *args) { return str_finder(n_args, args, -1, false); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rfind_obj, 2, 4, str_rfind); STATIC mp_obj_t str_index(size_t n_args, const mp_obj_t *args) { return str_finder(n_args, args, 1, true); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_index_obj, 2, 4, str_index); STATIC mp_obj_t str_rindex(size_t n_args, const mp_obj_t *args) { return str_finder(n_args, args, -1, true); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rindex_obj, 2, 4, str_rindex); // TODO: (Much) more variety in args STATIC mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) { const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); GET_STR_DATA_LEN(args[0], str, str_len); size_t prefix_len; const char *prefix = mp_obj_str_get_data(args[1], &prefix_len); const byte *start = str; if (n_args > 2) { start = str_index_to_ptr(self_type, str, str_len, args[2], true); } if (prefix_len + (start - str) > str_len) { return mp_const_false; } return mp_obj_new_bool(memcmp(start, prefix, prefix_len) == 0); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 3, str_startswith); STATIC mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { GET_STR_DATA_LEN(args[0], str, str_len); size_t suffix_len; const char *suffix = mp_obj_str_get_data(args[1], &suffix_len); if (n_args > 2) { mp_raise_NotImplementedError(MP_ERROR_TEXT("start/end indices")); } if (suffix_len > str_len) { return mp_const_false; } return mp_obj_new_bool(memcmp(str + (str_len - suffix_len), suffix, suffix_len) == 0); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 3, str_endswith); enum { LSTRIP, RSTRIP, STRIP }; STATIC mp_obj_t str_uni_strip(int type, size_t n_args, const mp_obj_t *args) { mp_check_self(mp_obj_is_str_or_bytes(args[0])); const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); const byte *chars_to_del; uint chars_to_del_len; static const byte whitespace[] = " \t\n\r\v\f"; if (n_args == 1) { chars_to_del = whitespace; chars_to_del_len = sizeof(whitespace) - 1; } else { if (mp_obj_get_type(args[1]) != self_type) { bad_implicit_conversion(args[1]); } GET_STR_DATA_LEN(args[1], s, l); chars_to_del = s; chars_to_del_len = l; } GET_STR_DATA_LEN(args[0], orig_str, orig_str_len); size_t first_good_char_pos = 0; bool first_good_char_pos_set = false; size_t last_good_char_pos = 0; size_t i = 0; int delta = 1; if (type == RSTRIP) { i = orig_str_len - 1; delta = -1; } for (size_t len = orig_str_len; len > 0; len--) { if (find_subbytes(chars_to_del, chars_to_del_len, &orig_str[i], 1, 1) == NULL) { if (!first_good_char_pos_set) { first_good_char_pos_set = true; first_good_char_pos = i; if (type == LSTRIP) { last_good_char_pos = orig_str_len - 1; break; } else if (type == RSTRIP) { first_good_char_pos = 0; last_good_char_pos = i; break; } } last_good_char_pos = i; } i += delta; } if (!first_good_char_pos_set) { // string is all whitespace, return '' if (self_type == &mp_type_str) { return MP_OBJ_NEW_QSTR(MP_QSTR_); } else { return mp_const_empty_bytes; } } assert(last_good_char_pos >= first_good_char_pos); // +1 to accommodate the last character size_t stripped_len = last_good_char_pos - first_good_char_pos + 1; if (stripped_len == orig_str_len) { // If nothing was stripped, don't bother to dup original string // TODO: watch out for this case when we'll get to bytearray.strip() assert(first_good_char_pos == 0); return args[0]; } return mp_obj_new_str_of_type(self_type, orig_str + first_good_char_pos, stripped_len); } STATIC mp_obj_t str_strip(size_t n_args, const mp_obj_t *args) { return str_uni_strip(STRIP, n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_strip_obj, 1, 2, str_strip); STATIC mp_obj_t str_lstrip(size_t n_args, const mp_obj_t *args) { return str_uni_strip(LSTRIP, n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_lstrip_obj, 1, 2, str_lstrip); STATIC mp_obj_t str_rstrip(size_t n_args, const mp_obj_t *args) { return str_uni_strip(RSTRIP, n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rstrip_obj, 1, 2, str_rstrip); #if MICROPY_PY_BUILTINS_STR_CENTER STATIC mp_obj_t str_center(mp_obj_t str_in, mp_obj_t width_in) { GET_STR_DATA_LEN(str_in, str, str_len); mp_uint_t width = mp_obj_get_int(width_in); if (str_len >= width) { return str_in; } vstr_t vstr; vstr_init_len(&vstr, width); memset(vstr.buf, ' ', width); int left = (width - str_len) / 2; memcpy(vstr.buf + left, str, str_len); return mp_obj_new_str_from_vstr(mp_obj_get_type(str_in), &vstr); } MP_DEFINE_CONST_FUN_OBJ_2(str_center_obj, str_center); #endif // Takes an int arg, but only parses unsigned numbers, and only changes // *num if at least one digit was parsed. STATIC const char *str_to_int(const char *str, const char *top, int *num) { if (str < top && '0' <= *str && *str <= '9') { *num = 0; do { *num = *num * 10 + (*str - '0'); str++; } while (str < top && '0' <= *str && *str <= '9'); } return str; } STATIC bool isalignment(char ch) { return ch && strchr("<>=^", ch) != NULL; } STATIC bool istype(char ch) { return ch && strchr("bcdeEfFgGnosxX%", ch) != NULL; } STATIC bool arg_looks_integer(mp_obj_t arg) { return mp_obj_is_bool(arg) || mp_obj_is_int(arg); } STATIC bool arg_looks_numeric(mp_obj_t arg) { return arg_looks_integer(arg) #if MICROPY_PY_BUILTINS_FLOAT || mp_obj_is_float(arg) #endif ; } #if MICROPY_PY_BUILTINS_STR_OP_MODULO STATIC mp_obj_t arg_as_int(mp_obj_t arg) { #if MICROPY_PY_BUILTINS_FLOAT if (mp_obj_is_float(arg)) { return mp_obj_new_int_from_float(mp_obj_float_get(arg)); } #endif return arg; } #endif #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE STATIC NORETURN void terse_str_format_value_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); } #else // define to nothing to improve coverage #define terse_str_format_value_error() #endif STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *arg_i, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 16, &print); for (; str < top; str++) { if (*str == '}') { str++; if (str < top && *str == '}') { vstr_add_byte(&vstr, '}'); continue; } #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("single '}' encountered in format string")); #endif } if (*str != '{') { vstr_add_byte(&vstr, *str); continue; } str++; if (str < top && *str == '{') { vstr_add_byte(&vstr, '{'); continue; } // replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}" const char *field_name = NULL; const char *field_name_top = NULL; char conversion = '\0'; const char *format_spec = NULL; if (str < top && *str != '}' && *str != '!' && *str != ':') { field_name = (const char *)str; while (str < top && *str != '}' && *str != '!' && *str != ':') { ++str; } field_name_top = (const char *)str; } // conversion ::= "r" | "s" if (str < top && *str == '!') { str++; if (str < top && (*str == 'r' || *str == 's')) { conversion = *str++; } else { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_raise_ValueError(MP_ERROR_TEXT("bad conversion specifier")); #else if (str >= top) { mp_raise_ValueError( MP_ERROR_TEXT("end of format while looking for conversion specifier")); } else { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unknown conversion specifier %c"), *str); } #endif } } if (str < top && *str == ':') { str++; // {:} is the same as {}, which is the same as {!s} // This makes a difference when passing in a True or False // '{}'.format(True) returns 'True' // '{:d}'.format(True) returns '1' // So we treat {:} as {} and this later gets treated to be {!s} if (*str != '}') { format_spec = str; for (int nest = 1; str < top;) { if (*str == '{') { ++nest; } else if (*str == '}') { if (--nest == 0) { break; } } ++str; } } } if (str >= top) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("unmatched '{' in format")); #endif } if (*str != '}') { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("expected ':' after format specifier")); #endif } mp_obj_t arg = mp_const_none; if (field_name) { int index = 0; if (MP_LIKELY(unichar_isdigit(*field_name))) { if (*arg_i > 0) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( MP_ERROR_TEXT("can't switch from automatic field numbering to manual field specification")); #endif } field_name = str_to_int(field_name, field_name_top, &index); if ((uint)index >= n_args - 1) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range")); } arg = args[index + 1]; *arg_i = -1; } else { const char *lookup; for (lookup = field_name; lookup < field_name_top && *lookup != '.' && *lookup != '['; lookup++) {; } mp_obj_t field_q = mp_obj_new_str_via_qstr(field_name, lookup - field_name); // should it be via qstr? field_name = lookup; mp_map_elem_t *key_elem = mp_map_lookup(kwargs, field_q, MP_MAP_LOOKUP); if (key_elem == NULL) { mp_raise_type_arg(&mp_type_KeyError, field_q); } arg = key_elem->value; } if (field_name < field_name_top) { mp_raise_NotImplementedError(MP_ERROR_TEXT("attributes not supported yet")); } } else { if (*arg_i < 0) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( MP_ERROR_TEXT("can't switch from manual field specification to automatic field numbering")); #endif } if ((uint)*arg_i >= n_args - 1) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range")); } arg = args[(*arg_i) + 1]; (*arg_i)++; } if (!format_spec && !conversion) { conversion = 's'; } if (conversion) { mp_print_kind_t print_kind; if (conversion == 's') { print_kind = PRINT_STR; } else { assert(conversion == 'r'); print_kind = PRINT_REPR; } vstr_t arg_vstr; mp_print_t arg_print; vstr_init_print(&arg_vstr, 16, &arg_print); mp_obj_print_helper(&arg_print, arg, print_kind); arg = mp_obj_new_str_from_vstr(&mp_type_str, &arg_vstr); } char fill = '\0'; char align = '\0'; int width = -1; int precision = -1; char type = '\0'; int flags = 0; if (format_spec) { // The format specifier (from http://docs.python.org/2/library/string.html#formatspec) // // [[fill]align][sign][#][0][width][,][.precision][type] // fill ::= // align ::= "<" | ">" | "=" | "^" // sign ::= "+" | "-" | " " // width ::= integer // precision ::= integer // type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" // recursively call the formatter to format any nested specifiers MP_STACK_CHECK(); vstr_t format_spec_vstr = mp_obj_str_format_helper(format_spec, str, arg_i, n_args, args, kwargs); const char *s = vstr_null_terminated_str(&format_spec_vstr); const char *stop = s + format_spec_vstr.len; if (isalignment(*s)) { align = *s++; } else if (*s && isalignment(s[1])) { fill = *s++; align = *s++; } if (*s == '+' || *s == '-' || *s == ' ') { if (*s == '+') { flags |= PF_FLAG_SHOW_SIGN; } else if (*s == ' ') { flags |= PF_FLAG_SPACE_SIGN; } s++; } if (*s == '#') { flags |= PF_FLAG_SHOW_PREFIX; s++; } if (*s == '0') { if (!align && arg_looks_numeric(arg)) { align = '='; } if (!fill) { fill = '0'; } } s = str_to_int(s, stop, &width); if (*s == ',') { flags |= PF_FLAG_SHOW_COMMA; s++; } if (*s == '.') { s++; s = str_to_int(s, stop, &precision); } if (istype(*s)) { type = *s++; } if (*s) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("invalid format specifier")); #endif } vstr_clear(&format_spec_vstr); } if (!align) { if (arg_looks_numeric(arg)) { align = '>'; } else { align = '<'; } } if (!fill) { fill = ' '; } if (flags & (PF_FLAG_SHOW_SIGN | PF_FLAG_SPACE_SIGN)) { if (type == 's') { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("sign not allowed in string format specifier")); #endif } if (type == 'c') { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( MP_ERROR_TEXT("sign not allowed with integer format specifier 'c'")); #endif } } switch (align) { case '<': flags |= PF_FLAG_LEFT_ADJUST; break; case '=': flags |= PF_FLAG_PAD_AFTER_SIGN; break; case '^': flags |= PF_FLAG_CENTER_ADJUST; break; } if (arg_looks_integer(arg)) { switch (type) { case 'b': mp_print_mp_int(&print, arg, 2, 'a', flags, fill, width, 0); continue; case 'c': { char ch = mp_obj_get_int(arg); mp_print_strn(&print, &ch, 1, flags, fill, width); continue; } case '\0': // No explicit format type implies 'd' case 'n': // I don't think we support locales in uPy so use 'd' case 'd': mp_print_mp_int(&print, arg, 10, 'a', flags, fill, width, 0); continue; case 'o': if (flags & PF_FLAG_SHOW_PREFIX) { flags |= PF_FLAG_SHOW_OCTAL_LETTER; } mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, 0); continue; case 'X': case 'x': mp_print_mp_int(&print, arg, 16, type - ('X' - 'A'), flags, fill, width, 0); continue; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case '%': // The floating point formatters all work with anything that // looks like an integer break; default: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), type, mp_obj_get_type_str(arg)); #endif } } // NOTE: no else here. We need the e, f, g etc formats for integer // arguments (from above if) to take this if. if (arg_looks_numeric(arg)) { if (!type) { // Even though the docs say that an unspecified type is the same // as 'g', there is one subtle difference, when the exponent // is one less than the precision. // // '{:10.1}'.format(0.0) ==> '0e+00' // '{:10.1g}'.format(0.0) ==> '0' // // TODO: Figure out how to deal with this. // // A proper solution would involve adding a special flag // or something to format_float, and create a format_double // to deal with doubles. In order to fix this when using // sprintf, we'd need to use the e format and tweak the // returned result to strip trailing zeros like the g format // does. // // {:10.3} and {:10.2e} with 1.23e2 both produce 1.23e+02 // but with 1.e2 you get 1e+02 and 1.00e+02 // // Stripping the trailing 0's (like g) does would make the // e format give us the right format. // // CPython sources say: // Omitted type specifier. Behaves in the same way as repr(x) // and str(x) if no precision is given, else like 'g', but with // at least one digit after the decimal point. */ type = 'g'; } if (type == 'n') { type = 'g'; } switch (type) { #if MICROPY_PY_BUILTINS_FLOAT case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': mp_print_float(&print, mp_obj_get_float(arg), type, flags, fill, width, precision); break; case '%': flags |= PF_FLAG_ADD_PERCENT; #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT #define F100 100.0F #else #define F100 100.0 #endif mp_print_float(&print, mp_obj_get_float(arg) * F100, 'f', flags, fill, width, precision); #undef F100 break; #endif default: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), type, mp_obj_get_type_str(arg)); #endif } } else { // arg doesn't look like a number if (align == '=') { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( MP_ERROR_TEXT("'=' alignment not allowed in string format specifier")); #endif } switch (type) { case '\0': // no explicit format type implies 's' case 's': { size_t slen; const char *s = mp_obj_str_get_data(arg, &slen); if (precision < 0) { precision = slen; } if (slen > (size_t)precision) { slen = precision; } mp_print_strn(&print, s, slen, flags, fill, width); break; } default: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), type, mp_obj_get_type_str(arg)); #endif } } } return vstr; } mp_obj_t mp_obj_str_format(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { mp_check_self(mp_obj_is_str_or_bytes(args[0])); GET_STR_DATA_LEN(args[0], str, len); int arg_i = 0; vstr_t vstr = mp_obj_str_format_helper((const char *)str, (const char *)str + len, &arg_i, n_args, args, kwargs); return mp_obj_new_str_from_vstr(mp_obj_get_type(args[0]), &vstr); } MP_DEFINE_CONST_FUN_OBJ_KW(str_format_obj, 1, mp_obj_str_format); #if MICROPY_PY_BUILTINS_STR_OP_MODULO STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict) { mp_check_self(mp_obj_is_str_or_bytes(pattern)); GET_STR_DATA_LEN(pattern, str, len); #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_TERSE const byte *start_str = str; #endif bool is_bytes = mp_obj_is_type(pattern, &mp_type_bytes); size_t arg_i = 0; vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 16, &print); for (const byte *top = str + len; str < top; str++) { mp_obj_t arg = MP_OBJ_NULL; if (*str != '%') { vstr_add_byte(&vstr, *str); continue; } if (++str >= top) { goto incomplete_format; } if (*str == '%') { vstr_add_byte(&vstr, '%'); continue; } // Dictionary value lookup if (*str == '(') { if (dict == MP_OBJ_NULL) { mp_raise_TypeError(MP_ERROR_TEXT("format needs a dict")); } arg_i = 1; // we used up the single dict argument const byte *key = ++str; while (*str != ')') { if (str >= top) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("incomplete format key")); #endif } ++str; } mp_obj_t k_obj = mp_obj_new_str_via_qstr((const char *)key, str - key); arg = mp_obj_dict_get(dict, k_obj); str++; } int flags = 0; char fill = ' '; int alt = 0; while (str < top) { if (*str == '-') { flags |= PF_FLAG_LEFT_ADJUST; } else if (*str == '+') { flags |= PF_FLAG_SHOW_SIGN; } else if (*str == ' ') { flags |= PF_FLAG_SPACE_SIGN; } else if (*str == '#') { alt = PF_FLAG_SHOW_PREFIX; } else if (*str == '0') { flags |= PF_FLAG_PAD_AFTER_SIGN; fill = '0'; } else { break; } str++; } // parse width, if it exists int width = 0; if (str < top) { if (*str == '*') { if (arg_i >= n_args) { goto not_enough_args; } width = mp_obj_get_int(args[arg_i++]); str++; } else { str = (const byte *)str_to_int((const char *)str, (const char *)top, &width); } } int prec = -1; if (str < top && *str == '.') { if (++str < top) { if (*str == '*') { if (arg_i >= n_args) { goto not_enough_args; } prec = mp_obj_get_int(args[arg_i++]); str++; } else { prec = 0; str = (const byte *)str_to_int((const char *)str, (const char *)top, &prec); } } } if (str >= top) { incomplete_format: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("incomplete format")); #endif } // Tuple value lookup if (arg == MP_OBJ_NULL) { if (arg_i >= n_args) { not_enough_args: mp_raise_TypeError(MP_ERROR_TEXT("format string needs more arguments")); } arg = args[arg_i++]; } switch (*str) { case 'c': if (mp_obj_is_str(arg)) { size_t slen; const char *s = mp_obj_str_get_data(arg, &slen); if (slen != 1) { mp_raise_TypeError(MP_ERROR_TEXT("%c needs int or char")); } mp_print_strn(&print, s, 1, flags, ' ', width); } else if (arg_looks_integer(arg)) { char ch = mp_obj_get_int(arg); mp_print_strn(&print, &ch, 1, flags, ' ', width); } else { mp_raise_TypeError(MP_ERROR_TEXT("integer needed")); } break; case 'd': case 'i': case 'u': mp_print_mp_int(&print, arg_as_int(arg), 10, 'a', flags, fill, width, prec); break; #if MICROPY_PY_BUILTINS_FLOAT case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': mp_print_float(&print, mp_obj_get_float(arg), *str, flags, fill, width, prec); break; #endif case 'o': if (alt) { flags |= (PF_FLAG_SHOW_PREFIX | PF_FLAG_SHOW_OCTAL_LETTER); } mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, prec); break; case 'r': case 's': { vstr_t arg_vstr; mp_print_t arg_print; vstr_init_print(&arg_vstr, 16, &arg_print); mp_print_kind_t print_kind = (*str == 'r' ? PRINT_REPR : PRINT_STR); if (print_kind == PRINT_STR && is_bytes && mp_obj_is_type(arg, &mp_type_bytes)) { // If we have something like b"%s" % b"1", bytes arg should be // printed undecorated. print_kind = PRINT_RAW; } mp_obj_print_helper(&arg_print, arg, print_kind); uint vlen = arg_vstr.len; if (prec < 0) { prec = vlen; } if (vlen > (uint)prec) { vlen = prec; } mp_print_strn(&print, arg_vstr.buf, vlen, flags, ' ', width); vstr_clear(&arg_vstr); break; } case 'X': case 'x': mp_print_mp_int(&print, arg, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec); break; default: #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unsupported format character '%c' (0x%x) at index %d"), *str, *str, str - start_str); #endif } } if (arg_i != n_args) { mp_raise_TypeError(MP_ERROR_TEXT("format string didn't convert all arguments")); } return mp_obj_new_str_from_vstr(is_bytes ? &mp_type_bytes : &mp_type_str, &vstr); } #endif // The implementation is optimized, returning the original string if there's // nothing to replace. STATIC mp_obj_t str_replace(size_t n_args, const mp_obj_t *args) { mp_check_self(mp_obj_is_str_or_bytes(args[0])); mp_int_t max_rep = -1; if (n_args == 4) { max_rep = mp_obj_get_int(args[3]); if (max_rep == 0) { return args[0]; } else if (max_rep < 0) { max_rep = -1; } } // if max_rep is still -1 by this point we will need to do all possible replacements // check argument types const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); if (mp_obj_get_type(args[1]) != self_type) { bad_implicit_conversion(args[1]); } if (mp_obj_get_type(args[2]) != self_type) { bad_implicit_conversion(args[2]); } // extract string data GET_STR_DATA_LEN(args[0], str, str_len); GET_STR_DATA_LEN(args[1], old, old_len); GET_STR_DATA_LEN(args[2], new, new_len); // old won't exist in str if it's longer, so nothing to replace if (old_len > str_len) { return args[0]; } // data for the replaced string byte *data = NULL; vstr_t vstr; // do 2 passes over the string: // first pass computes the required length of the replaced string // second pass does the replacements for (;;) { size_t replaced_str_index = 0; size_t num_replacements_done = 0; const byte *old_occurrence; const byte *offset_ptr = str; size_t str_len_remain = str_len; if (old_len == 0) { // if old_str is empty, copy new_str to start of replaced string // copy the replacement string if (data != NULL) { memcpy(data, new, new_len); } replaced_str_index += new_len; num_replacements_done++; } while (num_replacements_done != (size_t)max_rep && str_len_remain > 0 && (old_occurrence = find_subbytes(offset_ptr, str_len_remain, old, old_len, 1)) != NULL) { if (old_len == 0) { old_occurrence += 1; } // copy from just after end of last occurrence of to-be-replaced string to right before start of next occurrence if (data != NULL) { memcpy(data + replaced_str_index, offset_ptr, old_occurrence - offset_ptr); } replaced_str_index += old_occurrence - offset_ptr; // copy the replacement string if (data != NULL) { memcpy(data + replaced_str_index, new, new_len); } replaced_str_index += new_len; offset_ptr = old_occurrence + old_len; str_len_remain = str + str_len - offset_ptr; num_replacements_done++; } // copy from just after end of last occurrence of to-be-replaced string to end of old string if (data != NULL) { memcpy(data + replaced_str_index, offset_ptr, str_len_remain); } replaced_str_index += str_len_remain; if (data == NULL) { // first pass if (num_replacements_done == 0) { // no substr found, return original string return args[0]; } else { // substr found, allocate new string vstr_init_len(&vstr, replaced_str_index); data = (byte *)vstr.buf; assert(data != NULL); } } else { // second pass, we are done break; } } return mp_obj_new_str_from_vstr(self_type, &vstr); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_replace_obj, 3, 4, str_replace); #if MICROPY_PY_BUILTINS_STR_COUNT STATIC mp_obj_t str_count(size_t n_args, const mp_obj_t *args) { const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); mp_check_self(mp_obj_is_str_or_bytes(args[0])); // check argument type if (mp_obj_get_type(args[1]) != self_type) { bad_implicit_conversion(args[1]); } GET_STR_DATA_LEN(args[0], haystack, haystack_len); GET_STR_DATA_LEN(args[1], needle, needle_len); const byte *start = haystack; const byte *end = haystack + haystack_len; if (n_args >= 3 && args[2] != mp_const_none) { start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); } if (n_args >= 4 && args[3] != mp_const_none) { end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); } // if needle_len is zero then we count each gap between characters as an occurrence if (needle_len == 0) { return MP_OBJ_NEW_SMALL_INT(utf8_charlen(start, end - start) + 1); } // count the occurrences mp_int_t num_occurrences = 0; for (const byte *haystack_ptr = start; haystack_ptr + needle_len <= end;) { if (memcmp(haystack_ptr, needle, needle_len) == 0) { num_occurrences++; haystack_ptr += needle_len; } else { haystack_ptr = utf8_next_char(haystack_ptr); } } return MP_OBJ_NEW_SMALL_INT(num_occurrences); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_count_obj, 2, 4, str_count); #endif #if MICROPY_PY_BUILTINS_STR_PARTITION STATIC mp_obj_t str_partitioner(mp_obj_t self_in, mp_obj_t arg, int direction) { mp_check_self(mp_obj_is_str_or_bytes(self_in)); const mp_obj_type_t *self_type = mp_obj_get_type(self_in); if (self_type != mp_obj_get_type(arg)) { bad_implicit_conversion(arg); } GET_STR_DATA_LEN(self_in, str, str_len); GET_STR_DATA_LEN(arg, sep, sep_len); if (sep_len == 0) { mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } mp_obj_t result[3]; if (self_type == &mp_type_str) { result[0] = MP_OBJ_NEW_QSTR(MP_QSTR_); result[1] = MP_OBJ_NEW_QSTR(MP_QSTR_); result[2] = MP_OBJ_NEW_QSTR(MP_QSTR_); } else { result[0] = mp_const_empty_bytes; result[1] = mp_const_empty_bytes; result[2] = mp_const_empty_bytes; } if (direction > 0) { result[0] = self_in; } else { result[2] = self_in; } const byte *position_ptr = find_subbytes(str, str_len, sep, sep_len, direction); if (position_ptr != NULL) { size_t position = position_ptr - str; result[0] = mp_obj_new_str_of_type(self_type, str, position); result[1] = arg; result[2] = mp_obj_new_str_of_type(self_type, str + position + sep_len, str_len - position - sep_len); } return mp_obj_new_tuple(3, result); } STATIC mp_obj_t str_partition(mp_obj_t self_in, mp_obj_t arg) { return str_partitioner(self_in, arg, 1); } MP_DEFINE_CONST_FUN_OBJ_2(str_partition_obj, str_partition); STATIC mp_obj_t str_rpartition(mp_obj_t self_in, mp_obj_t arg) { return str_partitioner(self_in, arg, -1); } MP_DEFINE_CONST_FUN_OBJ_2(str_rpartition_obj, str_rpartition); #endif // Supposedly not too critical operations, so optimize for code size STATIC mp_obj_t str_caseconv(unichar (*op)(unichar), mp_obj_t self_in) { GET_STR_DATA_LEN(self_in, self_data, self_len); vstr_t vstr; vstr_init_len(&vstr, self_len); byte *data = (byte *)vstr.buf; for (size_t i = 0; i < self_len; i++) { *data++ = op(*self_data++); } return mp_obj_new_str_from_vstr(mp_obj_get_type(self_in), &vstr); } STATIC mp_obj_t str_lower(mp_obj_t self_in) { return str_caseconv(unichar_tolower, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_lower_obj, str_lower); STATIC mp_obj_t str_upper(mp_obj_t self_in) { return str_caseconv(unichar_toupper, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_upper_obj, str_upper); STATIC mp_obj_t str_uni_istype(bool (*f)(unichar), mp_obj_t self_in) { GET_STR_DATA_LEN(self_in, self_data, self_len); if (self_len == 0) { return mp_const_false; // default to False for empty str } if (f != unichar_isupper && f != unichar_islower) { for (size_t i = 0; i < self_len; i++) { if (!f(*self_data++)) { return mp_const_false; } } } else { bool contains_alpha = false; for (size_t i = 0; i < self_len; i++) { // only check alphanumeric characters if (unichar_isalpha(*self_data++)) { contains_alpha = true; if (!f(*(self_data - 1))) { // -1 because we already incremented above return mp_const_false; } } } if (!contains_alpha) { return mp_const_false; } } return mp_const_true; } STATIC mp_obj_t str_isspace(mp_obj_t self_in) { return str_uni_istype(unichar_isspace, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_isspace_obj, str_isspace); STATIC mp_obj_t str_isalpha(mp_obj_t self_in) { return str_uni_istype(unichar_isalpha, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_isalpha_obj, str_isalpha); STATIC mp_obj_t str_isdigit(mp_obj_t self_in) { return str_uni_istype(unichar_isdigit, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_isdigit_obj, str_isdigit); STATIC mp_obj_t str_isupper(mp_obj_t self_in) { return str_uni_istype(unichar_isupper, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_isupper_obj, str_isupper); STATIC mp_obj_t str_islower(mp_obj_t self_in) { return str_uni_istype(unichar_islower, self_in); } MP_DEFINE_CONST_FUN_OBJ_1(str_islower_obj, str_islower); #if MICROPY_CPYTHON_COMPAT // These methods are superfluous in the presence of str() and bytes() // constructors. // TODO: should accept kwargs too STATIC mp_obj_t bytes_decode(size_t n_args, const mp_obj_t *args) { mp_obj_t new_args[2]; if (n_args == 1) { new_args[0] = args[0]; new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); args = new_args; n_args++; } return mp_obj_str_make_new(&mp_type_str, n_args, 0, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bytes_decode_obj, 1, 3, bytes_decode); // TODO: should accept kwargs too STATIC mp_obj_t str_encode(size_t n_args, const mp_obj_t *args) { mp_obj_t new_args[2]; if (n_args == 1) { new_args[0] = args[0]; new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); args = new_args; n_args++; } return bytes_make_new(NULL, n_args, 0, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_encode_obj, 1, 3, str_encode); #endif mp_int_t mp_obj_str_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { if (flags == MP_BUFFER_READ) { GET_STR_DATA_LEN(self_in, str_data, str_len); bufinfo->buf = (void *)str_data; bufinfo->len = str_len; bufinfo->typecode = 'B'; // bytes should be unsigned, so should unicode byte-access return 0; } else { // can't write to a string return 1; } } STATIC const mp_rom_map_elem_t str8_locals_dict_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, #if !MICROPY_PY_BUILTINS_STR_UNICODE // If we have separate unicode type, then here we have methods only // for bytes type, and it should not have encode() methods. Otherwise, // we have non-compliant-but-practical bytestring type, which shares // method table with bytes, so they both have encode() and decode() // methods (which should do type checking at runtime). { MP_ROM_QSTR(MP_QSTR_encode), MP_ROM_PTR(&str_encode_obj) }, #endif #endif { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&str_find_obj) }, { MP_ROM_QSTR(MP_QSTR_rfind), MP_ROM_PTR(&str_rfind_obj) }, { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&str_index_obj) }, { MP_ROM_QSTR(MP_QSTR_rindex), MP_ROM_PTR(&str_rindex_obj) }, { MP_ROM_QSTR(MP_QSTR_join), MP_ROM_PTR(&str_join_obj) }, { MP_ROM_QSTR(MP_QSTR_split), MP_ROM_PTR(&str_split_obj) }, #if MICROPY_PY_BUILTINS_STR_SPLITLINES { MP_ROM_QSTR(MP_QSTR_splitlines), MP_ROM_PTR(&str_splitlines_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_rsplit), MP_ROM_PTR(&str_rsplit_obj) }, { MP_ROM_QSTR(MP_QSTR_startswith), MP_ROM_PTR(&str_startswith_obj) }, { MP_ROM_QSTR(MP_QSTR_endswith), MP_ROM_PTR(&str_endswith_obj) }, { MP_ROM_QSTR(MP_QSTR_strip), MP_ROM_PTR(&str_strip_obj) }, { MP_ROM_QSTR(MP_QSTR_lstrip), MP_ROM_PTR(&str_lstrip_obj) }, { MP_ROM_QSTR(MP_QSTR_rstrip), MP_ROM_PTR(&str_rstrip_obj) }, { MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&str_format_obj) }, { MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&str_replace_obj) }, #if MICROPY_PY_BUILTINS_STR_COUNT { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&str_count_obj) }, #endif #if MICROPY_PY_BUILTINS_STR_PARTITION { MP_ROM_QSTR(MP_QSTR_partition), MP_ROM_PTR(&str_partition_obj) }, { MP_ROM_QSTR(MP_QSTR_rpartition), MP_ROM_PTR(&str_rpartition_obj) }, #endif #if MICROPY_PY_BUILTINS_STR_CENTER { MP_ROM_QSTR(MP_QSTR_center), MP_ROM_PTR(&str_center_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_lower), MP_ROM_PTR(&str_lower_obj) }, { MP_ROM_QSTR(MP_QSTR_upper), MP_ROM_PTR(&str_upper_obj) }, { MP_ROM_QSTR(MP_QSTR_isspace), MP_ROM_PTR(&str_isspace_obj) }, { MP_ROM_QSTR(MP_QSTR_isalpha), MP_ROM_PTR(&str_isalpha_obj) }, { MP_ROM_QSTR(MP_QSTR_isdigit), MP_ROM_PTR(&str_isdigit_obj) }, { MP_ROM_QSTR(MP_QSTR_isupper), MP_ROM_PTR(&str_isupper_obj) }, { MP_ROM_QSTR(MP_QSTR_islower), MP_ROM_PTR(&str_islower_obj) }, }; STATIC MP_DEFINE_CONST_DICT(str8_locals_dict, str8_locals_dict_table); #if !MICROPY_PY_BUILTINS_STR_UNICODE STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); const mp_obj_type_t mp_type_str = { { &mp_type_type }, .name = MP_QSTR_str, .print = str_print, .make_new = mp_obj_str_make_new, .binary_op = mp_obj_str_binary_op, .subscr = bytes_subscr, .getiter = mp_obj_new_str_iterator, .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, }; #endif // Reuses most of methods from str const mp_obj_type_t mp_type_bytes = { { &mp_type_type }, .name = MP_QSTR_bytes, .print = str_print, .make_new = bytes_make_new, .binary_op = mp_obj_str_binary_op, .subscr = bytes_subscr, .getiter = mp_obj_new_bytes_iterator, .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, }; // The zero-length bytes object, with data that includes a null-terminating byte const mp_obj_str_t mp_const_empty_bytes_obj = {{&mp_type_bytes}, 0, 0, (const byte *)""}; // Create a str/bytes object using the given data. New memory is allocated and // the data is copied across. This function should only be used if the type is bytes, // or if the type is str and the string data is known to be not interned. mp_obj_t mp_obj_new_str_copy(const mp_obj_type_t *type, const byte *data, size_t len) { mp_obj_str_t *o = m_new_obj(mp_obj_str_t); o->base.type = type; o->len = len; if (data) { o->hash = qstr_compute_hash(data, len); byte *p = m_new(byte, len + 1); o->data = p; memcpy(p, data, len * sizeof(byte)); p[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings } return MP_OBJ_FROM_PTR(o); } // Create a str/bytes object using the given data. If the type is str and the string // data is already interned, then a qstr object is returned. Otherwise new memory is // allocated for the object and the data is copied across. mp_obj_t mp_obj_new_str_of_type(const mp_obj_type_t *type, const byte *data, size_t len) { if (type == &mp_type_str) { return mp_obj_new_str((const char *)data, len); } else { return mp_obj_new_bytes(data, len); } } // Create a str using a qstr to store the data; may use existing or new qstr. mp_obj_t mp_obj_new_str_via_qstr(const char *data, size_t len) { return MP_OBJ_NEW_QSTR(qstr_from_strn(data, len)); } // Create a str/bytes object from the given vstr. The vstr buffer is resized to // the exact length required and then reused for the str/bytes object. The vstr // is cleared and can safely be passed to vstr_free if it was heap allocated. mp_obj_t mp_obj_new_str_from_vstr(const mp_obj_type_t *type, vstr_t *vstr) { // if not a bytes object, look if a qstr with this data already exists if (type == &mp_type_str) { qstr q = qstr_find_strn(vstr->buf, vstr->len); if (q != MP_QSTRnull) { vstr_clear(vstr); vstr->alloc = 0; return MP_OBJ_NEW_QSTR(q); } } // make a new str/bytes object mp_obj_str_t *o = m_new_obj(mp_obj_str_t); o->base.type = type; o->len = vstr->len; o->hash = qstr_compute_hash((byte *)vstr->buf, vstr->len); if (vstr->len + 1 == vstr->alloc) { o->data = (byte *)vstr->buf; } else { o->data = (byte *)m_renew(char, vstr->buf, vstr->alloc, vstr->len + 1); } ((byte *)o->data)[o->len] = '\0'; // add null byte vstr->buf = NULL; vstr->alloc = 0; return MP_OBJ_FROM_PTR(o); } mp_obj_t mp_obj_new_str(const char *data, size_t len) { qstr q = qstr_find_strn(data, len); if (q != MP_QSTRnull) { // qstr with this data already exists return MP_OBJ_NEW_QSTR(q); } else { // no existing qstr, don't make one return mp_obj_new_str_copy(&mp_type_str, (const byte *)data, len); } } mp_obj_t mp_obj_str_intern(mp_obj_t str) { GET_STR_DATA_LEN(str, data, len); return mp_obj_new_str_via_qstr((const char *)data, len); } mp_obj_t mp_obj_str_intern_checked(mp_obj_t obj) { size_t len; const char *data = mp_obj_str_get_data(obj, &len); return mp_obj_new_str_via_qstr((const char *)data, len); } mp_obj_t mp_obj_new_bytes(const byte *data, size_t len) { return mp_obj_new_str_copy(&mp_type_bytes, data, len); } bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { if (mp_obj_is_qstr(s1) && mp_obj_is_qstr(s2)) { return s1 == s2; } else { GET_STR_HASH(s1, h1); GET_STR_HASH(s2, h2); // If any of hashes is 0, it means it's not valid if (h1 != 0 && h2 != 0 && h1 != h2) { return false; } GET_STR_DATA_LEN(s1, d1, l1); GET_STR_DATA_LEN(s2, d2, l2); if (l1 != l2) { return false; } return memcmp(d1, d2, l1) == 0; } } STATIC NORETURN void bad_implicit_conversion(mp_obj_t self_in) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); #else const qstr src_name = mp_obj_get_type(self_in)->name; mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert '%q' object to %q implicitly"), src_name, src_name == MP_QSTR_str ? MP_QSTR_bytes : MP_QSTR_str); #endif } // use this if you will anyway convert the string to a qstr // will be more efficient for the case where it's already a qstr qstr mp_obj_str_get_qstr(mp_obj_t self_in) { if (mp_obj_is_qstr(self_in)) { return MP_OBJ_QSTR_VALUE(self_in); } else if (mp_obj_is_type(self_in, &mp_type_str)) { mp_obj_str_t *self = MP_OBJ_TO_PTR(self_in); return qstr_from_strn((char *)self->data, self->len); } else { bad_implicit_conversion(self_in); } } // only use this function if you need the str data to be zero terminated // at the moment all strings are zero terminated to help with C ASCIIZ compatibility const char *mp_obj_str_get_str(mp_obj_t self_in) { if (mp_obj_is_str_or_bytes(self_in)) { GET_STR_DATA_LEN(self_in, s, l); (void)l; // len unused return (const char *)s; } else { bad_implicit_conversion(self_in); } } const char *mp_obj_str_get_data(mp_obj_t self_in, size_t *len) { if (mp_obj_is_str_or_bytes(self_in)) { GET_STR_DATA_LEN(self_in, s, l); *len = l; return (const char *)s; } else { bad_implicit_conversion(self_in); } } #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D const byte *mp_obj_str_get_data_no_check(mp_obj_t self_in, size_t *len) { if (mp_obj_is_qstr(self_in)) { return qstr_data(MP_OBJ_QSTR_VALUE(self_in), len); } else { *len = ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->len; return ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->data; } } #endif /******************************************************************************/ /* str iterator */ typedef struct _mp_obj_str8_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t str; size_t cur; } mp_obj_str8_it_t; #if !MICROPY_PY_BUILTINS_STR_UNICODE STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) { mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); GET_STR_DATA_LEN(self->str, str, len); if (self->cur < len) { mp_obj_t o_out = mp_obj_new_str_via_qstr((const char *)str + self->cur, 1); self->cur += 1; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = str_it_iternext; o->str = str; o->cur = 0; return MP_OBJ_FROM_PTR(o); } #endif STATIC mp_obj_t bytes_it_iternext(mp_obj_t self_in) { mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); GET_STR_DATA_LEN(self->str, str, len); if (self->cur < len) { mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(str[self->cur]); self->cur += 1; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = bytes_it_iternext; o->str = str; o->cur = 0; return MP_OBJ_FROM_PTR(o); } #include #include #include "py/objstr.h" #include "py/objlist.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_STR_UNICODE STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); /******************************************************************************/ /* str */ STATIC void uni_print_quoted(const mp_print_t *print, const byte *str_data, uint str_len) { // this escapes characters, but it will be very slow to print (calling print many times) bool has_single_quote = false; bool has_double_quote = false; for (const byte *s = str_data, *top = str_data + str_len; !has_double_quote && s < top; s++) { if (*s == '\'') { has_single_quote = true; } else if (*s == '"') { has_double_quote = true; } } unichar quote_char = '\''; if (has_single_quote && !has_double_quote) { quote_char = '"'; } mp_printf(print, "%c", quote_char); const byte *s = str_data, *top = str_data + str_len; while (s < top) { unichar ch; ch = utf8_get_char(s); s = utf8_next_char(s); if (ch == quote_char) { mp_printf(print, "\\%c", quote_char); } else if (ch == '\\') { mp_print_str(print, "\\\\"); } else if (32 <= ch && ch <= 126) { mp_printf(print, "%c", ch); } else if (ch == '\n') { mp_print_str(print, "\\n"); } else if (ch == '\r') { mp_print_str(print, "\\r"); } else if (ch == '\t') { mp_print_str(print, "\\t"); } else if (ch < 0x100) { mp_printf(print, "\\x%02x", ch); } else if (ch < 0x10000) { mp_printf(print, "\\u%04x", ch); } else { mp_printf(print, "\\U%08x", ch); } } mp_printf(print, "%c", quote_char); } STATIC void uni_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { GET_STR_DATA_LEN(self_in, str_data, str_len); #if MICROPY_PY_UJSON if (kind == PRINT_JSON) { mp_str_print_json(print, str_data, str_len); return; } #endif if (kind == PRINT_STR) { print->print_strn(print->data, (const char *)str_data, str_len); } else { uni_print_quoted(print, str_data, str_len); } } STATIC mp_obj_t uni_unary_op(mp_unary_op_t op, mp_obj_t self_in) { GET_STR_DATA_LEN(self_in, str_data, str_len); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(str_len != 0); case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(utf8_charlen(str_data, str_len)); default: return MP_OBJ_NULL; // op not supported } } // Convert an index into a pointer to its lead byte. Out of bounds indexing will raise IndexError or // be capped to the first/last character of the string, depending on is_slice. const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, size_t self_len, mp_obj_t index, bool is_slice) { // All str functions also handle bytes objects, and they call str_index_to_ptr(), // so it must handle bytes. if (type == &mp_type_bytes) { // Taken from objstr.c:str_index_to_ptr() size_t index_val = mp_get_index(type, self_len, index, is_slice); return self_data + index_val; } mp_int_t i; // Copied from mp_get_index; I don't want bounds checking, just give me // the integer as-is. (I can't bounds-check without scanning the whole // string; an out-of-bounds index will be caught in the loops below.) if (mp_obj_is_small_int(index)) { i = MP_OBJ_SMALL_INT_VALUE(index); } else if (!mp_obj_get_int_maybe(index, &i)) { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index)); } const byte *s, *top = self_data + self_len; if (i < 0) { // Negative indexing is performed by counting from the end of the string. for (s = top - 1; i; --s) { if (s < self_data) { if (is_slice) { return self_data; } mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); } if (!UTF8_IS_CONT(*s)) { ++i; } } ++s; } else { // Positive indexing, correspondingly, counts from the start of the string. // It's assumed that negative indexing will generally be used with small // absolute values (eg str[-1], not str[-1000000]), which means it'll be // more efficient this way. s = self_data; while (1) { // First check out-of-bounds if (s >= top) { if (is_slice) { return top; } mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); } // Then check completion if (i-- == 0) { break; } // Then skip UTF-8 char ++s; while (UTF8_IS_CONT(*s)) { ++s; } } } return s; } STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { const mp_obj_type_t *type = mp_obj_get_type(self_in); assert(type == &mp_type_str); GET_STR_DATA_LEN(self_in, self_data, self_len); if (value == MP_OBJ_SENTINEL) { // load #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_obj_t ostart, ostop, ostep; mp_obj_slice_t *slice = MP_OBJ_TO_PTR(index); ostart = slice->start; ostop = slice->stop; ostep = slice->step; if (ostep != mp_const_none && ostep != MP_OBJ_NEW_SMALL_INT(1)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); } const byte *pstart, *pstop; if (ostart != mp_const_none) { pstart = str_index_to_ptr(type, self_data, self_len, ostart, true); } else { pstart = self_data; } if (ostop != mp_const_none) { // pstop will point just after the stop character. This depends on // the \0 at the end of the string. pstop = str_index_to_ptr(type, self_data, self_len, ostop, true); } else { pstop = self_data + self_len; } if (pstop < pstart) { return MP_OBJ_NEW_QSTR(MP_QSTR_); } return mp_obj_new_str_of_type(type, (const byte *)pstart, pstop - pstart); } #endif const byte *s = str_index_to_ptr(type, self_data, self_len, index, false); int len = 1; if (UTF8_IS_NONASCII(*s)) { // Count the number of 1 bits (after the first) for (char mask = 0x40; *s & mask; mask >>= 1) { ++len; } } return mp_obj_new_str_via_qstr((const char *)s, len); // This will create a one-character string } else { return MP_OBJ_NULL; // op not supported } } STATIC const mp_rom_map_elem_t struni_locals_dict_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_encode), MP_ROM_PTR(&str_encode_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&str_find_obj) }, { MP_ROM_QSTR(MP_QSTR_rfind), MP_ROM_PTR(&str_rfind_obj) }, { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&str_index_obj) }, { MP_ROM_QSTR(MP_QSTR_rindex), MP_ROM_PTR(&str_rindex_obj) }, { MP_ROM_QSTR(MP_QSTR_join), MP_ROM_PTR(&str_join_obj) }, { MP_ROM_QSTR(MP_QSTR_split), MP_ROM_PTR(&str_split_obj) }, #if MICROPY_PY_BUILTINS_STR_SPLITLINES { MP_ROM_QSTR(MP_QSTR_splitlines), MP_ROM_PTR(&str_splitlines_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_rsplit), MP_ROM_PTR(&str_rsplit_obj) }, { MP_ROM_QSTR(MP_QSTR_startswith), MP_ROM_PTR(&str_startswith_obj) }, { MP_ROM_QSTR(MP_QSTR_endswith), MP_ROM_PTR(&str_endswith_obj) }, { MP_ROM_QSTR(MP_QSTR_strip), MP_ROM_PTR(&str_strip_obj) }, { MP_ROM_QSTR(MP_QSTR_lstrip), MP_ROM_PTR(&str_lstrip_obj) }, { MP_ROM_QSTR(MP_QSTR_rstrip), MP_ROM_PTR(&str_rstrip_obj) }, { MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&str_format_obj) }, { MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&str_replace_obj) }, #if MICROPY_PY_BUILTINS_STR_COUNT { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&str_count_obj) }, #endif #if MICROPY_PY_BUILTINS_STR_PARTITION { MP_ROM_QSTR(MP_QSTR_partition), MP_ROM_PTR(&str_partition_obj) }, { MP_ROM_QSTR(MP_QSTR_rpartition), MP_ROM_PTR(&str_rpartition_obj) }, #endif #if MICROPY_PY_BUILTINS_STR_CENTER { MP_ROM_QSTR(MP_QSTR_center), MP_ROM_PTR(&str_center_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_lower), MP_ROM_PTR(&str_lower_obj) }, { MP_ROM_QSTR(MP_QSTR_upper), MP_ROM_PTR(&str_upper_obj) }, { MP_ROM_QSTR(MP_QSTR_isspace), MP_ROM_PTR(&str_isspace_obj) }, { MP_ROM_QSTR(MP_QSTR_isalpha), MP_ROM_PTR(&str_isalpha_obj) }, { MP_ROM_QSTR(MP_QSTR_isdigit), MP_ROM_PTR(&str_isdigit_obj) }, { MP_ROM_QSTR(MP_QSTR_isupper), MP_ROM_PTR(&str_isupper_obj) }, { MP_ROM_QSTR(MP_QSTR_islower), MP_ROM_PTR(&str_islower_obj) }, }; STATIC MP_DEFINE_CONST_DICT(struni_locals_dict, struni_locals_dict_table); const mp_obj_type_t mp_type_str = { { &mp_type_type }, .name = MP_QSTR_str, .print = uni_print, .make_new = mp_obj_str_make_new, .unary_op = uni_unary_op, .binary_op = mp_obj_str_binary_op, .subscr = str_subscr, .getiter = mp_obj_new_str_iterator, .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, .locals_dict = (mp_obj_dict_t *)&struni_locals_dict, }; /******************************************************************************/ /* str iterator */ typedef struct _mp_obj_str_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t str; size_t cur; } mp_obj_str_it_t; STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) { mp_obj_str_it_t *self = MP_OBJ_TO_PTR(self_in); GET_STR_DATA_LEN(self->str, str, len); if (self->cur < len) { const byte *cur = str + self->cur; const byte *end = utf8_next_char(str + self->cur); mp_obj_t o_out = mp_obj_new_str_via_qstr((const char *)cur, end - cur); self->cur += end - cur; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_str_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_str_it_t *o = (mp_obj_str_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = str_it_iternext; o->str = str; o->cur = 0; return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_PY_BUILTINS_STR_UNICODE #ifndef __IPU__ #include #endif #include #include "py/objstr.h" #include "py/objstringio.h" #include "py/runtime.h" #include "py/stream.h" #if MICROPY_PY_IO #if MICROPY_CPYTHON_COMPAT STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { if (o->vstr == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file")); } } #else #define check_stringio_is_open(o) #endif STATIC void stringio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, self->base.type == &mp_type_stringio ? "" : "", self); } STATIC mp_uint_t stringio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); check_stringio_is_open(o); if (o->vstr->len <= o->pos) { // read to EOF, or seeked to EOF or beyond return 0; } mp_uint_t remaining = o->vstr->len - o->pos; if (size > remaining) { size = remaining; } memcpy(buf, o->vstr->buf + o->pos, size); o->pos += size; return size; } STATIC void stringio_copy_on_write(mp_obj_stringio_t *o) { const void *buf = o->vstr->buf; o->vstr->buf = m_new(char, o->vstr->len); o->vstr->fixed_buf = false; o->ref_obj = MP_OBJ_NULL; memcpy(o->vstr->buf, buf, o->vstr->len); } STATIC mp_uint_t stringio_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); check_stringio_is_open(o); if (o->vstr->fixed_buf) { stringio_copy_on_write(o); } mp_uint_t new_pos = o->pos + size; if (new_pos < size) { // Writing bytes will overflow o->pos beyond limit of mp_uint_t. *errcode = MP_EFBIG; return MP_STREAM_ERROR; } mp_uint_t org_len = o->vstr->len; if (new_pos > o->vstr->alloc) { // Take all what's already allocated... o->vstr->len = o->vstr->alloc; // ... and add more vstr_add_len(o->vstr, new_pos - o->vstr->alloc); } // If there was a seek past EOF, clear the hole if (o->pos > org_len) { memset(o->vstr->buf + org_len, 0, o->pos - org_len); } memcpy(o->vstr->buf + o->pos, buf, size); o->pos = new_pos; if (new_pos > o->vstr->len) { o->vstr->len = new_pos; } return size; } STATIC mp_uint_t stringio_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); switch (request) { case MP_STREAM_SEEK: { struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; mp_uint_t ref = 0; switch (s->whence) { case MP_SEEK_CUR: ref = o->pos; break; case MP_SEEK_END: ref = o->vstr->len; break; } mp_uint_t new_pos = ref + s->offset; // For MP_SEEK_SET, offset is unsigned if (s->whence != MP_SEEK_SET && s->offset < 0) { if (new_pos > ref) { // Negative offset from SEEK_CUR or SEEK_END went past 0. // CPython sets position to 0, POSIX returns an EINVAL error new_pos = 0; } } else if (new_pos < ref) { // positive offset went beyond the limit of mp_uint_t *errcode = MP_EINVAL; // replace with MP_EOVERFLOW when defined return MP_STREAM_ERROR; } s->offset = o->pos = new_pos; return 0; } case MP_STREAM_FLUSH: return 0; case MP_STREAM_CLOSE: #if MICROPY_CPYTHON_COMPAT vstr_free(o->vstr); o->vstr = NULL; #else vstr_clear(o->vstr); o->vstr->alloc = 0; o->vstr->len = 0; o->pos = 0; #endif return 0; default: *errcode = MP_EINVAL; return MP_STREAM_ERROR; } } #define STREAM_TO_CONTENT_TYPE(o) (((o)->base.type == &mp_type_stringio) ? &mp_type_str : &mp_type_bytes) STATIC mp_obj_t stringio_getvalue(mp_obj_t self_in) { mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); check_stringio_is_open(self); // TODO: Try to avoid copying string return mp_obj_new_str_of_type(STREAM_TO_CONTENT_TYPE(self), (byte *)self->vstr->buf, self->vstr->len); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(stringio_getvalue_obj, stringio_getvalue); STATIC mp_obj_t stringio___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; return mp_stream_close(args[0]); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(stringio___exit___obj, 4, 4, stringio___exit__); STATIC mp_obj_stringio_t *stringio_new(const mp_obj_type_t *type) { mp_obj_stringio_t *o = m_new_obj(mp_obj_stringio_t); o->base.type = type; o->pos = 0; o->ref_obj = MP_OBJ_NULL; return o; } STATIC mp_obj_t stringio_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)n_kw; // TODO check n_kw==0 mp_uint_t sz = 16; bool initdata = false; mp_buffer_info_t bufinfo; mp_obj_stringio_t *o = stringio_new(type_in); if (n_args > 0) { if (mp_obj_is_int(args[0])) { sz = mp_obj_get_int(args[0]); } else { mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); if (mp_obj_is_str_or_bytes(args[0])) { o->vstr = m_new_obj(vstr_t); vstr_init_fixed_buf(o->vstr, bufinfo.len, bufinfo.buf); o->vstr->len = bufinfo.len; o->ref_obj = args[0]; return MP_OBJ_FROM_PTR(o); } sz = bufinfo.len; initdata = true; } } o->vstr = vstr_new(sz); if (initdata) { stringio_write(MP_OBJ_FROM_PTR(o), bufinfo.buf, bufinfo.len, NULL); // Cur ptr is always at the beginning of buffer at the construction o->pos = 0; } return MP_OBJ_FROM_PTR(o); } STATIC const mp_rom_map_elem_t stringio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, { MP_ROM_QSTR(MP_QSTR_getvalue), MP_ROM_PTR(&stringio_getvalue_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&stringio___exit___obj) }, }; STATIC MP_DEFINE_CONST_DICT(stringio_locals_dict, stringio_locals_dict_table); STATIC const mp_stream_p_t stringio_stream_p = { .read = stringio_read, .write = stringio_write, .ioctl = stringio_ioctl, .is_text = true, }; const mp_obj_type_t mp_type_stringio = { { &mp_type_type }, .name = MP_QSTR_StringIO, .print = stringio_print, .make_new = stringio_make_new, .getiter = mp_identity_getiter, .iternext = mp_stream_unbuffered_iter, .protocol = &stringio_stream_p, .locals_dict = (mp_obj_dict_t *)&stringio_locals_dict, }; #if MICROPY_PY_IO_BYTESIO STATIC const mp_stream_p_t bytesio_stream_p = { .read = stringio_read, .write = stringio_write, .ioctl = stringio_ioctl, }; const mp_obj_type_t mp_type_bytesio = { { &mp_type_type }, .name = MP_QSTR_BytesIO, .print = stringio_print, .make_new = stringio_make_new, .getiter = mp_identity_getiter, .iternext = mp_stream_unbuffered_iter, .protocol = &bytesio_stream_p, .locals_dict = (mp_obj_dict_t *)&stringio_locals_dict, }; #endif #endif #include #include #include "py/objtuple.h" #include "py/runtime.h" // type check is done on getiter method to allow tuple, namedtuple, attrtuple #define mp_obj_is_tuple_compatible(o) (mp_obj_get_type(o)->getiter == mp_obj_tuple_getiter) /******************************************************************************/ /* tuple */ void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_tuple_t *o = MP_OBJ_TO_PTR(o_in); const char *item_separator = ", "; if (MICROPY_PY_UJSON && kind == PRINT_JSON) { mp_print_str(print, "["); #if MICROPY_PY_UJSON_SEPARATORS item_separator = MP_PRINT_GET_EXT(print)->item_separator; #endif } else { mp_print_str(print, "("); kind = PRINT_REPR; } for (size_t i = 0; i < o->len; i++) { if (i > 0) { mp_print_str(print, item_separator); } mp_obj_print_helper(print, o->items[i], kind); } if (MICROPY_PY_UJSON && kind == PRINT_JSON) { mp_print_str(print, "]"); } else { if (o->len == 1) { mp_print_str(print, ","); } mp_print_str(print, ")"); } } STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 1, false); switch (n_args) { case 0: // return a empty tuple return mp_const_empty_tuple; case 1: default: { // 1 argument, an iterable from which we make a new tuple if (mp_obj_is_type(args[0], &mp_type_tuple)) { return args[0]; } // TODO optimise for cases where we know the length of the iterator size_t alloc = 4; size_t len = 0; mp_obj_t *items = m_new(mp_obj_t, alloc); mp_obj_t iterable = mp_getiter(args[0], NULL); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { if (len >= alloc) { items = m_renew(mp_obj_t, items, alloc, alloc * 2); alloc *= 2; } items[len++] = item; } mp_obj_t tuple = mp_obj_new_tuple(len, items); m_del(mp_obj_t, items, alloc); return tuple; } } } // Don't pass MP_BINARY_OP_NOT_EQUAL here STATIC mp_obj_t tuple_cmp_helper(mp_uint_t op, mp_obj_t self_in, mp_obj_t another_in) { mp_check_self(mp_obj_is_tuple_compatible(self_in)); const mp_obj_type_t *another_type = mp_obj_get_type(another_in); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); if (another_type->getiter != mp_obj_tuple_getiter) { // Slow path for user subclasses another_in = mp_obj_cast_to_native_base(another_in, MP_OBJ_FROM_PTR(&mp_type_tuple)); if (another_in == MP_OBJ_NULL) { return MP_OBJ_NULL; } } mp_obj_tuple_t *another = MP_OBJ_TO_PTR(another_in); return mp_obj_new_bool(mp_seq_cmp_objs(op, self->items, self->len, another->items, another->len)); } mp_obj_t mp_obj_tuple_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->len != 0); case MP_UNARY_OP_HASH: { // start hash with pointer to empty tuple, to make it fairly unique mp_int_t hash = (mp_int_t)mp_const_empty_tuple; for (size_t i = 0; i < self->len; i++) { hash += MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, self->items[i])); } return MP_OBJ_NEW_SMALL_INT(hash); } case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(self->len); default: return MP_OBJ_NULL; // op not supported } } mp_obj_t mp_obj_tuple_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { mp_obj_tuple_t *o = MP_OBJ_TO_PTR(lhs); switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: { if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(rhs)), MP_OBJ_FROM_PTR(&mp_type_tuple))) { return MP_OBJ_NULL; // op not supported } mp_obj_tuple_t *p = MP_OBJ_TO_PTR(rhs); mp_obj_tuple_t *s = MP_OBJ_TO_PTR(mp_obj_new_tuple(o->len + p->len, NULL)); mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); return MP_OBJ_FROM_PTR(s); } case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: { mp_int_t n; if (!mp_obj_get_int_maybe(rhs, &n)) { return MP_OBJ_NULL; // op not supported } if (n <= 0) { return mp_const_empty_tuple; } mp_obj_tuple_t *s = MP_OBJ_TO_PTR(mp_obj_new_tuple(o->len * n, NULL)); mp_seq_multiply(o->items, sizeof(*o->items), o->len, n, s->items); return MP_OBJ_FROM_PTR(s); } case MP_BINARY_OP_EQUAL: case MP_BINARY_OP_LESS: case MP_BINARY_OP_LESS_EQUAL: case MP_BINARY_OP_MORE: case MP_BINARY_OP_MORE_EQUAL: return tuple_cmp_helper(op, lhs, rhs); default: return MP_OBJ_NULL; // op not supported } } mp_obj_t mp_obj_tuple_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (value == MP_OBJ_SENTINEL) { // load mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); } mp_obj_tuple_t *res = MP_OBJ_TO_PTR(mp_obj_new_tuple(slice.stop - slice.start, NULL)); mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); return MP_OBJ_FROM_PTR(res); } #endif size_t index_value = mp_get_index(self->base.type, self->len, index, false); return self->items[index_value]; } else { return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t tuple_count(mp_obj_t self_in, mp_obj_t value) { mp_check_self(mp_obj_is_type(self_in, &mp_type_tuple)); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); return mp_seq_count_obj(self->items, self->len, value); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(tuple_count_obj, tuple_count); STATIC mp_obj_t tuple_index(size_t n_args, const mp_obj_t *args) { mp_check_self(mp_obj_is_type(args[0], &mp_type_tuple)); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(args[0]); return mp_seq_index_obj(self->items, self->len, n_args, args); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(tuple_index_obj, 2, 4, tuple_index); STATIC const mp_rom_map_elem_t tuple_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&tuple_count_obj) }, { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&tuple_index_obj) }, }; STATIC MP_DEFINE_CONST_DICT(tuple_locals_dict, tuple_locals_dict_table); const mp_obj_type_t mp_type_tuple = { { &mp_type_type }, .name = MP_QSTR_tuple, .print = mp_obj_tuple_print, .make_new = mp_obj_tuple_make_new, .unary_op = mp_obj_tuple_unary_op, .binary_op = mp_obj_tuple_binary_op, .subscr = mp_obj_tuple_subscr, .getiter = mp_obj_tuple_getiter, .locals_dict = (mp_obj_dict_t *)&tuple_locals_dict, }; // the zero-length tuple const mp_obj_tuple_t mp_const_empty_tuple_obj = {{&mp_type_tuple}, 0}; mp_obj_t mp_obj_new_tuple(size_t n, const mp_obj_t *items) { if (n == 0) { return mp_const_empty_tuple; } mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n); o->base.type = &mp_type_tuple; o->len = n; if (items) { for (size_t i = 0; i < n; i++) { o->items[i] = items[i]; } } return MP_OBJ_FROM_PTR(o); } void mp_obj_tuple_get(mp_obj_t self_in, size_t *len, mp_obj_t **items) { assert(mp_obj_is_tuple_compatible(self_in)); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); *len = self->len; *items = &self->items[0]; } void mp_obj_tuple_del(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_tuple)); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); m_del_var(mp_obj_tuple_t, mp_obj_t, self->len, self); } /******************************************************************************/ /* tuple iterator */ typedef struct _mp_obj_tuple_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_tuple_t *tuple; size_t cur; } mp_obj_tuple_it_t; STATIC mp_obj_t tuple_it_iternext(mp_obj_t self_in) { mp_obj_tuple_it_t *self = MP_OBJ_TO_PTR(self_in); if (self->cur < self->tuple->len) { mp_obj_t o_out = self->tuple->items[self->cur]; self->cur += 1; return o_out; } else { return MP_OBJ_STOP_ITERATION; } } mp_obj_t mp_obj_tuple_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_tuple_it_t) <= sizeof(mp_obj_iter_buf_t)); mp_obj_tuple_it_t *o = (mp_obj_tuple_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; o->iternext = tuple_it_iternext; o->tuple = MP_OBJ_TO_PTR(o_in); o->cur = 0; return MP_OBJ_FROM_PTR(o); } /* * Copyright (c) 2013-2018 Damien P. George * Copyright (c) 2014-2018 Paul Sokolovsky */ #ifndef __IPU__ #include #endif #include #include #include #include "py/objtype.h" #include "py/runtime.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif #define ENABLE_SPECIAL_ACCESSORS \ (MICROPY_PY_DESCRIPTORS || MICROPY_PY_DELATTR_SETATTR || MICROPY_PY_BUILTINS_PROPERTY) STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); /******************************************************************************/ // instance object STATIC int instance_count_native_bases(const mp_obj_type_t *type, const mp_obj_type_t **last_native_base) { int count = 0; for (;;) { if (type == &mp_type_object) { // Not a "real" type, end search here. return count; } else if (mp_obj_is_native_type(type)) { // Native types don't have parents (at least not from our perspective) so end. *last_native_base = type; return count + 1; } else if (type->parent == NULL) { // No parents so end search here. return count; #if MICROPY_MULTIPLE_INHERITANCE } else if (((mp_obj_base_t *)type->parent)->type == &mp_type_tuple) { // Multiple parents, search through them all recursively. const mp_obj_tuple_t *parent_tuple = type->parent; const mp_obj_t *item = parent_tuple->items; const mp_obj_t *top = item + parent_tuple->len; for (; item < top; ++item) { assert(mp_obj_is_type(*item, &mp_type_type)); const mp_obj_type_t *bt = (const mp_obj_type_t *)MP_OBJ_TO_PTR(*item); count += instance_count_native_bases(bt, last_native_base); } return count; #endif } else { // A single parent, use iteration to continue the search. type = type->parent; } } } // This wrapper function is allows a subclass of a native type to call the // __init__() method (corresponding to type->make_new) of the native type. STATIC mp_obj_t native_base_init_wrapper(size_t n_args, const mp_obj_t *args) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(args[0]); const mp_obj_type_t *native_base = NULL; instance_count_native_bases(self->base.type, &native_base); self->subobj[0] = native_base->make_new(native_base, n_args - 1, 0, args + 1); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(native_base_init_wrapper_obj, 1, MP_OBJ_FUN_ARGS_MAX, native_base_init_wrapper); #if !MICROPY_CPYTHON_COMPAT STATIC #endif mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *class, const mp_obj_type_t **native_base) { size_t num_native_bases = instance_count_native_bases(class, native_base); assert(num_native_bases < 2); mp_obj_instance_t *o = m_new_obj_var(mp_obj_instance_t, mp_obj_t, num_native_bases); o->base.type = class; mp_map_init(&o->members, 0); // Initialise the native base-class slot (should be 1 at most) with a valid // object. It doesn't matter which object, so long as it can be uniquely // distinguished from a native class that is initialised. if (num_native_bases != 0) { o->subobj[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); } return o; } // TODO // This implements depth-first left-to-right MRO, which is not compliant with Python3 MRO // http://python-history.blogspot.com/2010/06/method-resolution-order.html // https://www.python.org/download/releases/2.3/mro/ // // will keep lookup->dest[0]'s value (should be MP_OBJ_NULL on invocation) if attribute // is not found // will set lookup->dest[0] to MP_OBJ_SENTINEL if special method was found in a native // type base via slot id (as specified by lookup->meth_offset). As there can be only one // native base, it's known that it applies to instance->subobj[0]. In most cases, we also // don't need to know which type it was - because instance->subobj[0] is of that type. // The only exception is when object is not yet constructed, then we need to know base // native type to construct its instance->subobj[0] from. But this case is handled via // instance_count_native_bases(), which returns a native base which it saw. struct class_lookup_data { mp_obj_instance_t *obj; qstr attr; size_t meth_offset; mp_obj_t *dest; bool is_type; }; STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_type_t *type) { assert(lookup->dest[0] == MP_OBJ_NULL); assert(lookup->dest[1] == MP_OBJ_NULL); for (;;) { DEBUG_printf("mp_obj_class_lookup: Looking up %s in %s\n", qstr_str(lookup->attr), qstr_str(type->name)); // Optimize special method lookup for native types // This avoids extra method_name => slot lookup. On the other hand, // this should not be applied to class types, as will result in extra // lookup either. if (lookup->meth_offset != 0 && mp_obj_is_native_type(type)) { if (*(void **)((char *)type + lookup->meth_offset) != NULL) { DEBUG_printf("mp_obj_class_lookup: Matched special meth slot (off=%d) for %s\n", lookup->meth_offset, qstr_str(lookup->attr)); lookup->dest[0] = MP_OBJ_SENTINEL; return; } } if (type->locals_dict != NULL) { // search locals_dict (the set of methods/attributes) assert(mp_obj_is_dict_or_ordereddict(MP_OBJ_FROM_PTR(type->locals_dict))); // MicroPython restriction, for now mp_map_t *locals_map = &type->locals_dict->map; mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(lookup->attr), MP_MAP_LOOKUP); if (elem != NULL) { if (lookup->is_type) { // If we look up a class method, we need to return original type for which we // do a lookup, not a (base) type in which we found the class method. const mp_obj_type_t *org_type = (const mp_obj_type_t *)lookup->obj; mp_convert_member_lookup(MP_OBJ_NULL, org_type, elem->value, lookup->dest); } else { mp_obj_instance_t *obj = lookup->obj; mp_obj_t obj_obj; if (obj != NULL && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { // If we're dealing with native base class, then it applies to native sub-object obj_obj = obj->subobj[0]; } else { obj_obj = MP_OBJ_FROM_PTR(obj); } mp_convert_member_lookup(obj_obj, type, elem->value, lookup->dest); } #if DEBUG_PRINT DEBUG_printf("mp_obj_class_lookup: Returning: "); mp_obj_print_helper(MICROPY_DEBUG_PRINTER, lookup->dest[0], PRINT_REPR); if (lookup->dest[1] != MP_OBJ_NULL) { // Don't try to repr() lookup->dest[1], as we can be called recursively DEBUG_printf(" <%s @%p>", mp_obj_get_type_str(lookup->dest[1]), MP_OBJ_TO_PTR(lookup->dest[1])); } DEBUG_printf("\n"); #endif return; } } // Previous code block takes care about attributes defined in .locals_dict, // but some attributes of native types may be handled using .load_attr method, // so make sure we try to lookup those too. if (lookup->obj != NULL && !lookup->is_type && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { mp_load_method_maybe(lookup->obj->subobj[0], lookup->attr, lookup->dest); if (lookup->dest[0] != MP_OBJ_NULL) { return; } } // attribute not found, keep searching base classes if (type->parent == NULL) { DEBUG_printf("mp_obj_class_lookup: No more parents\n"); return; #if MICROPY_MULTIPLE_INHERITANCE } else if (((mp_obj_base_t *)type->parent)->type == &mp_type_tuple) { const mp_obj_tuple_t *parent_tuple = type->parent; const mp_obj_t *item = parent_tuple->items; const mp_obj_t *top = item + parent_tuple->len - 1; for (; item < top; ++item) { assert(mp_obj_is_type(*item, &mp_type_type)); mp_obj_type_t *bt = (mp_obj_type_t *)MP_OBJ_TO_PTR(*item); if (bt == &mp_type_object) { // Not a "real" type continue; } mp_obj_class_lookup(lookup, bt); if (lookup->dest[0] != MP_OBJ_NULL) { return; } } // search last base (simple tail recursion elimination) assert(mp_obj_is_type(*item, &mp_type_type)); type = (mp_obj_type_t *)MP_OBJ_TO_PTR(*item); #endif } else { type = type->parent; } if (type == &mp_type_object) { // Not a "real" type return; } } } STATIC void instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); qstr meth = (kind == PRINT_STR) ? MP_QSTR___str__ : MP_QSTR___repr__; mp_obj_t member[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = self, .attr = meth, .meth_offset = offsetof(mp_obj_type_t, print), .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_NULL && kind == PRINT_STR) { // If there's no __str__, fall back to __repr__ lookup.attr = MP_QSTR___repr__; lookup.meth_offset = 0; mp_obj_class_lookup(&lookup, self->base.type); } if (member[0] == MP_OBJ_SENTINEL) { // Handle Exception subclasses specially if (mp_obj_is_native_exception_instance(self->subobj[0])) { if (kind != PRINT_STR) { mp_print_str(print, qstr_str(self->base.type->name)); } mp_obj_print_helper(print, self->subobj[0], kind | PRINT_EXC_SUBCLASS); } else { mp_obj_print_helper(print, self->subobj[0], kind); } return; } if (member[0] != MP_OBJ_NULL) { mp_obj_t r = mp_call_function_1(member[0], self_in); mp_obj_print_helper(print, r, PRINT_STR); return; } // TODO: CPython prints fully-qualified type name mp_printf(print, "<%s object at %p>", mp_obj_get_type_str(self_in), self); } mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_instance_type(self)); // look for __new__ function mp_obj_t init_fn[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = NULL, .attr = MP_QSTR___new__, .meth_offset = offsetof(mp_obj_type_t, make_new), .dest = init_fn, .is_type = false, }; mp_obj_class_lookup(&lookup, self); const mp_obj_type_t *native_base = NULL; mp_obj_instance_t *o; if (init_fn[0] == MP_OBJ_NULL || init_fn[0] == MP_OBJ_SENTINEL) { // Either there is no __new__() method defined or there is a native // constructor. In both cases create a blank instance. o = mp_obj_new_instance(self, &native_base); // Since type->make_new() implements both __new__() and __init__() in // one go, of which the latter may be overridden by the Python subclass, // we defer (see the end of this function) the call of the native // constructor to give a chance for the Python __init__() method to call // said native constructor. } else { // Call Python class __new__ function with all args to create an instance mp_obj_t new_ret; if (n_args == 0 && n_kw == 0) { mp_obj_t args2[1] = {MP_OBJ_FROM_PTR(self)}; new_ret = mp_call_function_n_kw(init_fn[0], 1, 0, args2); } else { mp_obj_t *args2 = m_new(mp_obj_t, 1 + n_args + 2 * n_kw); args2[0] = MP_OBJ_FROM_PTR(self); memcpy(args2 + 1, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); new_ret = mp_call_function_n_kw(init_fn[0], n_args + 1, n_kw, args2); m_del(mp_obj_t, args2, 1 + n_args + 2 * n_kw); } // https://docs.python.org/3.4/reference/datamodel.html#object.__new__ // "If __new__() does not return an instance of cls, then the new // instance's __init__() method will not be invoked." if (mp_obj_get_type(new_ret) != self) { return new_ret; } // The instance returned by __new__() becomes the new object o = MP_OBJ_TO_PTR(new_ret); } // now call Python class __init__ function with all args // This method has a chance to call super().__init__() to construct a // possible native base class. init_fn[0] = init_fn[1] = MP_OBJ_NULL; lookup.obj = o; lookup.attr = MP_QSTR___init__; lookup.meth_offset = 0; mp_obj_class_lookup(&lookup, self); if (init_fn[0] != MP_OBJ_NULL) { mp_obj_t init_ret; if (n_args == 0 && n_kw == 0) { init_ret = mp_call_method_n_kw(0, 0, init_fn); } else { mp_obj_t *args2 = m_new(mp_obj_t, 2 + n_args + 2 * n_kw); args2[0] = init_fn[0]; args2[1] = init_fn[1]; memcpy(args2 + 2, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); init_ret = mp_call_method_n_kw(n_args, n_kw, args2); m_del(mp_obj_t, args2, 2 + n_args + 2 * n_kw); } if (init_ret != mp_const_none) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("__init__() should return None")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("__init__() should return None, not '%s'"), mp_obj_get_type_str(init_ret)); #endif } } // If the type had a native base that was not explicitly initialised // (constructed) by the Python __init__() method then construct it now. if (native_base != NULL && o->subobj[0] == MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj)) { o->subobj[0] = native_base->make_new(native_base, n_args, n_kw, args); } return MP_OBJ_FROM_PTR(o); } // Qstrs for special methods are guaranteed to have a small value, so we use byte // type to represent them. const byte mp_unary_op_method_name[MP_UNARY_OP_NUM_RUNTIME] = { [MP_UNARY_OP_BOOL] = MP_QSTR___bool__, [MP_UNARY_OP_LEN] = MP_QSTR___len__, [MP_UNARY_OP_HASH] = MP_QSTR___hash__, [MP_UNARY_OP_INT] = MP_QSTR___int__, #if MICROPY_PY_ALL_SPECIAL_METHODS [MP_UNARY_OP_POSITIVE] = MP_QSTR___pos__, [MP_UNARY_OP_NEGATIVE] = MP_QSTR___neg__, [MP_UNARY_OP_INVERT] = MP_QSTR___invert__, [MP_UNARY_OP_ABS] = MP_QSTR___abs__, #endif #if MICROPY_PY_SYS_GETSIZEOF [MP_UNARY_OP_SIZEOF] = MP_QSTR___sizeof__, #endif }; STATIC mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_SYS_GETSIZEOF if (MP_UNLIKELY(op == MP_UNARY_OP_SIZEOF)) { // TODO: This doesn't count inherited objects (self->subobj) const mp_obj_type_t *native_base; size_t num_native_bases = instance_count_native_bases(mp_obj_get_type(self_in), &native_base); size_t sz = sizeof(*self) + sizeof(*self->subobj) * num_native_bases + sizeof(*self->members.table) * self->members.alloc; return MP_OBJ_NEW_SMALL_INT(sz); } #endif qstr op_name = mp_unary_op_method_name[op]; /* Still try to lookup native slot if (op_name == 0) { return MP_OBJ_NULL; } */ mp_obj_t member[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = self, .attr = op_name, .meth_offset = offsetof(mp_obj_type_t, unary_op), .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_SENTINEL) { return mp_unary_op(op, self->subobj[0]); } else if (member[0] != MP_OBJ_NULL) { mp_obj_t val = mp_call_function_1(member[0], self_in); switch (op) { case MP_UNARY_OP_HASH: // __hash__ must return a small int val = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int_truncated(val)); break; case MP_UNARY_OP_INT: // Must return int if (!mp_obj_is_int(val)) { mp_raise_TypeError(NULL); } break; default: // No need to do anything ; } return val; } else { if (op == MP_UNARY_OP_HASH) { lookup.attr = MP_QSTR___eq__; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_NULL) { // https://docs.python.org/3/reference/datamodel.html#object.__hash__ // "User-defined classes have __eq__() and __hash__() methods by default; // with them, all objects compare unequal (except with themselves) and // x.__hash__() returns an appropriate value such that x == y implies // both that x is y and hash(x) == hash(y)." return MP_OBJ_NEW_SMALL_INT((mp_uint_t)self_in); } // "A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. // When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError" } return MP_OBJ_NULL; // op not supported } } // Binary-op enum values not listed here will have the default value of 0 in the // table, corresponding to MP_QSTRnull, and are therefore unsupported (a lookup will // fail). They can be added at the expense of code size for the qstr. // Qstrs for special methods are guaranteed to have a small value, so we use byte // type to represent them. const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = { [MP_BINARY_OP_LESS] = MP_QSTR___lt__, [MP_BINARY_OP_MORE] = MP_QSTR___gt__, [MP_BINARY_OP_EQUAL] = MP_QSTR___eq__, [MP_BINARY_OP_LESS_EQUAL] = MP_QSTR___le__, [MP_BINARY_OP_MORE_EQUAL] = MP_QSTR___ge__, [MP_BINARY_OP_NOT_EQUAL] = MP_QSTR___ne__, [MP_BINARY_OP_CONTAINS] = MP_QSTR___contains__, // If an inplace method is not found a normal method will be used as a fallback [MP_BINARY_OP_INPLACE_ADD] = MP_QSTR___iadd__, [MP_BINARY_OP_INPLACE_SUBTRACT] = MP_QSTR___isub__, #if MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS [MP_BINARY_OP_INPLACE_MULTIPLY] = MP_QSTR___imul__, [MP_BINARY_OP_INPLACE_MAT_MULTIPLY] = MP_QSTR___imatmul__, [MP_BINARY_OP_INPLACE_FLOOR_DIVIDE] = MP_QSTR___ifloordiv__, [MP_BINARY_OP_INPLACE_TRUE_DIVIDE] = MP_QSTR___itruediv__, [MP_BINARY_OP_INPLACE_MODULO] = MP_QSTR___imod__, [MP_BINARY_OP_INPLACE_POWER] = MP_QSTR___ipow__, [MP_BINARY_OP_INPLACE_OR] = MP_QSTR___ior__, [MP_BINARY_OP_INPLACE_XOR] = MP_QSTR___ixor__, [MP_BINARY_OP_INPLACE_AND] = MP_QSTR___iand__, [MP_BINARY_OP_INPLACE_LSHIFT] = MP_QSTR___ilshift__, [MP_BINARY_OP_INPLACE_RSHIFT] = MP_QSTR___irshift__, #endif [MP_BINARY_OP_ADD] = MP_QSTR___add__, [MP_BINARY_OP_SUBTRACT] = MP_QSTR___sub__, #if MICROPY_PY_ALL_SPECIAL_METHODS [MP_BINARY_OP_MULTIPLY] = MP_QSTR___mul__, [MP_BINARY_OP_MAT_MULTIPLY] = MP_QSTR___matmul__, [MP_BINARY_OP_FLOOR_DIVIDE] = MP_QSTR___floordiv__, [MP_BINARY_OP_TRUE_DIVIDE] = MP_QSTR___truediv__, [MP_BINARY_OP_MODULO] = MP_QSTR___mod__, [MP_BINARY_OP_DIVMOD] = MP_QSTR___divmod__, [MP_BINARY_OP_POWER] = MP_QSTR___pow__, [MP_BINARY_OP_OR] = MP_QSTR___or__, [MP_BINARY_OP_XOR] = MP_QSTR___xor__, [MP_BINARY_OP_AND] = MP_QSTR___and__, [MP_BINARY_OP_LSHIFT] = MP_QSTR___lshift__, [MP_BINARY_OP_RSHIFT] = MP_QSTR___rshift__, #endif #if MICROPY_PY_REVERSE_SPECIAL_METHODS [MP_BINARY_OP_REVERSE_ADD] = MP_QSTR___radd__, [MP_BINARY_OP_REVERSE_SUBTRACT] = MP_QSTR___rsub__, #if MICROPY_PY_ALL_SPECIAL_METHODS [MP_BINARY_OP_REVERSE_MULTIPLY] = MP_QSTR___rmul__, [MP_BINARY_OP_REVERSE_MAT_MULTIPLY] = MP_QSTR___rmatmul__, [MP_BINARY_OP_REVERSE_FLOOR_DIVIDE] = MP_QSTR___rfloordiv__, [MP_BINARY_OP_REVERSE_TRUE_DIVIDE] = MP_QSTR___rtruediv__, [MP_BINARY_OP_REVERSE_MODULO] = MP_QSTR___rmod__, [MP_BINARY_OP_REVERSE_POWER] = MP_QSTR___rpow__, [MP_BINARY_OP_REVERSE_OR] = MP_QSTR___ror__, [MP_BINARY_OP_REVERSE_XOR] = MP_QSTR___rxor__, [MP_BINARY_OP_REVERSE_AND] = MP_QSTR___rand__, [MP_BINARY_OP_REVERSE_LSHIFT] = MP_QSTR___rlshift__, [MP_BINARY_OP_REVERSE_RSHIFT] = MP_QSTR___rrshift__, #endif #endif }; STATIC mp_obj_t instance_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { // Note: For ducktyping, CPython does not look in the instance members or use // __getattr__ or __getattribute__. It only looks in the class dictionary. mp_obj_instance_t *lhs = MP_OBJ_TO_PTR(lhs_in); retry:; qstr op_name = mp_binary_op_method_name[op]; /* Still try to lookup native slot if (op_name == 0) { return MP_OBJ_NULL; } */ mp_obj_t dest[3] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = lhs, .attr = op_name, .meth_offset = offsetof(mp_obj_type_t, binary_op), .dest = dest, .is_type = false, }; mp_obj_class_lookup(&lookup, lhs->base.type); mp_obj_t res; if (dest[0] == MP_OBJ_SENTINEL) { res = mp_binary_op(op, lhs->subobj[0], rhs_in); } else if (dest[0] != MP_OBJ_NULL) { dest[2] = rhs_in; res = mp_call_method_n_kw(1, 0, dest); } else { // If this was an inplace method, fallback to normal method // https://docs.python.org/3/reference/datamodel.html#object.__iadd__ : // "If a specific method is not defined, the augmented assignment // falls back to the normal methods." if (op >= MP_BINARY_OP_INPLACE_OR && op <= MP_BINARY_OP_INPLACE_POWER) { op -= MP_BINARY_OP_INPLACE_OR - MP_BINARY_OP_OR; goto retry; } return MP_OBJ_NULL; // op not supported } #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED // NotImplemented means "try other fallbacks (like calling __rop__ // instead of __op__) and if nothing works, raise TypeError". As // MicroPython doesn't implement any fallbacks, signal to raise // TypeError right away. if (res == mp_const_notimplemented) { return MP_OBJ_NULL; // op not supported } #endif return res; } STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { // logic: look in instance members then class locals assert(mp_obj_is_instance_type(mp_obj_get_type(self_in))); mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); // Note: This is fast-path'ed in the VM for the MP_BC_LOAD_ATTR operation. mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { // object member, always treated as a value dest[0] = elem->value; return; } #if MICROPY_CPYTHON_COMPAT if (attr == MP_QSTR___dict__) { // Create a new dict with a copy of the instance's map items. // This creates, unlike CPython, a read-only __dict__ that can't be modified. mp_obj_dict_t dict; dict.base.type = &mp_type_dict; dict.map = self->members; dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(&dict)); mp_obj_dict_t *dest_dict = MP_OBJ_TO_PTR(dest[0]); dest_dict->map.is_fixed = 1; return; } #endif struct class_lookup_data lookup = { .obj = self, .attr = attr, .meth_offset = 0, .dest = dest, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); mp_obj_t member = dest[0]; if (member != MP_OBJ_NULL) { if (!(self->base.type->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { // Class doesn't have any special accessors to check so return straightaway return; } #if MICROPY_PY_BUILTINS_PROPERTY if (mp_obj_is_type(member, &mp_type_property)) { // object member is a property; delegate the load to the property // Note: This is an optimisation for code size and execution time. // The proper way to do it is have the functionality just below // in a __get__ method of the property object, and then it would // be called by the descriptor code down below. But that way // requires overhead for the nested mp_call's and overhead for // the code. const mp_obj_t *proxy = mp_obj_property_get(member); if (proxy[0] == mp_const_none) { mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("unreadable attribute")); } else { dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); } return; } #endif #if MICROPY_PY_DESCRIPTORS // found a class attribute; if it has a __get__ method then call it with the // class instance and class as arguments and return the result // Note that this is functionally correct but very slow: each load_attr // requires an extra mp_load_method_maybe to check for the __get__. mp_obj_t attr_get_method[4]; mp_load_method_maybe(member, MP_QSTR___get__, attr_get_method); if (attr_get_method[0] != MP_OBJ_NULL) { attr_get_method[2] = self_in; attr_get_method[3] = MP_OBJ_FROM_PTR(mp_obj_get_type(self_in)); dest[0] = mp_call_method_n_kw(2, 0, attr_get_method); } #endif return; } // try __getattr__ if (attr != MP_QSTR___getattr__) { #if MICROPY_PY_DELATTR_SETATTR // If the requested attr is __setattr__/__delattr__ then don't delegate the lookup // to __getattr__. If we followed CPython's behaviour then __setattr__/__delattr__ // would have already been found in the "object" base class. if (attr == MP_QSTR___setattr__ || attr == MP_QSTR___delattr__) { return; } #endif mp_obj_t dest2[3]; mp_load_method_maybe(self_in, MP_QSTR___getattr__, dest2); if (dest2[0] != MP_OBJ_NULL) { // __getattr__ exists, call it and return its result dest2[2] = MP_OBJ_NEW_QSTR(attr); dest[0] = mp_call_method_n_kw(1, 0, dest2); return; } } } STATIC bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); if (!(self->base.type->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { // Class doesn't have any special accessors so skip their checks goto skip_special_accessors; } #if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS // With property and/or descriptors enabled we need to do a lookup // first in the class dict for the attribute to see if the store should // be delegated. mp_obj_t member[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = self, .attr = attr, .meth_offset = 0, .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] != MP_OBJ_NULL) { #if MICROPY_PY_BUILTINS_PROPERTY if (mp_obj_is_type(member[0], &mp_type_property)) { // attribute exists and is a property; delegate the store/delete // Note: This is an optimisation for code size and execution time. // The proper way to do it is have the functionality just below in // a __set__/__delete__ method of the property object, and then it // would be called by the descriptor code down below. But that way // requires overhead for the nested mp_call's and overhead for // the code. const mp_obj_t *proxy = mp_obj_property_get(member[0]); mp_obj_t dest[2] = {self_in, value}; if (value == MP_OBJ_NULL) { // delete attribute if (proxy[2] == mp_const_none) { // TODO better error message? return false; } else { mp_call_function_n_kw(proxy[2], 1, 0, dest); return true; } } else { // store attribute if (proxy[1] == mp_const_none) { // TODO better error message? return false; } else { mp_call_function_n_kw(proxy[1], 2, 0, dest); return true; } } } #endif #if MICROPY_PY_DESCRIPTORS // found a class attribute; if it has a __set__/__delete__ method then // call it with the class instance (and value) as arguments if (value == MP_OBJ_NULL) { // delete attribute mp_obj_t attr_delete_method[3]; mp_load_method_maybe(member[0], MP_QSTR___delete__, attr_delete_method); if (attr_delete_method[0] != MP_OBJ_NULL) { attr_delete_method[2] = self_in; mp_call_method_n_kw(1, 0, attr_delete_method); return true; } } else { // store attribute mp_obj_t attr_set_method[4]; mp_load_method_maybe(member[0], MP_QSTR___set__, attr_set_method); if (attr_set_method[0] != MP_OBJ_NULL) { attr_set_method[2] = self_in; attr_set_method[3] = value; mp_call_method_n_kw(2, 0, attr_set_method); return true; } } #endif } #endif #if MICROPY_PY_DELATTR_SETATTR if (value == MP_OBJ_NULL) { // delete attribute // try __delattr__ first mp_obj_t attr_delattr_method[3]; mp_load_method_maybe(self_in, MP_QSTR___delattr__, attr_delattr_method); if (attr_delattr_method[0] != MP_OBJ_NULL) { // __delattr__ exists, so call it attr_delattr_method[2] = MP_OBJ_NEW_QSTR(attr); mp_call_method_n_kw(1, 0, attr_delattr_method); return true; } } else { // store attribute // try __setattr__ first mp_obj_t attr_setattr_method[4]; mp_load_method_maybe(self_in, MP_QSTR___setattr__, attr_setattr_method); if (attr_setattr_method[0] != MP_OBJ_NULL) { // __setattr__ exists, so call it attr_setattr_method[2] = MP_OBJ_NEW_QSTR(attr); attr_setattr_method[3] = value; mp_call_method_n_kw(2, 0, attr_setattr_method); return true; } } #endif skip_special_accessors: if (value == MP_OBJ_NULL) { // delete attribute mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); return elem != NULL; } else { // store attribute mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; return true; } } STATIC void mp_obj_instance_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { mp_obj_instance_load_attr(self_in, attr, dest); } else { if (mp_obj_instance_store_attr(self_in, attr, dest[1])) { dest[0] = MP_OBJ_NULL; // indicate success } } } STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t member[4] = {MP_OBJ_NULL, MP_OBJ_NULL, index, value}; struct class_lookup_data lookup = { .obj = self, .meth_offset = offsetof(mp_obj_type_t, subscr), .dest = member, .is_type = false, }; if (value == MP_OBJ_NULL) { // delete item lookup.attr = MP_QSTR___delitem__; } else if (value == MP_OBJ_SENTINEL) { // load item lookup.attr = MP_QSTR___getitem__; } else { // store item lookup.attr = MP_QSTR___setitem__; } mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_SENTINEL) { return mp_obj_subscr(self->subobj[0], index, value); } else if (member[0] != MP_OBJ_NULL) { size_t n_args = value == MP_OBJ_NULL || value == MP_OBJ_SENTINEL ? 1 : 2; mp_obj_t ret = mp_call_method_n_kw(n_args, 0, member); if (value == MP_OBJ_SENTINEL) { return ret; } else { return mp_const_none; } } else { return MP_OBJ_NULL; // op not supported } } STATIC mp_obj_t mp_obj_instance_get_call(mp_obj_t self_in, mp_obj_t *member) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); struct class_lookup_data lookup = { .obj = self, .attr = MP_QSTR___call__, .meth_offset = offsetof(mp_obj_type_t, call), .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); return member[0]; } bool mp_obj_instance_is_callable(mp_obj_t self_in) { mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; return mp_obj_instance_get_call(self_in, member) != MP_OBJ_NULL; } mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; mp_obj_t call = mp_obj_instance_get_call(self_in, member); if (call == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(self_in)); #endif } mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); if (call == MP_OBJ_SENTINEL) { return mp_call_function_n_kw(self->subobj[0], n_args, n_kw, args); } return mp_call_method_self_n_kw(member[0], member[1], n_args, n_kw, args); } // Note that iter_buf may be NULL, and needs to be allocated if needed mp_obj_t mp_obj_instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t member[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = self, .attr = MP_QSTR___iter__, .meth_offset = offsetof(mp_obj_type_t, getiter), .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_NULL) { return MP_OBJ_NULL; } else if (member[0] == MP_OBJ_SENTINEL) { const mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); if (iter_buf == NULL) { iter_buf = m_new_obj(mp_obj_iter_buf_t); } return type->getiter(self->subobj[0], iter_buf); } else { return mp_call_method_n_kw(0, 0, member); } } STATIC mp_int_t instance_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t member[2] = {MP_OBJ_NULL}; struct class_lookup_data lookup = { .obj = self, .attr = MP_QSTR_, // don't actually look for a method .meth_offset = offsetof(mp_obj_type_t, buffer_p.get_buffer), .dest = member, .is_type = false, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_SENTINEL) { const mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); return type->buffer_p.get_buffer(self->subobj[0], bufinfo, flags); } else { return 1; // object does not support buffer protocol } } /******************************************************************************/ // type object // - the struct is mp_obj_type_t and is defined in obj.h so const types can be made // - there is a constant mp_obj_type_t (called mp_type_type) for the 'type' object // - creating a new class (a new type) creates a new mp_obj_type_t #if ENABLE_SPECIAL_ACCESSORS STATIC bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { #if MICROPY_PY_DELATTR_SETATTR if (key == MP_OBJ_NEW_QSTR(MP_QSTR___setattr__) || key == MP_OBJ_NEW_QSTR(MP_QSTR___delattr__)) { return true; } #endif #if MICROPY_PY_BUILTINS_PROPERTY if (mp_obj_is_type(value, &mp_type_property)) { return true; } #endif #if MICROPY_PY_DESCRIPTORS static const uint8_t to_check[] = { MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, }; for (size_t i = 0; i < MP_ARRAY_SIZE(to_check); ++i) { mp_obj_t dest_temp[2]; mp_load_method_protected(value, to_check[i], dest_temp, true); if (dest_temp[0] != MP_OBJ_NULL) { return true; } } #endif return false; } #endif STATIC void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "", self->name); } STATIC mp_obj_t type_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 1, 3, false); switch (n_args) { case 1: return MP_OBJ_FROM_PTR(mp_obj_get_type(args[0])); case 3: // args[0] = name // args[1] = bases tuple // args[2] = locals dict return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]); default: mp_raise_TypeError(MP_ERROR_TEXT("type takes 1 or 3 arguments")); } } STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // instantiate an instance of a class mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (self->make_new == NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't create instance")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't create '%q' instances"), self->name); #endif } // make new instance mp_obj_t o = self->make_new(self, n_args, n_kw, args); // return new instance return o; } STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { assert(mp_obj_is_type(self_in, &mp_type_type)); mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { // load attribute #if MICROPY_CPYTHON_COMPAT if (attr == MP_QSTR___name__) { dest[0] = MP_OBJ_NEW_QSTR(self->name); return; } #if MICROPY_CPYTHON_COMPAT if (attr == MP_QSTR___dict__) { // Returns a read-only dict of the class attributes. // If the internal locals is not fixed, a copy will be created. const mp_obj_dict_t *dict = self->locals_dict; if (!dict) { dict = &mp_const_empty_dict_obj; } if (dict->map.is_fixed) { dest[0] = MP_OBJ_FROM_PTR(dict); } else { dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict)); mp_obj_dict_t *dict_copy = MP_OBJ_TO_PTR(dest[0]); dict_copy->map.is_fixed = 1; } return; } #endif if (attr == MP_QSTR___bases__) { if (self == &mp_type_object) { dest[0] = mp_const_empty_tuple; return; } mp_obj_t parent_obj = self->parent ? MP_OBJ_FROM_PTR(self->parent) : MP_OBJ_FROM_PTR(&mp_type_object); #if MICROPY_MULTIPLE_INHERITANCE if (mp_obj_is_type(parent_obj, &mp_type_tuple)) { dest[0] = parent_obj; return; } #endif dest[0] = mp_obj_new_tuple(1, &parent_obj); return; } #endif struct class_lookup_data lookup = { .obj = (mp_obj_instance_t *)self, .attr = attr, .meth_offset = 0, .dest = dest, .is_type = true, }; mp_obj_class_lookup(&lookup, self); } else { // delete/store attribute if (self->locals_dict != NULL) { assert(mp_obj_is_dict_or_ordereddict(MP_OBJ_FROM_PTR(self->locals_dict))); // MicroPython restriction, for now mp_map_t *locals_map = &self->locals_dict->map; if (locals_map->is_fixed) { // can't apply delete/store to a fixed map return; } if (dest[1] == MP_OBJ_NULL) { // delete attribute mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); if (elem != NULL) { dest[0] = MP_OBJ_NULL; // indicate success } } else { #if ENABLE_SPECIAL_ACCESSORS // Check if we add any special accessor methods with this store if (!(self->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { if (self->flags & MP_TYPE_FLAG_IS_SUBCLASSED) { // This class is already subclassed so can't have special accessors added mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't add special method to already-subclassed class")); } self->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } } #endif // store attribute mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); elem->value = dest[1]; dest[0] = MP_OBJ_NULL; // indicate success } } } } const mp_obj_type_t mp_type_type = { { &mp_type_type }, .name = MP_QSTR_type, .print = type_print, .make_new = type_make_new, .call = type_call, .unary_op = mp_generic_unary_op, .attr = type_attr, }; mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) { // Verify input objects have expected type if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { mp_raise_TypeError(NULL); } if (!mp_obj_is_dict_or_ordereddict(locals_dict)) { mp_raise_TypeError(NULL); } // TODO might need to make a copy of locals_dict; at least that's how CPython does it // Basic validation of base classes uint16_t base_flags = MP_TYPE_FLAG_EQ_NOT_REFLEXIVE | MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST; size_t bases_len; mp_obj_t *bases_items; mp_obj_tuple_get(bases_tuple, &bases_len, &bases_items); for (size_t i = 0; i < bases_len; i++) { if (!mp_obj_is_type(bases_items[i], &mp_type_type)) { mp_raise_TypeError(NULL); } mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); // TODO: Verify with CPy, tested on function type if (t->make_new == NULL) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("type isn't an acceptable base type")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name); #endif } #if ENABLE_SPECIAL_ACCESSORS if (mp_obj_is_instance_type(t)) { t->flags |= MP_TYPE_FLAG_IS_SUBCLASSED; base_flags |= t->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } #endif } mp_obj_type_t *o = m_new0(mp_obj_type_t, 1); o->base.type = &mp_type_type; o->flags = base_flags; o->name = name; o->print = instance_print; o->make_new = mp_obj_instance_make_new; o->call = mp_obj_instance_call; o->unary_op = instance_unary_op; o->binary_op = instance_binary_op; o->attr = mp_obj_instance_attr; o->subscr = instance_subscr; o->getiter = mp_obj_instance_getiter; // o->iternext = ; not implemented o->buffer_p.get_buffer = instance_get_buffer; if (bases_len > 0) { // Inherit protocol from a base class. This allows to define an // abstract base class which would translate C-level protocol to // Python method calls, and any subclass inheriting from it will // support this feature. o->protocol = ((mp_obj_type_t *)MP_OBJ_TO_PTR(bases_items[0]))->protocol; if (bases_len >= 2) { #if MICROPY_MULTIPLE_INHERITANCE o->parent = MP_OBJ_TO_PTR(bases_tuple); #else mp_raise_NotImplementedError(MP_ERROR_TEXT("multiple inheritance not supported")); #endif } else { o->parent = MP_OBJ_TO_PTR(bases_items[0]); } } o->locals_dict = MP_OBJ_TO_PTR(locals_dict); #if ENABLE_SPECIAL_ACCESSORS // Check if the class has any special accessor methods if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { for (size_t i = 0; i < o->locals_dict->map.alloc; i++) { if (mp_map_slot_is_filled(&o->locals_dict->map, i)) { const mp_map_elem_t *elem = &o->locals_dict->map.table[i]; if (check_for_special_accessors(elem->key, elem->value)) { o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; break; } } } } #endif const mp_obj_type_t *native_base; size_t num_native_bases = instance_count_native_bases(o, &native_base); if (num_native_bases > 1) { mp_raise_TypeError(MP_ERROR_TEXT("multiple bases have instance lay-out conflict")); } mp_map_t *locals_map = &o->locals_dict->map; mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(MP_QSTR___new__), MP_MAP_LOOKUP); if (elem != NULL) { // __new__ slot exists; check if it is a function if (mp_obj_is_fun(elem->value)) { // __new__ is a function, wrap it in a staticmethod decorator elem->value = static_class_method_make_new(&mp_type_staticmethod, 1, 0, &elem->value); } } return MP_OBJ_FROM_PTR(o); } /******************************************************************************/ // super object typedef struct _mp_obj_super_t { mp_obj_base_t base; mp_obj_t type; mp_obj_t obj; } mp_obj_super_t; STATIC void super_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); mp_print_str(print, "type, PRINT_STR); mp_print_str(print, ", "); mp_obj_print_helper(print, self->obj, PRINT_STR); mp_print_str(print, ">"); } STATIC mp_obj_t super_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; // 0 arguments are turned into 2 in the compiler // 1 argument is not yet implemented mp_arg_check_num(n_args, n_kw, 2, 2, false); if (!mp_obj_is_type(args[0], &mp_type_type)) { mp_raise_TypeError(NULL); } mp_obj_super_t *o = m_new_obj(mp_obj_super_t); *o = (mp_obj_super_t) {{type_in}, args[0], args[1]}; return MP_OBJ_FROM_PTR(o); } STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute return; } assert(mp_obj_is_type(self_in, &mp_type_super)); mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); assert(mp_obj_is_type(self->type, &mp_type_type)); mp_obj_type_t *type = MP_OBJ_TO_PTR(self->type); struct class_lookup_data lookup = { .obj = MP_OBJ_TO_PTR(self->obj), .attr = attr, .meth_offset = 0, .dest = dest, .is_type = false, }; // Allow a call super().__init__() to reach any native base classes if (attr == MP_QSTR___init__) { lookup.meth_offset = offsetof(mp_obj_type_t, make_new); } if (type->parent == NULL) { // no parents, do nothing #if MICROPY_MULTIPLE_INHERITANCE } else if (((mp_obj_base_t *)type->parent)->type == &mp_type_tuple) { const mp_obj_tuple_t *parent_tuple = type->parent; size_t len = parent_tuple->len; const mp_obj_t *items = parent_tuple->items; for (size_t i = 0; i < len; i++) { assert(mp_obj_is_type(items[i], &mp_type_type)); if (MP_OBJ_TO_PTR(items[i]) == &mp_type_object) { // The "object" type will be searched at the end of this function, // and we don't want to lookup native methods in object. continue; } mp_obj_class_lookup(&lookup, (mp_obj_type_t *)MP_OBJ_TO_PTR(items[i])); if (dest[0] != MP_OBJ_NULL) { break; } } #endif } else if (type->parent != &mp_type_object) { mp_obj_class_lookup(&lookup, type->parent); } if (dest[0] != MP_OBJ_NULL) { if (dest[0] == MP_OBJ_SENTINEL) { // Looked up native __init__ so defer to it dest[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); dest[1] = self->obj; } return; } // Reset meth_offset so we don't look up any native methods in object, // because object never takes up the native base-class slot. lookup.meth_offset = 0; mp_obj_class_lookup(&lookup, &mp_type_object); } const mp_obj_type_t mp_type_super = { { &mp_type_type }, .name = MP_QSTR_super, .print = super_print, .make_new = super_make_new, .attr = super_attr, }; void mp_load_super_method(qstr attr, mp_obj_t *dest) { mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); } /******************************************************************************/ // subclassing and built-ins specific to types // object and classinfo should be type objects // (but the function will fail gracefully if they are not) bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo) { for (;;) { if (object == classinfo) { return true; } // not equivalent classes, keep searching base classes // object should always be a type object, but just return false if it's not if (!mp_obj_is_type(object, &mp_type_type)) { return false; } const mp_obj_type_t *self = MP_OBJ_TO_PTR(object); if (self->parent == NULL) { // type has no parents return false; #if MICROPY_MULTIPLE_INHERITANCE } else if (((mp_obj_base_t *)self->parent)->type == &mp_type_tuple) { // get the base objects (they should be type objects) const mp_obj_tuple_t *parent_tuple = self->parent; const mp_obj_t *item = parent_tuple->items; const mp_obj_t *top = item + parent_tuple->len - 1; // iterate through the base objects for (; item < top; ++item) { if (mp_obj_is_subclass_fast(*item, classinfo)) { return true; } } // search last base (simple tail recursion elimination) object = *item; #endif } else { // type has 1 parent object = MP_OBJ_FROM_PTR(self->parent); } } } STATIC mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { size_t len; mp_obj_t *items; if (mp_obj_is_type(classinfo, &mp_type_type)) { len = 1; items = &classinfo; } else if (mp_obj_is_type(classinfo, &mp_type_tuple)) { mp_obj_tuple_get(classinfo, &len, &items); } else { mp_raise_TypeError(MP_ERROR_TEXT("issubclass() arg 2 must be a class or a tuple of classes")); } for (size_t i = 0; i < len; i++) { // We explicitly check for 'object' here since no-one explicitly derives from it if (items[i] == MP_OBJ_FROM_PTR(&mp_type_object) || mp_obj_is_subclass_fast(object, items[i])) { return mp_const_true; } } return mp_const_false; } STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) { if (!mp_obj_is_type(object, &mp_type_type)) { mp_raise_TypeError(MP_ERROR_TEXT("issubclass() arg 1 must be a class")); } return mp_obj_is_subclass(object, classinfo); } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_issubclass_obj, mp_builtin_issubclass); STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) { return mp_obj_is_subclass(MP_OBJ_FROM_PTR(mp_obj_get_type(object)), classinfo); } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance); mp_obj_t mp_obj_cast_to_native_base(mp_obj_t self_in, mp_const_obj_t native_type) { const mp_obj_type_t *self_type = mp_obj_get_type(self_in); if (MP_OBJ_FROM_PTR(self_type) == native_type) { return self_in; } else if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(self_type), native_type)) { return MP_OBJ_NULL; } else { mp_obj_instance_t *self = (mp_obj_instance_t *)MP_OBJ_TO_PTR(self_in); return self->subobj[0]; } } /******************************************************************************/ // staticmethod and classmethod types (probably should go in a different file) STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(self == &mp_type_staticmethod || self == &mp_type_classmethod); mp_arg_check_num(n_args, n_kw, 1, 1, false); mp_obj_static_class_method_t *o = m_new_obj(mp_obj_static_class_method_t); *o = (mp_obj_static_class_method_t) {{self}, args[0]}; return MP_OBJ_FROM_PTR(o); } const mp_obj_type_t mp_type_staticmethod = { { &mp_type_type }, .name = MP_QSTR_staticmethod, .make_new = static_class_method_make_new, }; const mp_obj_type_t mp_type_classmethod = { { &mp_type_type }, .name = MP_QSTR_classmethod, .make_new = static_class_method_make_new, }; #include #include #include "py/objtuple.h" #include "py/runtime.h" typedef struct _mp_obj_zip_t { mp_obj_base_t base; size_t n_iters; mp_obj_t iters[]; } mp_obj_zip_t; STATIC mp_obj_t zip_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false); mp_obj_zip_t *o = m_new_obj_var(mp_obj_zip_t, mp_obj_t, n_args); o->base.type = type; o->n_iters = n_args; for (size_t i = 0; i < n_args; i++) { o->iters[i] = mp_getiter(args[i], NULL); } return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t zip_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_zip)); mp_obj_zip_t *self = MP_OBJ_TO_PTR(self_in); if (self->n_iters == 0) { return MP_OBJ_STOP_ITERATION; } mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(self->n_iters, NULL)); for (size_t i = 0; i < self->n_iters; i++) { mp_obj_t next = mp_iternext(self->iters[i]); if (next == MP_OBJ_STOP_ITERATION) { mp_obj_tuple_del(MP_OBJ_FROM_PTR(tuple)); return MP_OBJ_STOP_ITERATION; } tuple->items[i] = next; } return MP_OBJ_FROM_PTR(tuple); } const mp_obj_type_t mp_type_zip = { { &mp_type_type }, .name = MP_QSTR_zip, .make_new = zip_make_new, .getiter = mp_identity_getiter, .iternext = zip_iternext, }; #include "py/obj.h" #include "py/builtin.h" STATIC mp_obj_t op_getitem(mp_obj_t self_in, mp_obj_t key_in) { const mp_obj_type_t *type = mp_obj_get_type(self_in); return type->subscr(self_in, key_in, MP_OBJ_SENTINEL); } MP_DEFINE_CONST_FUN_OBJ_2(mp_op_getitem_obj, op_getitem); STATIC mp_obj_t op_setitem(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { const mp_obj_type_t *type = mp_obj_get_type(self_in); return type->subscr(self_in, key_in, value_in); } MP_DEFINE_CONST_FUN_OBJ_3(mp_op_setitem_obj, op_setitem); STATIC mp_obj_t op_delitem(mp_obj_t self_in, mp_obj_t key_in) { const mp_obj_type_t *type = mp_obj_get_type(self_in); return type->subscr(self_in, key_in, MP_OBJ_NULL); } MP_DEFINE_CONST_FUN_OBJ_2(mp_op_delitem_obj, op_delitem); STATIC mp_obj_t op_contains(mp_obj_t lhs_in, mp_obj_t rhs_in) { const mp_obj_type_t *type = mp_obj_get_type(lhs_in); return type->binary_op(MP_BINARY_OP_CONTAINS, lhs_in, rhs_in); } MP_DEFINE_CONST_FUN_OBJ_2(mp_op_contains_obj, op_contains); #include #include "py/runtime.h" // Helpers for sequence types #define SWAP(type, var1, var2) { type t = var2; var2 = var1; var1 = t; } // Implements backend of sequence * integer operation. Assumes elements are // memory-adjacent in sequence. void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times, void *dest) { for (size_t i = 0; i < times; i++) { size_t copy_sz = item_sz * len; memcpy(dest, items, copy_sz); dest = (char *)dest + copy_sz; } } #if MICROPY_PY_BUILTINS_SLICE bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) { mp_obj_slice_indices(slice, len, indexes); // If the index is negative then stop points to the last item, not after it if (indexes->step < 0) { indexes->stop++; } // CPython returns empty sequence in such case, or point for assignment is at start if (indexes->step > 0 && indexes->start > indexes->stop) { indexes->stop = indexes->start; } else if (indexes->step < 0 && indexes->start < indexes->stop) { indexes->stop = indexes->start + 1; } return indexes->step == 1; } #endif mp_obj_t mp_seq_extract_slice(size_t len, const mp_obj_t *seq, mp_bound_slice_t *indexes) { (void)len; // TODO can we remove len from the arg list? mp_int_t start = indexes->start, stop = indexes->stop; mp_int_t step = indexes->step; mp_obj_t res = mp_obj_new_list(0, NULL); if (step < 0) { while (start >= stop) { mp_obj_list_append(res, seq[start]); start += step; } } else { while (start < stop) { mp_obj_list_append(res, seq[start]); start += step; } } return res; } // Special-case comparison function for sequences of bytes // Don't pass MP_BINARY_OP_NOT_EQUAL here bool mp_seq_cmp_bytes(mp_uint_t op, const byte *data1, size_t len1, const byte *data2, size_t len2) { if (op == MP_BINARY_OP_EQUAL && len1 != len2) { return false; } // Let's deal only with > & >= if (op == MP_BINARY_OP_LESS || op == MP_BINARY_OP_LESS_EQUAL) { SWAP(const byte *, data1, data2); SWAP(size_t, len1, len2); if (op == MP_BINARY_OP_LESS) { op = MP_BINARY_OP_MORE; } else { op = MP_BINARY_OP_MORE_EQUAL; } } size_t min_len = len1 < len2 ? len1 : len2; int res = memcmp(data1, data2, min_len); if (op == MP_BINARY_OP_EQUAL) { // If we are checking for equality, here's the answer return res == 0; } if (res < 0) { return false; } if (res > 0) { return true; } // If we had tie in the last element... // ... and we have lists of different lengths... if (len1 != len2) { if (len1 < len2) { // ... then longer list length wins (we deal only with >) return false; } } else if (op == MP_BINARY_OP_MORE) { // Otherwise, if we have strict relation, equality means failure return false; } return true; } // Special-case comparison function for sequences of mp_obj_t // Don't pass MP_BINARY_OP_NOT_EQUAL here bool mp_seq_cmp_objs(mp_uint_t op, const mp_obj_t *items1, size_t len1, const mp_obj_t *items2, size_t len2) { if (op == MP_BINARY_OP_EQUAL && len1 != len2) { return false; } // Let's deal only with > & >= if (op == MP_BINARY_OP_LESS || op == MP_BINARY_OP_LESS_EQUAL) { SWAP(const mp_obj_t *, items1, items2); SWAP(size_t, len1, len2); if (op == MP_BINARY_OP_LESS) { op = MP_BINARY_OP_MORE; } else { op = MP_BINARY_OP_MORE_EQUAL; } } size_t len = len1 < len2 ? len1 : len2; for (size_t i = 0; i < len; i++) { // If current elements equal, can't decide anything - go on if (mp_obj_equal(items1[i], items2[i])) { continue; } // Othewise, if they are not equal, we can have final decision based on them if (op == MP_BINARY_OP_EQUAL) { // In particular, if we are checking for equality, here're the answer return false; } // Otherwise, application of relation op gives the answer return mp_binary_op(op, items1[i], items2[i]) == mp_const_true; } // If we had tie in the last element... // ... and we have lists of different lengths... if (len1 != len2) { if (len1 < len2) { // ... then longer list length wins (we deal only with >) return false; } } else if (op == MP_BINARY_OP_MORE) { // Otherwise, if we have strict relation, sequence equality means failure return false; } return true; } // Special-case of index() which searches for mp_obj_t mp_obj_t mp_seq_index_obj(const mp_obj_t *items, size_t len, size_t n_args, const mp_obj_t *args) { const mp_obj_type_t *type = mp_obj_get_type(args[0]); mp_obj_t value = args[1]; size_t start = 0; size_t stop = len; if (n_args >= 3) { start = mp_get_index(type, len, args[2], true); if (n_args >= 4) { stop = mp_get_index(type, len, args[3], true); } } for (size_t i = start; i < stop; i++) { if (mp_obj_equal(items[i], value)) { // Common sense says this cannot overflow small int return MP_OBJ_NEW_SMALL_INT(i); } } mp_raise_ValueError(MP_ERROR_TEXT("object not in sequence")); } mp_obj_t mp_seq_count_obj(const mp_obj_t *items, size_t len, mp_obj_t value) { size_t count = 0; for (size_t i = 0; i < len; i++) { if (mp_obj_equal(items[i], value)) { count++; } } // Common sense says this cannot overflow small int return MP_OBJ_NEW_SMALL_INT(count); } /* * Copyright (c) 2014 Damien P. George * Copyright (c) 2014-2016 Paul Sokolovsky */ #include #ifndef __IPU__ #include #else #include #endif #include "py/objstr.h" #include "py/stream.h" #include "py/runtime.h" // This file defines generic Python stream read/write methods which // dispatch to the underlying stream interface of an object. // TODO: should be in mpconfig.h #define DEFAULT_BUFFER_SIZE 256 STATIC mp_obj_t stream_readall(mp_obj_t self_in); #define STREAM_CONTENT_TYPE(stream) (((stream)->is_text) ? &mp_type_str : &mp_type_bytes) // Returns error condition in *errcode, if non-zero, return value is number of bytes written // before error condition occurred. If *errcode == 0, returns total bytes written (which will // be equal to input size). mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf_, mp_uint_t size, int *errcode, byte flags) { byte *buf = buf_; typedef mp_uint_t (*io_func_t)(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode); io_func_t io_func; const mp_stream_p_t *stream_p = mp_get_stream(stream); if (flags & MP_STREAM_RW_WRITE) { io_func = (io_func_t)stream_p->write; } else { io_func = stream_p->read; } *errcode = 0; mp_uint_t done = 0; while (size > 0) { mp_uint_t out_sz = io_func(stream, buf, size, errcode); // For read, out_sz == 0 means EOF. For write, it's unspecified // what it means, but we don't make any progress, so returning // is still the best option. if (out_sz == 0) { return done; } if (out_sz == MP_STREAM_ERROR) { // If we read something before getting EAGAIN, don't leak it if (mp_is_nonblocking_error(*errcode) && done != 0) { *errcode = 0; } return done; } if (flags & MP_STREAM_RW_ONCE) { return out_sz; } buf += out_sz; size -= out_sz; done += out_sz; } return done; } const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) { const mp_obj_type_t *type = mp_obj_get_type(self_in); const mp_stream_p_t *stream_p = type->protocol; if (stream_p == NULL || ((flags & MP_STREAM_OP_READ) && stream_p->read == NULL) || ((flags & MP_STREAM_OP_WRITE) && stream_p->write == NULL) || ((flags & MP_STREAM_OP_IOCTL) && stream_p->ioctl == NULL)) { // CPython: io.UnsupportedOperation, OSError subclass mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); } return stream_p; } STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte flags) { // What to do if sz < -1? Python docs don't specify this case. // CPython does a readall, but here we silently let negatives through, // and they will cause a MemoryError. mp_int_t sz; if (n_args == 1 || ((sz = mp_obj_get_int(args[1])) == -1)) { return stream_readall(args[0]); } const mp_stream_p_t *stream_p = mp_get_stream(args[0]); #if MICROPY_PY_BUILTINS_STR_UNICODE if (stream_p->is_text) { // We need to read sz number of unicode characters. Because we don't have any // buffering, and because the stream API can only read bytes, we must read here // in units of bytes and must never over read. If we want sz chars, then reading // sz bytes will never over-read, so we follow this approach, in a loop to keep // reading until we have exactly enough chars. This will be 1 read for text // with ASCII-only chars, and about 2 reads for text with a couple of non-ASCII // chars. For text with lots of non-ASCII chars, it'll be pretty inefficient // in time and memory. vstr_t vstr; vstr_init(&vstr, sz); mp_uint_t more_bytes = sz; mp_uint_t last_buf_offset = 0; while (more_bytes > 0) { char *p = vstr_add_len(&vstr, more_bytes); int error; mp_uint_t out_sz = mp_stream_read_exactly(args[0], p, more_bytes, &error); if (error != 0) { vstr_cut_tail_bytes(&vstr, more_bytes); if (mp_is_nonblocking_error(error)) { // With non-blocking streams, we read as much as we can. // If we read nothing, return None, just like read(). // Otherwise, return data read so far. // TODO what if we have read only half a non-ASCII char? if (vstr.len == 0) { vstr_clear(&vstr); return mp_const_none; } break; } mp_raise_OSError(error); } if (out_sz < more_bytes) { // Finish reading. // TODO what if we have read only half a non-ASCII char? vstr_cut_tail_bytes(&vstr, more_bytes - out_sz); if (out_sz == 0) { break; } } // count chars from bytes just read for (mp_uint_t off = last_buf_offset;;) { byte b = vstr.buf[off]; int n; if (!UTF8_IS_NONASCII(b)) { // 1-byte ASCII char n = 1; } else if ((b & 0xe0) == 0xc0) { // 2-byte char n = 2; } else if ((b & 0xf0) == 0xe0) { // 3-byte char n = 3; } else if ((b & 0xf8) == 0xf0) { // 4-byte char n = 4; } else { // TODO n = 5; } if (off + n <= vstr.len) { // got a whole char in n bytes off += n; sz -= 1; last_buf_offset = off; if (off >= vstr.len) { more_bytes = sz; break; } } else { // didn't get a whole char, so work out how many extra bytes are needed for // this partial char, plus bytes for additional chars that we want more_bytes = (off + n - vstr.len) + (sz - 1); break; } } } return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); } #endif vstr_t vstr; vstr_init_len(&vstr, sz); int error; mp_uint_t out_sz = mp_stream_rw(args[0], vstr.buf, sz, &error, flags); if (error != 0) { vstr_clear(&vstr); if (mp_is_nonblocking_error(error)) { // https://docs.python.org/3.4/library/io.html#io.RawIOBase.read // "If the object is in non-blocking mode and no bytes are available, // None is returned." // This is actually very weird, as naive truth check will treat // this as EOF. return mp_const_none; } mp_raise_OSError(error); } else { vstr.len = out_sz; return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr); } } STATIC mp_obj_t stream_read(size_t n_args, const mp_obj_t *args) { return stream_read_generic(n_args, args, MP_STREAM_RW_READ); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj, 1, 2, stream_read); STATIC mp_obj_t stream_read1(size_t n_args, const mp_obj_t *args) { return stream_read_generic(n_args, args, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read1_obj, 1, 2, stream_read1); mp_obj_t mp_stream_write(mp_obj_t self_in, const void *buf, size_t len, byte flags) { int error; mp_uint_t out_sz = mp_stream_rw(self_in, (void *)buf, len, &error, flags); if (error != 0) { if (mp_is_nonblocking_error(error)) { // http://docs.python.org/3/library/io.html#io.RawIOBase.write // "None is returned if the raw stream is set not to block and // no single byte could be readily written to it." return mp_const_none; } mp_raise_OSError(error); } else { return MP_OBJ_NEW_SMALL_INT(out_sz); } } // This is used to adapt a stream object to an mp_print_t interface void mp_stream_write_adaptor(void *self, const char *buf, size_t len) { mp_stream_write(MP_OBJ_FROM_PTR(self), buf, len, MP_STREAM_RW_WRITE); } STATIC mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); size_t max_len = (size_t)-1; size_t off = 0; if (n_args == 3) { max_len = mp_obj_get_int_truncated(args[2]); } else if (n_args == 4) { off = mp_obj_get_int_truncated(args[2]); max_len = mp_obj_get_int_truncated(args[3]); if (off > bufinfo.len) { off = bufinfo.len; } } bufinfo.len -= off; return mp_stream_write(args[0], (byte *)bufinfo.buf + off, MIN(bufinfo.len, max_len), MP_STREAM_RW_WRITE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj, 2, 4, stream_write_method); STATIC mp_obj_t stream_write1_method(mp_obj_t self_in, mp_obj_t arg) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); return mp_stream_write(self_in, bufinfo.buf, bufinfo.len, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE); } MP_DEFINE_CONST_FUN_OBJ_2(mp_stream_write1_obj, stream_write1_method); STATIC mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); // CPython extension: if 2nd arg is provided, that's max len to read, // instead of full buffer. Similar to // https://docs.python.org/3/library/socket.html#socket.socket.recv_into mp_uint_t len = bufinfo.len; if (n_args > 2) { len = mp_obj_get_int(args[2]); if (len > bufinfo.len) { len = bufinfo.len; } } int error; mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error); if (error != 0) { if (mp_is_nonblocking_error(error)) { return mp_const_none; } mp_raise_OSError(error); } else { return MP_OBJ_NEW_SMALL_INT(out_sz); } } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto_obj, 2, 3, stream_readinto); STATIC mp_obj_t stream_readall(mp_obj_t self_in) { const mp_stream_p_t *stream_p = mp_get_stream(self_in); mp_uint_t total_size = 0; vstr_t vstr; vstr_init(&vstr, DEFAULT_BUFFER_SIZE); char *p = vstr.buf; mp_uint_t current_read = DEFAULT_BUFFER_SIZE; while (true) { int error; mp_uint_t out_sz = stream_p->read(self_in, p, current_read, &error); if (out_sz == MP_STREAM_ERROR) { if (mp_is_nonblocking_error(error)) { // With non-blocking streams, we read as much as we can. // If we read nothing, return None, just like read(). // Otherwise, return data read so far. if (total_size == 0) { return mp_const_none; } break; } mp_raise_OSError(error); } if (out_sz == 0) { break; } total_size += out_sz; if (out_sz < current_read) { current_read -= out_sz; p += out_sz; } else { p = vstr_extend(&vstr, DEFAULT_BUFFER_SIZE); current_read = DEFAULT_BUFFER_SIZE; } } vstr.len = total_size; return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr); } // Unbuffered, inefficient implementation of readline() for raw I/O files. STATIC mp_obj_t stream_unbuffered_readline(size_t n_args, const mp_obj_t *args) { const mp_stream_p_t *stream_p = mp_get_stream(args[0]); mp_int_t max_size = -1; if (n_args > 1) { max_size = MP_OBJ_SMALL_INT_VALUE(args[1]); } vstr_t vstr; if (max_size != -1) { vstr_init(&vstr, max_size); } else { vstr_init(&vstr, 16); } while (max_size == -1 || max_size-- != 0) { char *p = vstr_add_len(&vstr, 1); int error; mp_uint_t out_sz = stream_p->read(args[0], p, 1, &error); if (out_sz == MP_STREAM_ERROR) { if (mp_is_nonblocking_error(error)) { if (vstr.len == 1) { // We just incremented it, but otherwise we read nothing // and immediately got EAGAIN. This case is not well // specified in // https://docs.python.org/3/library/io.html#io.IOBase.readline // unlike similar case for read(). But we follow the latter's // behavior - return None. vstr_clear(&vstr); return mp_const_none; } else { goto done; } } mp_raise_OSError(error); } if (out_sz == 0) { done: // Back out previously added byte // Consider, what's better - read a char and get OutOfMemory (so read // char is lost), or allocate first as we do. vstr_cut_tail_bytes(&vstr, 1); break; } if (*p == '\n') { break; } } return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_unbuffered_readline_obj, 1, 2, stream_unbuffered_readline); // TODO take an optional extra argument (what does it do exactly?) STATIC mp_obj_t stream_unbuffered_readlines(mp_obj_t self) { mp_obj_t lines = mp_obj_new_list(0, NULL); for (;;) { mp_obj_t line = stream_unbuffered_readline(1, &self); if (!mp_obj_is_true(line)) { break; } mp_obj_list_append(lines, line); } return lines; } MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_unbuffered_readlines_obj, stream_unbuffered_readlines); mp_obj_t mp_stream_unbuffered_iter(mp_obj_t self) { mp_obj_t l_in = stream_unbuffered_readline(1, &self); if (mp_obj_is_true(l_in)) { return l_in; } return MP_OBJ_STOP_ITERATION; } mp_obj_t mp_stream_close(mp_obj_t stream) { const mp_stream_p_t *stream_p = mp_get_stream(stream); int error; mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); if (res == MP_STREAM_ERROR) { mp_raise_OSError(error); } return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_close_obj, mp_stream_close); STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) { struct mp_stream_seek_t seek_s; // TODO: Could be uint64 seek_s.offset = mp_obj_get_int(args[1]); seek_s.whence = SEEK_SET; if (n_args == 3) { seek_s.whence = mp_obj_get_int(args[2]); } // In POSIX, it's error to seek before end of stream, we enforce it here. if (seek_s.whence == SEEK_SET && seek_s.offset < 0) { mp_raise_OSError(MP_EINVAL); } const mp_stream_p_t *stream_p = mp_get_stream(args[0]); int error; mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); if (res == MP_STREAM_ERROR) { mp_raise_OSError(error); } // TODO: Could be uint64 return mp_obj_new_int_from_uint(seek_s.offset); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj, 2, 3, stream_seek); STATIC mp_obj_t stream_tell(mp_obj_t self) { mp_obj_t offset = MP_OBJ_NEW_SMALL_INT(0); mp_obj_t whence = MP_OBJ_NEW_SMALL_INT(SEEK_CUR); const mp_obj_t args[3] = {self, offset, whence}; return stream_seek(3, args); } MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_tell_obj, stream_tell); STATIC mp_obj_t stream_flush(mp_obj_t self) { const mp_stream_p_t *stream_p = mp_get_stream(self); int error; mp_uint_t res = stream_p->ioctl(self, MP_STREAM_FLUSH, 0, &error); if (res == MP_STREAM_ERROR) { mp_raise_OSError(error); } return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_flush_obj, stream_flush); STATIC mp_obj_t stream_ioctl(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo; uintptr_t val = 0; if (n_args > 2) { if (mp_get_buffer(args[2], &bufinfo, MP_BUFFER_WRITE)) { val = (uintptr_t)bufinfo.buf; } else { val = mp_obj_get_int_truncated(args[2]); } } const mp_stream_p_t *stream_p = mp_get_stream(args[0]); int error; mp_uint_t res = stream_p->ioctl(args[0], mp_obj_get_int(args[1]), val, &error); if (res == MP_STREAM_ERROR) { mp_raise_OSError(error); } return mp_obj_new_int(res); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_ioctl_obj, 2, 3, stream_ioctl); #if MICROPY_STREAMS_POSIX_API /* * POSIX-like functions * * These functions have POSIX-compatible signature (except for "void *stream" * first argument instead of "int fd"). They are useful to port existing * POSIX-compatible software to work with MicroPython streams. */ #include ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len) { mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = o->type->protocol; mp_uint_t out_sz = stream_p->write(MP_OBJ_FROM_PTR(stream), buf, len, &errno); if (out_sz == MP_STREAM_ERROR) { return -1; } else { return out_sz; } } ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) { mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = o->type->protocol; mp_uint_t out_sz = stream_p->read(MP_OBJ_FROM_PTR(stream), buf, len, &errno); if (out_sz == MP_STREAM_ERROR) { return -1; } else { return out_sz; } } off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) { const mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = o->type->protocol; struct mp_stream_seek_t seek_s; seek_s.offset = offset; seek_s.whence = whence; mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &errno); if (res == MP_STREAM_ERROR) { return -1; } return seek_s.offset; } int mp_stream_posix_fsync(void *stream) { mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = o->type->protocol; mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_FLUSH, 0, &errno); if (res == MP_STREAM_ERROR) { return -1; } return res; } #endif /* * Copyright (c) 2014-2017 Paul Sokolovsky * Copyright (c) 2014-2019 Damien P. George */ #include #include #include #include #include #include "py/binary.h" #include "py/smallint.h" #include "py/objint.h" #include "py/runtime.h" // Helpers to work with binary-encoded data #ifndef alignof #define alignof(type) offsetof(struct { char c; type t; }, t) #endif size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { size_t size = 0; int align = 1; switch (struct_type) { case '<': case '>': switch (val_type) { case 'b': case 'B': size = 1; break; case 'h': case 'H': size = 2; break; case 'i': case 'I': size = 4; break; case 'l': case 'L': size = 4; break; case 'q': case 'Q': size = 8; break; case 'P': case 'O': case 'S': size = sizeof(void *); break; case 'f': size = sizeof(float); break; case 'd': size = sizeof(double); break; } break; case '@': { // TODO: // The simplest heuristic for alignment is to align by value // size, but that doesn't work for "bigger than int" types, // for example, long long may very well have long alignment // So, we introduce separate alignment handling, but having // formal support for that is different from actually supporting // particular (or any) ABI. switch (val_type) { case BYTEARRAY_TYPECODE: case 'b': case 'B': align = size = 1; break; case 'h': case 'H': align = alignof(short); size = sizeof(short); break; case 'i': case 'I': align = alignof(int); size = sizeof(int); break; case 'l': case 'L': align = alignof(long); size = sizeof(long); break; case 'q': case 'Q': align = alignof(long long); size = sizeof(long long); break; case 'P': case 'O': case 'S': align = alignof(void *); size = sizeof(void *); break; case 'f': align = alignof(float); size = sizeof(float); break; case 'd': align = alignof(double); size = sizeof(double); break; } } } if (size == 0) { mp_raise_ValueError(MP_ERROR_TEXT("bad typecode")); } if (palign != NULL) { *palign = align; } return size; } mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { mp_int_t val = 0; switch (typecode) { case 'b': val = ((signed char *)p)[index]; break; case BYTEARRAY_TYPECODE: case 'B': val = ((unsigned char *)p)[index]; break; case 'h': val = ((short *)p)[index]; break; case 'H': val = ((unsigned short *)p)[index]; break; case 'i': return mp_obj_new_int(((int *)p)[index]); case 'I': return mp_obj_new_int_from_uint(((unsigned int *)p)[index]); case 'l': return mp_obj_new_int(((long *)p)[index]); case 'L': return mp_obj_new_int_from_uint(((unsigned long *)p)[index]); #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE case 'q': return mp_obj_new_int_from_ll(((long long *)p)[index]); case 'Q': return mp_obj_new_int_from_ull(((unsigned long long *)p)[index]); #endif #if MICROPY_PY_BUILTINS_FLOAT case 'f': return mp_obj_new_float_from_f(((float *)p)[index]); case 'd': return mp_obj_new_float_from_d(((double *)p)[index]); #endif // Extension to CPython: array of objects case 'O': return ((mp_obj_t *)p)[index]; // Extension to CPython: array of pointers case 'P': return mp_obj_new_int((mp_int_t)(uintptr_t)((void **)p)[index]); } return MP_OBJ_NEW_SMALL_INT(val); } // The long long type is guaranteed to hold at least 64 bits, and size is at // most 8 (for q and Q), so we will always be able to parse the given data // and fit it into a long long. long long mp_binary_get_int(size_t size, bool is_signed, bool big_endian, const byte *src) { int delta; if (!big_endian) { delta = -1; src += size - 1; } else { delta = 1; } unsigned long long val = 0; if (is_signed && *src & 0x80) { val = -1; } for (uint i = 0; i < size; i++) { val <<= 8; val |= *src; src += delta; } return val; } #define is_signed(typecode) (typecode > 'Z') mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte **ptr) { byte *p = *ptr; size_t align; size_t size = mp_binary_get_size(struct_type, val_type, &align); if (struct_type == '@') { // Align p relative to p_base p = p_base + (uintptr_t)MP_ALIGN(p - p_base, align); #if MP_ENDIANNESS_LITTLE struct_type = '<'; #else struct_type = '>'; #endif } *ptr = p + size; long long val = mp_binary_get_int(size, is_signed(val_type), (struct_type == '>'), p); if (val_type == 'O') { return (mp_obj_t)(mp_uint_t)val; } else if (val_type == 'S') { const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val; return mp_obj_new_str(s_val, strlen(s_val)); #if MICROPY_PY_BUILTINS_FLOAT } else if (val_type == 'f') { union { uint32_t i; float f; } fpu = {val}; return mp_obj_new_float_from_f(fpu.f); } else if (val_type == 'd') { union { uint64_t i; double f; } fpu = {val}; return mp_obj_new_float_from_d(fpu.f); #endif } else if (is_signed(val_type)) { if ((long long)MP_SMALL_INT_MIN <= val && val <= (long long)MP_SMALL_INT_MAX) { return mp_obj_new_int((mp_int_t)val); } else { return mp_obj_new_int_from_ll(val); } } else { if ((unsigned long long)val <= (unsigned long long)MP_SMALL_INT_MAX) { return mp_obj_new_int_from_uint((mp_uint_t)val); } else { return mp_obj_new_int_from_ull(val); } } } void mp_binary_set_int(size_t val_sz, bool big_endian, byte *dest, mp_uint_t val) { if (MP_ENDIANNESS_LITTLE && !big_endian) { memcpy(dest, &val, val_sz); } else if (MP_ENDIANNESS_BIG && big_endian) { // only copy the least-significant val_sz bytes memcpy(dest, (byte *)&val + sizeof(mp_uint_t) - val_sz, val_sz); } else { const byte *src; if (MP_ENDIANNESS_LITTLE) { src = (const byte *)&val + val_sz; } else { src = (const byte *)&val + sizeof(mp_uint_t); } while (val_sz--) { *dest++ = *--src; } } } void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p_base, byte **ptr) { byte *p = *ptr; size_t align; size_t size = mp_binary_get_size(struct_type, val_type, &align); if (struct_type == '@') { // Align p relative to p_base p = p_base + (uintptr_t)MP_ALIGN(p - p_base, align); if (MP_ENDIANNESS_LITTLE) { struct_type = '<'; } else { struct_type = '>'; } } *ptr = p + size; mp_uint_t val; switch (val_type) { case 'O': val = (mp_uint_t)val_in; break; #if MICROPY_PY_BUILTINS_FLOAT case 'f': { union { uint32_t i; float f; } fp_sp; fp_sp.f = mp_obj_get_float_to_f(val_in); val = fp_sp.i; break; } case 'd': { union { uint64_t i64; uint32_t i32[2]; double f; } fp_dp; fp_dp.f = mp_obj_get_float_to_d(val_in); if (MP_BYTES_PER_OBJ_WORD == 8) { val = fp_dp.i64; } else { int be = struct_type == '>'; mp_binary_set_int(sizeof(uint32_t), be, p, fp_dp.i32[MP_ENDIANNESS_BIG ^ be]); p += sizeof(uint32_t); val = fp_dp.i32[MP_ENDIANNESS_LITTLE ^ be]; } break; } #endif default: #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (mp_obj_is_type(val_in, &mp_type_int)) { mp_obj_int_to_bytes_impl(val_in, struct_type == '>', size, p); return; } #endif val = mp_obj_get_int(val_in); // zero/sign extend if needed if (MP_BYTES_PER_OBJ_WORD < 8 && size > sizeof(val)) { int c = (mp_int_t)val < 0 ? 0xff : 0x00; memset(p, c, size); if (struct_type == '>') { p += size - sizeof(val); } } break; } mp_binary_set_int(MIN((size_t)size, sizeof(val)), struct_type == '>', p, val); } void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_in) { switch (typecode) { #if MICROPY_PY_BUILTINS_FLOAT case 'f': ((float *)p)[index] = mp_obj_get_float_to_f(val_in); break; case 'd': ((double *)p)[index] = mp_obj_get_float_to_d(val_in); break; #endif // Extension to CPython: array of objects case 'O': ((mp_obj_t *)p)[index] = val_in; break; default: #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (mp_obj_is_type(val_in, &mp_type_int)) { size_t size = mp_binary_get_size('@', typecode, NULL); mp_obj_int_to_bytes_impl(val_in, MP_ENDIANNESS_BIG, size, (uint8_t *)p + index * size); return; } #endif mp_binary_set_val_array_from_int(typecode, p, index, mp_obj_get_int(val_in)); } } void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val) { switch (typecode) { case 'b': ((signed char *)p)[index] = val; break; case BYTEARRAY_TYPECODE: case 'B': ((unsigned char *)p)[index] = val; break; case 'h': ((short *)p)[index] = val; break; case 'H': ((unsigned short *)p)[index] = val; break; case 'i': ((int *)p)[index] = val; break; case 'I': ((unsigned int *)p)[index] = val; break; case 'l': ((long *)p)[index] = val; break; case 'L': ((unsigned long *)p)[index] = val; break; #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE case 'q': ((long long *)p)[index] = val; break; case 'Q': ((unsigned long long *)p)[index] = val; break; #endif #if MICROPY_PY_BUILTINS_FLOAT case 'f': ((float *)p)[index] = (float)val; break; case 'd': ((double *)p)[index] = (double)val; break; #endif // Extension to CPython: array of pointers case 'P': ((void **)p)[index] = (void *)(uintptr_t)val; break; } } /* * Copyright (c) 2013-2019 Damien P. George * Copyright (c) 2014 Paul Sokolovsky * Copyright (c) 2021 Jim Mussared */ #ifdef __IPU__ #include #else #include #endif #include #include #include "py/compile.h" #include "py/objmodule.h" #include "py/persistentcode.h" #include "py/runtime.h" #include "py/builtin.h" #include "py/frozenmod.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif #if MICROPY_ENABLE_EXTERNAL_IMPORT // Must be a string of one byte. #define PATH_SEP_CHAR "/" // Virtual sys.path entry that maps to the frozen modules. #define MP_FROZEN_PATH_PREFIX ".frozen/" bool mp_obj_is_package(mp_obj_t module) { mp_obj_t dest[2]; mp_load_method_maybe(module, MP_QSTR___path__, dest); return dest[0] != MP_OBJ_NULL; } // Wrapper for mp_import_stat (which is provided by the port, and typically // uses mp_vfs_import_stat) to also search frozen modules. Given an exact // path to a file or directory (e.g. "foo/bar", foo/bar.py" or "foo/bar.mpy"), // will return whether the path is a file, directory, or doesn't exist. STATIC mp_import_stat_t stat_path_or_frozen(const char *path) { #if MICROPY_MODULE_FROZEN // Only try and load as a frozen module if it starts with .frozen/. const int frozen_path_prefix_len = strlen(MP_FROZEN_PATH_PREFIX); if (strncmp(path, MP_FROZEN_PATH_PREFIX, frozen_path_prefix_len) == 0) { return mp_find_frozen_module(path + frozen_path_prefix_len, NULL, NULL); } #endif return mp_import_stat(path); } // Given a path to a .py file, try and find this path as either a .py or .mpy // in either the filesystem or frozen modules. STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { return stat; } #if MICROPY_PERSISTENT_CODE_LOAD // Didn't find .py -- try the .mpy instead by inserting an 'm' into the '.py'. vstr_ins_byte(path, path->len - 2, 'm'); stat = stat_path_or_frozen(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { return stat; } #endif return MP_IMPORT_STAT_NO_EXIST; } // Given an import path (e.g. "foo/bar"), try and find "foo/bar" (a directory) // or "foo/bar.(m)py" in either the filesystem or frozen modules. STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path)); DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); if (stat == MP_IMPORT_STAT_DIR) { return stat; } // not a directory, add .py and try as a file vstr_add_str(path, ".py"); return stat_file_py_or_mpy(path); } // Given a top-level module, try and find it in each of the sys.path entries // via stat_dir_or_file. STATIC mp_import_stat_t stat_top_level_dir_or_file(qstr mod_name, vstr_t *dest) { DEBUG_printf("stat_top_level_dir_or_file: '%s'\n", qstr_str(mod_name)); #if MICROPY_PY_SYS size_t path_num; mp_obj_t *path_items; mp_obj_list_get(mp_sys_path, &path_num, &path_items); if (path_num > 0) { // go through each path looking for a directory or file for (size_t i = 0; i < path_num; i++) { vstr_reset(dest); size_t p_len; const char *p = mp_obj_str_get_data(path_items[i], &p_len); if (p_len > 0) { vstr_add_strn(dest, p, p_len); vstr_add_char(dest, PATH_SEP_CHAR[0]); } vstr_add_str(dest, qstr_str(mod_name)); mp_import_stat_t stat = stat_dir_or_file(dest); if (stat != MP_IMPORT_STAT_NO_EXIST) { return stat; } } // could not find a directory or file return MP_IMPORT_STAT_NO_EXIST; } #endif // mp_sys_path is empty (or not enabled), so just stat the given path // directly. vstr_add_str(dest, qstr_str(mod_name)); return stat_dir_or_file(dest); } #if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER STATIC void do_load_from_lexer(mp_module_context_t *context, mp_lexer_t *lex) { #if MICROPY_PY___FILE__ qstr source_name = lex->source_name; mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); #endif // parse, compile and execute the module in its context mp_obj_dict_t *mod_globals = context->module.globals; mp_parse_compile_execute(lex, MP_PARSE_FILE_INPUT, mod_globals, mod_globals); } #endif #if (MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD) || MICROPY_MODULE_FROZEN_MPY STATIC void do_execute_raw_code(mp_module_context_t *context, const mp_raw_code_t *rc, const mp_module_context_t *mc, const char *source_name) { (void)source_name; #if MICROPY_PY___FILE__ mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); #endif // execute the module in its context mp_obj_dict_t *mod_globals = context->module.globals; // save context mp_obj_dict_t *volatile old_globals = mp_globals_get(); mp_obj_dict_t *volatile old_locals = mp_locals_get(); // set new context mp_globals_set(mod_globals); mp_locals_set(mod_globals); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t module_fun = mp_make_function_from_raw_code(rc, mc, NULL); mp_call_function_0(module_fun); // finish nlr block, restore context nlr_pop(); mp_globals_set(old_globals); mp_locals_set(old_locals); } else { // exception; restore context and re-raise same exception mp_globals_set(old_globals); mp_locals_set(old_locals); nlr_jump(nlr.ret_val); } } #endif STATIC void do_load(mp_module_context_t *module_obj, vstr_t *file) { #if MICROPY_MODULE_FROZEN || MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) const char *file_str = vstr_null_terminated_str(file); #endif // If we support frozen modules (either as str or mpy) then try to find the // requested filename in the list of frozen module filenames. #if MICROPY_MODULE_FROZEN void *modref; int frozen_type; const int frozen_path_prefix_len = strlen(MP_FROZEN_PATH_PREFIX); if (strncmp(file_str, MP_FROZEN_PATH_PREFIX, frozen_path_prefix_len) == 0) { mp_find_frozen_module(file_str + frozen_path_prefix_len, &frozen_type, &modref); // If we support frozen str modules and the compiler is enabled, and we // found the filename in the list of frozen files, then load and execute it. #if MICROPY_MODULE_FROZEN_STR if (frozen_type == MP_FROZEN_STR) { do_load_from_lexer(module_obj, modref); return; } #endif // If we support frozen mpy modules and we found a corresponding file (and // its data) in the list of frozen files, execute it. #if MICROPY_MODULE_FROZEN_MPY if (frozen_type == MP_FROZEN_MPY) { const mp_frozen_module_t *frozen = modref; module_obj->constants = frozen->constants; do_execute_raw_code(module_obj, frozen->rc, module_obj, file_str + frozen_path_prefix_len); return; } #endif } #endif // MICROPY_MODULE_FROZEN // If we support loading .mpy files then check if the file extension is of // the correct format and, if so, load and execute the file. #if MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD if (file_str[file->len - 3] == 'm') { mp_compiled_module_t cm = mp_raw_code_load_file(file_str, module_obj); do_execute_raw_code(module_obj, cm.rc, cm.context, file_str); return; } #endif // If we can compile scripts then load the file and compile and execute it. #if MICROPY_ENABLE_COMPILER { mp_lexer_t *lex = mp_lexer_new_from_file(file_str); do_load_from_lexer(module_obj, lex); return; } #else // If we get here then the file was not frozen and we can't compile scripts. mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("script compilation not supported")); #endif } // Convert a relative (to the current module) import, going up "level" levels, // into an absolute import. STATIC void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len) { // What we want to do here is to take the name of the current module, // remove trailing components, and concatenate the passed-in // module name. // For example, level=3, module_name="foo.bar", __name__="a.b.c.d" --> "a.foo.bar" // "Relative imports use a module's __name__ attribute to determine that // module's position in the package hierarchy." // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name mp_obj_t current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); assert(current_module_name_obj != MP_OBJ_NULL); #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT && MICROPY_CPYTHON_COMPAT if (MP_OBJ_QSTR_VALUE(current_module_name_obj) == MP_QSTR___main__) { // This is a module loaded by -m command-line switch (e.g. unix port), // and so its __name__ has been set to "__main__". Get its real name // that we stored during import in the __main__ attribute. current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); } #endif // If we have a __path__ in the globals dict, then we're a package. bool is_pkg = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); #if DEBUG_PRINT DEBUG_printf("Current module/package: "); mp_obj_print_helper(MICROPY_DEBUG_PRINTER, current_module_name_obj, PRINT_REPR); DEBUG_printf(", is_package: %d", is_pkg); DEBUG_printf("\n"); #endif size_t current_module_name_len; const char *current_module_name = mp_obj_str_get_data(current_module_name_obj, ¤t_module_name_len); const char *p = current_module_name + current_module_name_len; if (is_pkg) { // If we're evaluating relative to a package, then take off one fewer // level (i.e. the relative search starts inside the package, rather // than as a sibling of the package). --level; } // Walk back 'level' dots (or run out of path). while (level && p > current_module_name) { if (*--p == '.') { --level; } } // We must have some component left over to import from. if (p == current_module_name) { mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("can't perform relative import")); } // New length is len("."). Note: might be one byte // more than we need if module_name is empty (for the extra . we will // append). uint new_module_name_len = (size_t)(p - current_module_name) + 1 + *module_name_len; char *new_mod = mp_local_alloc(new_module_name_len); memcpy(new_mod, current_module_name, p - current_module_name); // Only append "." if there was one). if (*module_name_len != 0) { new_mod[p - current_module_name] = '.'; memcpy(new_mod + (p - current_module_name) + 1, *module_name, *module_name_len); } else { --new_module_name_len; } // Copy into a QSTR. qstr new_mod_q = qstr_from_strn(new_mod, new_module_name_len); mp_local_free(new_mod); DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); *module_name = qstr_str(new_mod_q); *module_name_len = new_module_name_len; } // Load a module at the specified absolute path, possibly as a submodule of the given outer module. // full_mod_name: The full absolute path to this module (e.g. "foo.bar.baz"). // level_mod_name: The final component of the path (e.g. "baz"). // outer_module_obj: The parent module (we need to store this module as an // attribute on it) (or MP_OBJ_NULL for top-level). // path: The filesystem path where we found the parent module // (or empty for a top level module). // override_main: Whether to set the __name__ to "__main__" (and use __main__ // for the actual path). STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, mp_obj_t outer_module_obj, vstr_t *path, bool override_main) { mp_import_stat_t stat = MP_IMPORT_STAT_NO_EXIST; // Exact-match of built-in (or already-loaded) takes priority. mp_obj_t module_obj = mp_module_get_loaded_or_builtin(full_mod_name); // Even if we find the module, go through the motions of searching for it // because we may actually be in the process of importing a sub-module. // So we need to (re-)find the correct path to be finding the sub-module // on the next iteration of process_import_at_level. if (outer_module_obj == MP_OBJ_NULL) { DEBUG_printf("Searching for top-level module\n"); // First module in the dotted-name; search for a directory or file // relative to all the locations in sys.path. stat = stat_top_level_dir_or_file(full_mod_name, path); // If the module "foo" doesn't exist on the filesystem, and it's not a // builtin, try and find "ufoo" as a built-in. (This feature was // formerly known as "weak links"). #if MICROPY_MODULE_WEAK_LINKS if (stat == MP_IMPORT_STAT_NO_EXIST && module_obj == MP_OBJ_NULL) { char *umodule_buf = vstr_str(path); umodule_buf[0] = 'u'; strcpy(umodule_buf + 1, qstr_str(level_mod_name)); qstr umodule_name = qstr_from_str(umodule_buf); module_obj = mp_module_get_builtin(umodule_name); } #endif } else { DEBUG_printf("Searching for sub-module\n"); // Add the current part of the module name to the path. vstr_add_char(path, PATH_SEP_CHAR[0]); vstr_add_str(path, qstr_str(level_mod_name)); // Because it's not top level, we already know which path the parent was found in. stat = stat_dir_or_file(path); } DEBUG_printf("Current path: %.*s\n", (int)vstr_len(path), vstr_str(path)); if (module_obj == MP_OBJ_NULL) { // Not a built-in and not already-loaded. if (stat == MP_IMPORT_STAT_NO_EXIST) { // And the file wasn't found -- fail. #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); #else mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), full_mod_name); #endif } // Not a built-in but found on the filesystem, try and load it. DEBUG_printf("Found path: %.*s\n", (int)vstr_len(path), vstr_str(path)); // Prepare for loading from the filesystem. Create a new shell module. module_obj = mp_obj_new_module(full_mod_name); #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT // If this module is being loaded via -m on unix, then // override __name__ to "__main__". Do this only for *modules* // however - packages never have their names replaced, instead // they're -m'ed using a special __main__ submodule in them. (This all // apparently is done to not touch the package name itself, which is // important for future imports). if (override_main && stat != MP_IMPORT_STAT_DIR) { mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); #if MICROPY_CPYTHON_COMPAT // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); // Store real name in "__main__" attribute. Need this for // resolving relative imports later. "__main__ was chosen // semi-randonly, to reuse existing qstr's. mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(full_mod_name)); #endif } #endif // MICROPY_MODULE_OVERRIDE_MAIN_IMPORT if (stat == MP_IMPORT_STAT_DIR) { // Directory -- execute "path/__init__.py". DEBUG_printf("%.*s is dir\n", (int)vstr_len(path), vstr_str(path)); // Store the __path__ attribute onto this module. // https://docs.python.org/3/reference/import.html // "Specifically, any module that contains a __path__ attribute is considered a package." mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(path), vstr_len(path))); size_t orig_path_len = path->len; vstr_add_str(path, PATH_SEP_CHAR "__init__.py"); if (stat_file_py_or_mpy(path) == MP_IMPORT_STAT_FILE) { do_load(MP_OBJ_TO_PTR(module_obj), path); } else { // No-op. Nothing to load. // mp_warning("%s is imported as namespace package", vstr_str(&path)); } // Remove /__init__.py suffix. path->len = orig_path_len; } else { // MP_IMPORT_STAT_FILE // File -- execute "path.(m)py". do_load(MP_OBJ_TO_PTR(module_obj), path); // Note: This should be the last component in the import path. If // there are remaining components then it's an ImportError // because the current path(the module that was just loaded) is // not a package. This will be caught on the next iteration // because the file will not exist. } } if (outer_module_obj != MP_OBJ_NULL) { // If it's a sub-module (not a built-in one), then make it available on // the parent module. mp_store_attr(outer_module_obj, level_mod_name, module_obj); } return module_obj; } mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { #if DEBUG_PRINT DEBUG_printf("__import__:\n"); for (size_t i = 0; i < n_args; i++) { DEBUG_printf(" "); mp_obj_print_helper(MICROPY_DEBUG_PRINTER, args[i], PRINT_REPR); DEBUG_printf("\n"); } #endif // This is the import path, with any leading dots stripped. // "import foo.bar" --> module_name="foo.bar" // "from foo.bar import baz" --> module_name="foo.bar" // "from . import foo" --> module_name="" // "from ...foo.bar import baz" --> module_name="foo.bar" mp_obj_t module_name_obj = args[0]; // These are the imported names. // i.e. "from foo.bar import baz, zap" --> fromtuple=("baz", "zap",) // Note: There's a special case on the Unix port, where this is set to mp_const_false which means that it's __main__. mp_obj_t fromtuple = mp_const_none; // Level is the number of leading dots in a relative import. // i.e. "from . import foo" --> level=1 // i.e. "from ...foo.bar import baz" --> level=3 mp_int_t level = 0; if (n_args >= 4) { fromtuple = args[3]; if (n_args >= 5) { level = MP_OBJ_SMALL_INT_VALUE(args[4]); if (level < 0) { mp_raise_ValueError(NULL); } } } size_t module_name_len; const char *module_name = mp_obj_str_get_data(module_name_obj, &module_name_len); if (level != 0) { // Turn "foo.bar" into ".foo.bar". evaluate_relative_import(level, &module_name, &module_name_len); } if (module_name_len == 0) { mp_raise_ValueError(NULL); } DEBUG_printf("Starting module search for '%s'\n", module_name); VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) mp_obj_t top_module_obj = MP_OBJ_NULL; mp_obj_t outer_module_obj = MP_OBJ_NULL; // Search for the end of each component. size_t current_component_start = 0; for (size_t i = 1; i <= module_name_len; i++) { if (i == module_name_len || module_name[i] == '.') { // The module name up to this depth (e.g. foo.bar.baz). qstr full_mod_name = qstr_from_strn(module_name, i); // The current level name (e.g. baz). qstr level_mod_name = qstr_from_strn(module_name + current_component_start, i - current_component_start); DEBUG_printf("Processing module: '%s' at level '%s'\n", qstr_str(full_mod_name), qstr_str(level_mod_name)); DEBUG_printf("Previous path: =%.*s=\n", (int)vstr_len(&path), vstr_str(&path)); #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT // On unix, if this is being loaded via -m (magic mp_const_false), // then handle that if it's the final component. bool override_main = (i == module_name_len && fromtuple == mp_const_false); #else bool override_main = false; #endif // Import this module. mp_obj_t module_obj = process_import_at_level(full_mod_name, level_mod_name, outer_module_obj, &path, override_main); // Set this as the parent module, and remember the top-level module if it's the first. outer_module_obj = module_obj; if (top_module_obj == MP_OBJ_NULL) { top_module_obj = module_obj; } current_component_start = i + 1; } } if (fromtuple != mp_const_none) { // If fromtuple is not empty, return leaf module return outer_module_obj; } else { // Otherwise, we need to return top-level package return top_module_obj; } } #else // MICROPY_ENABLE_EXTERNAL_IMPORT mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // Check that it's not a relative import if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) { mp_raise_NotImplementedError(MP_ERROR_TEXT("relative import")); } // Check if module already exists, and return it if it does qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); mp_obj_t module_obj = mp_module_get_loaded_or_builtin(module_name_qstr); if (module_obj != MP_OBJ_NULL) { return module_obj; } #if MICROPY_MODULE_WEAK_LINKS // Check if there is a weak link to this module char umodule_buf[MICROPY_ALLOC_PATH_MAX]; umodule_buf[0] = 'u'; strcpy(umodule_buf + 1, args[0]); qstr umodule_name_qstr = qstr_from_str(umodule_buf); module_obj = mp_module_get_loaded_or_builtin(umodule_name_qstr); if (module_obj != MP_OBJ_NULL) { return module_obj; } #endif // Couldn't find the module, so fail #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); #else mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), module_name_qstr); #endif } #endif // MICROPY_ENABLE_EXTERNAL_IMPORT MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin___import___obj, 1, 5, mp_builtin___import__); #include #include "py/objfun.h" #include "py/compile.h" #include "py/runtime.h" #include "py/builtin.h" #if MICROPY_PY_BUILTINS_COMPILE typedef struct _mp_obj_code_t { mp_obj_base_t base; mp_obj_t module_fun; } mp_obj_code_t; STATIC const mp_obj_type_t mp_type_code = { { &mp_type_type }, .name = MP_QSTR_code, }; STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj_dict_t *locals) { // save context and set new context mp_obj_dict_t *old_globals = mp_globals_get(); mp_obj_dict_t *old_locals = mp_locals_get(); mp_globals_set(globals); mp_locals_set(locals); // a bit of a hack: fun_bc will re-set globals, so need to make sure it's // the correct one if (mp_obj_is_type(self->module_fun, &mp_type_fun_bc)) { mp_obj_fun_bc_t *fun_bc = MP_OBJ_TO_PTR(self->module_fun); ((mp_module_context_t *)fun_bc->context)->module.globals = globals; } // execute code nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_function_0(self->module_fun); nlr_pop(); mp_globals_set(old_globals); mp_locals_set(old_locals); return ret; } else { // exception; restore context and re-raise same exception mp_globals_set(old_globals); mp_locals_set(old_locals); nlr_jump(nlr.ret_val); } } STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { (void)n_args; // get the source size_t str_len; const char *str = mp_obj_str_get_data(args[0], &str_len); // get the filename qstr filename = mp_obj_str_get_qstr(args[1]); // create the lexer mp_lexer_t *lex = mp_lexer_new_from_str_len(filename, str, str_len, 0); // get the compile mode qstr mode = mp_obj_str_get_qstr(args[2]); mp_parse_input_kind_t parse_input_kind; switch (mode) { case MP_QSTR_single: parse_input_kind = MP_PARSE_SINGLE_INPUT; break; case MP_QSTR_exec: parse_input_kind = MP_PARSE_FILE_INPUT; break; case MP_QSTR_eval: parse_input_kind = MP_PARSE_EVAL_INPUT; break; default: mp_raise_ValueError(MP_ERROR_TEXT("bad compile mode")); } mp_obj_code_t *code = m_new_obj(mp_obj_code_t); code->base.type = &mp_type_code; code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); return MP_OBJ_FROM_PTR(code); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_compile_obj, 3, 6, mp_builtin_compile); #endif // MICROPY_PY_BUILTINS_COMPILE #if MICROPY_PY_BUILTINS_EVAL_EXEC STATIC mp_obj_t eval_exec_helper(size_t n_args, const mp_obj_t *args, mp_parse_input_kind_t parse_input_kind) { // work out the context mp_obj_dict_t *globals = mp_globals_get(); mp_obj_dict_t *locals = mp_locals_get(); for (size_t i = 1; i < 3 && i < n_args; ++i) { if (args[i] != mp_const_none) { if (!mp_obj_is_type(args[i], &mp_type_dict)) { mp_raise_TypeError(NULL); } locals = MP_OBJ_TO_PTR(args[i]); if (i == 1) { globals = locals; } } } #if MICROPY_PY_BUILTINS_COMPILE if (mp_obj_is_type(args[0], &mp_type_code)) { return code_execute(MP_OBJ_TO_PTR(args[0]), globals, locals); } #endif // Extract the source code. mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); // create the lexer // MP_PARSE_SINGLE_INPUT is used to indicate a file input mp_lexer_t *lex; if (MICROPY_PY_BUILTINS_EXECFILE && parse_input_kind == MP_PARSE_SINGLE_INPUT) { lex = mp_lexer_new_from_file(bufinfo.buf); parse_input_kind = MP_PARSE_FILE_INPUT; } else { lex = mp_lexer_new_from_str_len(MP_QSTR__lt_string_gt_, bufinfo.buf, bufinfo.len, 0); } return mp_parse_compile_execute(lex, parse_input_kind, globals, locals); } STATIC mp_obj_t mp_builtin_eval(size_t n_args, const mp_obj_t *args) { return eval_exec_helper(n_args, args, MP_PARSE_EVAL_INPUT); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_eval_obj, 1, 3, mp_builtin_eval); STATIC mp_obj_t mp_builtin_exec(size_t n_args, const mp_obj_t *args) { return eval_exec_helper(n_args, args, MP_PARSE_FILE_INPUT); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_exec_obj, 1, 3, mp_builtin_exec); #endif // MICROPY_PY_BUILTINS_EVAL_EXEC #if MICROPY_PY_BUILTINS_EXECFILE STATIC mp_obj_t mp_builtin_execfile(size_t n_args, const mp_obj_t *args) { // MP_PARSE_SINGLE_INPUT is used to indicate a file input return eval_exec_helper(n_args, args, MP_PARSE_SINGLE_INPUT); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_execfile_obj, 1, 3, mp_builtin_execfile); #endif #ifndef __IPU__ #include #endif #include #include "py/builtin.h" #include "py/objmodule.h" #if MICROPY_PY_BUILTINS_HELP const char mp_help_default_text[] = "Welcome to MicroPython!\n" "\n" "For online docs please visit http://docs.micropython.org/\n" "\n" "Control commands:\n" " CTRL-A -- on a blank line, enter raw REPL mode\n" " CTRL-B -- on a blank line, enter normal REPL mode\n" " CTRL-C -- interrupt a running program\n" " CTRL-D -- on a blank line, exit or do a soft reset\n" " CTRL-E -- on a blank line, enter paste mode\n" "\n" "For further help on a specific object, type help(obj)\n" ; STATIC void mp_help_print_info_about_object(mp_obj_t name_o, mp_obj_t value) { mp_print_str(MP_PYTHON_PRINTER, " "); mp_obj_print(name_o, PRINT_STR); mp_print_str(MP_PYTHON_PRINTER, " -- "); mp_obj_print(value, PRINT_STR); mp_print_str(MP_PYTHON_PRINTER, "\n"); } #if MICROPY_PY_BUILTINS_HELP_MODULES STATIC void mp_help_add_from_map(mp_obj_t list, const mp_map_t *map) { for (size_t i = 0; i < map->alloc; i++) { if (mp_map_slot_is_filled(map, i)) { mp_obj_list_append(list, map->table[i].key); } } } #if MICROPY_MODULE_FROZEN STATIC void mp_help_add_from_names(mp_obj_t list, const char *name) { while (*name) { size_t len = strlen(name); // name should end in '.py' and we strip it off mp_obj_list_append(list, mp_obj_new_str(name, len - 3)); name += len + 1; } } #endif STATIC void mp_help_print_modules(void) { mp_obj_t list = mp_obj_new_list(0, NULL); mp_help_add_from_map(list, &mp_builtin_module_map); #if MICROPY_MODULE_FROZEN extern const char mp_frozen_names[]; mp_help_add_from_names(list, mp_frozen_names); #endif // sort the list so it's printed in alphabetical order mp_obj_list_sort(1, &list, (mp_map_t *)&mp_const_empty_map); // print the list of modules in a column-first order #define NUM_COLUMNS (4) #define COLUMN_WIDTH (18) size_t len; mp_obj_t *items; mp_obj_list_get(list, &len, &items); unsigned int num_rows = (len + NUM_COLUMNS - 1) / NUM_COLUMNS; for (unsigned int i = 0; i < num_rows; ++i) { unsigned int j = i; for (;;) { int l = mp_print_str(MP_PYTHON_PRINTER, mp_obj_str_get_str(items[j])); j += num_rows; if (j >= len) { break; } int gap = COLUMN_WIDTH - l; while (gap < 1) { gap += COLUMN_WIDTH; } while (gap--) { mp_print_str(MP_PYTHON_PRINTER, " "); } } mp_print_str(MP_PYTHON_PRINTER, "\n"); } #if MICROPY_ENABLE_EXTERNAL_IMPORT // let the user know there may be other modules available from the filesystem mp_print_str(MP_PYTHON_PRINTER, "Plus any modules on the filesystem\n"); #endif } #endif STATIC void mp_help_print_obj(const mp_obj_t obj) { #if MICROPY_PY_BUILTINS_HELP_MODULES if (obj == MP_OBJ_NEW_QSTR(MP_QSTR_modules)) { mp_help_print_modules(); return; } #endif const mp_obj_type_t *type = mp_obj_get_type(obj); // try to print something sensible about the given object mp_print_str(MP_PYTHON_PRINTER, "object "); mp_obj_print(obj, PRINT_STR); mp_printf(MP_PYTHON_PRINTER, " is of type %q\n", type->name); mp_map_t *map = NULL; if (type == &mp_type_module) { map = &mp_obj_module_get_globals(obj)->map; } else { if (type == &mp_type_type) { type = MP_OBJ_TO_PTR(obj); } if (type->locals_dict != NULL) { map = &type->locals_dict->map; } } if (map != NULL) { for (uint i = 0; i < map->alloc; i++) { if (map->table[i].key != MP_OBJ_NULL) { mp_help_print_info_about_object(map->table[i].key, map->table[i].value); } } } } STATIC mp_obj_t mp_builtin_help(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // print a general help message mp_print_str(MP_PYTHON_PRINTER, MICROPY_PY_BUILTINS_HELP_TEXT); } else { // try to print something sensible about the given object mp_help_print_obj(args[0]); } return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_help_obj, 0, 1, mp_builtin_help); #endif // MICROPY_PY_BUILTINS_HELP #include "py/builtin.h" #if MICROPY_PY_ARRAY STATIC const mp_rom_map_elem_t mp_module_array_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uarray) }, { MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&mp_type_array) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_array_globals, mp_module_array_globals_table); const mp_obj_module_t mp_module_uarray = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_array_globals, }; MP_REGISTER_MODULE(MP_QSTR_uarray, mp_module_uarray, MICROPY_PY_ARRAY); #endif #ifndef __IPU__ #include #endif #include #include "py/smallint.h" #include "py/objint.h" #include "py/objstr.h" #include "py/objtype.h" #include "py/runtime.h" #include "py/builtin.h" #include "py/stream.h" #if MICROPY_PY_BUILTINS_FLOAT #include #endif #if MICROPY_PY_IO extern struct _mp_dummy_t mp_sys_stdout_obj; // type is irrelevant, just need pointer #endif // args[0] is function from class body // args[1] is class name // args[2:] are base objects STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) { assert(2 <= n_args); // set the new classes __locals__ object mp_obj_dict_t *old_locals = mp_locals_get(); mp_obj_t class_locals = mp_obj_new_dict(0); mp_locals_set(MP_OBJ_TO_PTR(class_locals)); // call the class code mp_obj_t cell = mp_call_function_0(args[0]); // restore old __locals__ object mp_locals_set(old_locals); // get the class type (meta object) from the base objects mp_obj_t meta; if (n_args == 2) { // no explicit bases, so use 'type' meta = MP_OBJ_FROM_PTR(&mp_type_type); } else { // use type of first base object meta = MP_OBJ_FROM_PTR(mp_obj_get_type(args[2])); } // TODO do proper metaclass resolution for multiple base objects // create the new class using a call to the meta object mp_obj_t meta_args[3]; meta_args[0] = args[1]; // class name meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases meta_args[2] = class_locals; // dict of members mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); // store into cell if neede if (cell != mp_const_none) { mp_obj_cell_set(cell, new_class); } return new_class; } MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj, 2, mp_builtin___build_class__); STATIC mp_obj_t mp_builtin_abs(mp_obj_t o_in) { return mp_unary_op(MP_UNARY_OP_ABS, o_in); } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_abs_obj, mp_builtin_abs); STATIC mp_obj_t mp_builtin_all(mp_obj_t o_in) { mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(o_in, &iter_buf); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { if (!mp_obj_is_true(item)) { return mp_const_false; } } return mp_const_true; } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_all_obj, mp_builtin_all); STATIC mp_obj_t mp_builtin_any(mp_obj_t o_in) { mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(o_in, &iter_buf); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { if (mp_obj_is_true(item)) { return mp_const_true; } } return mp_const_false; } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_any_obj, mp_builtin_any); STATIC mp_obj_t mp_builtin_bin(mp_obj_t o_in) { mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_b_brace_close_), o_in }; return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_bin_obj, mp_builtin_bin); STATIC mp_obj_t mp_builtin_callable(mp_obj_t o_in) { if (mp_obj_is_callable(o_in)) { return mp_const_true; } else { return mp_const_false; } } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_callable_obj, mp_builtin_callable); STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { #if MICROPY_PY_BUILTINS_STR_UNICODE mp_uint_t c = mp_obj_get_int(o_in); uint8_t str[4]; int len = 0; if (c < 0x80) { *str = c; len = 1; } else if (c < 0x800) { str[0] = (c >> 6) | 0xC0; str[1] = (c & 0x3F) | 0x80; len = 2; } else if (c < 0x10000) { str[0] = (c >> 12) | 0xE0; str[1] = ((c >> 6) & 0x3F) | 0x80; str[2] = (c & 0x3F) | 0x80; len = 3; } else if (c < 0x110000) { str[0] = (c >> 18) | 0xF0; str[1] = ((c >> 12) & 0x3F) | 0x80; str[2] = ((c >> 6) & 0x3F) | 0x80; str[3] = (c & 0x3F) | 0x80; len = 4; } else { mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(0x110000)")); } return mp_obj_new_str_via_qstr((char *)str, len); #else mp_int_t ord = mp_obj_get_int(o_in); if (0 <= ord && ord <= 0xff) { uint8_t str[1] = {ord}; return mp_obj_new_str_via_qstr((char *)str, 1); } else { mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(256)")); } #endif } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { mp_obj_t dir = mp_obj_new_list(0, NULL); if (n_args == 0) { // Make a list of names in the local namespace mp_obj_dict_t *dict = mp_locals_get(); for (size_t i = 0; i < dict->map.alloc; i++) { if (mp_map_slot_is_filled(&dict->map, i)) { mp_obj_list_append(dir, dict->map.table[i].key); } } } else { // n_args == 1 // Make a list of names in the given object // Implemented by probing all possible qstrs with mp_load_method_maybe size_t nqstr = QSTR_TOTAL(); for (size_t i = MP_QSTR_ + 1; i < nqstr; ++i) { mp_obj_t dest[2]; mp_load_method_protected(args[0], i, dest, false); if (dest[0] != MP_OBJ_NULL) { #if MICROPY_PY_ALL_SPECIAL_METHODS // Support for __dir__: see if we can dispatch to this special method // This relies on MP_QSTR__dir__ being first after MP_QSTR_ if (i == MP_QSTR___dir__ && dest[1] != MP_OBJ_NULL) { return mp_call_method_n_kw(0, 0, dest); } #endif mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i)); } } } return dir; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_divmod_obj, mp_builtin_divmod); STATIC mp_obj_t mp_builtin_hash(mp_obj_t o_in) { // result is guaranteed to be a (small) int return mp_unary_op(MP_UNARY_OP_HASH, o_in); } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hash_obj, mp_builtin_hash); STATIC mp_obj_t mp_builtin_hex(mp_obj_t o_in) { #if MICROPY_PY_BUILTINS_STR_OP_MODULO return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_x), o_in); #else mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_x_brace_close_), o_in }; return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); #endif } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hex_obj, mp_builtin_hex); #if MICROPY_PY_BUILTINS_INPUT #include "py/mphal.h" #include "shared/readline/readline.h" // A port can define mp_hal_readline if they want to use a custom function here #ifndef mp_hal_readline #define mp_hal_readline readline #endif STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { if (n_args == 1) { mp_obj_print(args[0], PRINT_STR); } vstr_t line; vstr_init(&line, 16); int ret = mp_hal_readline(&line, ""); if (ret == CHAR_CTRL_C) { mp_raise_type(&mp_type_KeyboardInterrupt); } if (line.len == 0 && ret == CHAR_CTRL_D) { mp_raise_type(&mp_type_EOFError); } return mp_obj_new_str_from_vstr(&mp_type_str, &line); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj, 0, 1, mp_builtin_input); #endif STATIC mp_obj_t mp_builtin_iter(mp_obj_t o_in) { return mp_getiter(o_in, NULL); } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter); #if MICROPY_PY_BUILTINS_MIN_MAX STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs, mp_uint_t op) { mp_map_elem_t *key_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_key), MP_MAP_LOOKUP); mp_map_elem_t *default_elem; mp_obj_t key_fn = key_elem == NULL ? MP_OBJ_NULL : key_elem->value; if (n_args == 1) { // given an iterable mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(args[0], &iter_buf); mp_obj_t best_key = MP_OBJ_NULL; mp_obj_t best_obj = MP_OBJ_NULL; mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { mp_obj_t key = key_fn == MP_OBJ_NULL ? item : mp_call_function_1(key_fn, item); if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { best_key = key; best_obj = item; } } if (best_obj == MP_OBJ_NULL) { default_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_default), MP_MAP_LOOKUP); if (default_elem != NULL) { best_obj = default_elem->value; } else { mp_raise_ValueError(MP_ERROR_TEXT("arg is an empty sequence")); } } return best_obj; } else { // given many args mp_obj_t best_key = MP_OBJ_NULL; mp_obj_t best_obj = MP_OBJ_NULL; for (size_t i = 0; i < n_args; i++) { mp_obj_t key = key_fn == MP_OBJ_NULL ? args[i] : mp_call_function_1(key_fn, args[i]); if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { best_key = key; best_obj = args[i]; } } return best_obj; } } STATIC mp_obj_t mp_builtin_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_MORE); } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_max_obj, 1, mp_builtin_max); STATIC mp_obj_t mp_builtin_min(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_LESS); } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_min_obj, 1, mp_builtin_min); #endif #if MICROPY_PY_BUILTINS_NEXT2 STATIC mp_obj_t mp_builtin_next(size_t n_args, const mp_obj_t *args) { if (n_args == 1) { mp_obj_t ret = mp_iternext_allow_raise(args[0]); if (ret == MP_OBJ_STOP_ITERATION) { mp_raise_StopIteration(MP_STATE_THREAD(stop_iteration_arg)); } else { return ret; } } else { mp_obj_t ret = mp_iternext(args[0]); return ret == MP_OBJ_STOP_ITERATION ? args[1] : ret; } } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); #else STATIC mp_obj_t mp_builtin_next(mp_obj_t o) { mp_obj_t ret = mp_iternext_allow_raise(o); if (ret == MP_OBJ_STOP_ITERATION) { mp_raise_StopIteration(MP_STATE_THREAD(stop_iteration_arg)); } else { return ret; } } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_next_obj, mp_builtin_next); #endif STATIC mp_obj_t mp_builtin_oct(mp_obj_t o_in) { #if MICROPY_PY_BUILTINS_STR_OP_MODULO return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_o), o_in); #else mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_o_brace_close_), o_in }; return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); #endif } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_oct_obj, mp_builtin_oct); STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { size_t len; const byte *str = (const byte *)mp_obj_str_get_data(o_in, &len); #if MICROPY_PY_BUILTINS_STR_UNICODE if (mp_obj_is_str(o_in)) { len = utf8_charlen(str, len); if (len == 1) { return mp_obj_new_int(utf8_get_char(str)); } } else #endif { // a bytes object, or a str without unicode support (don't sign extend the char) if (len == 1) { return MP_OBJ_NEW_SMALL_INT(str[0]); } } #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("ord expects a character")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("ord() expected a character, but string of length %d found"), (int)len); #endif } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { switch (n_args) { case 2: return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); default: #if !MICROPY_PY_BUILTINS_POW3 mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); #else return mp_obj_int_pow3(args[0], args[1], args[2]); #endif } } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); STATIC mp_obj_t mp_builtin_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_sep, ARG_end, ARG_file }; static const mp_arg_t allowed_args[] = { { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} }, { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} }, #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES { MP_QSTR_file, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_sys_stdout_obj)} }, #endif }; // parse args (a union is used to reduce the amount of C stack that is needed) union { mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; size_t len[2]; } u; mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, u.args); #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES mp_get_stream_raise(u.args[ARG_file].u_obj, MP_STREAM_OP_WRITE); mp_print_t print = {MP_OBJ_TO_PTR(u.args[ARG_file].u_obj), mp_stream_write_adaptor}; #endif // extract the objects first because we are going to use the other part of the union mp_obj_t sep = u.args[ARG_sep].u_obj; mp_obj_t end = u.args[ARG_end].u_obj; const char *sep_data = mp_obj_str_get_data(sep, &u.len[0]); const char *end_data = mp_obj_str_get_data(end, &u.len[1]); for (size_t i = 0; i < n_args; i++) { if (i > 0) { #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES mp_stream_write_adaptor(print.data, sep_data, u.len[0]); #else mp_print_strn(&mp_plat_print, sep_data, u.len[0], 0, 0, 0); #endif } #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES mp_obj_print_helper(&print, pos_args[i], PRINT_STR); #else mp_obj_print_helper(&mp_plat_print, pos_args[i], PRINT_STR); #endif } #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES mp_stream_write_adaptor(print.data, end_data, u.len[1]); #else mp_print_strn(&mp_plat_print, end_data, u.len[1], 0, 0, 0); #endif return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_print_obj, 0, mp_builtin_print); STATIC mp_obj_t mp_builtin___repl_print__(mp_obj_t o) { if (o != mp_const_none) { mp_obj_print_helper(MP_PYTHON_PRINTER, o, PRINT_REPR); mp_print_str(MP_PYTHON_PRINTER, "\n"); #if MICROPY_CAN_OVERRIDE_BUILTINS // Set "_" special variable mp_obj_t dest[2] = {MP_OBJ_SENTINEL, o}; mp_type_module.attr(MP_OBJ_FROM_PTR(&mp_module_builtins), MP_QSTR__, dest); #endif } return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin___repl_print___obj, mp_builtin___repl_print__); STATIC mp_obj_t mp_builtin_repr(mp_obj_t o_in) { vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 16, &print); mp_obj_print_helper(&print, o_in, PRINT_REPR); return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_repr_obj, mp_builtin_repr); STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { mp_obj_t o_in = args[0]; if (mp_obj_is_int(o_in)) { if (n_args <= 1) { return o_in; } #if !MICROPY_PY_BUILTINS_ROUND_INT mp_raise_NotImplementedError(NULL); #else mp_int_t num_dig = mp_obj_get_int(args[1]); if (num_dig >= 0) { return o_in; } mp_obj_t mult = mp_binary_op(MP_BINARY_OP_POWER, MP_OBJ_NEW_SMALL_INT(10), MP_OBJ_NEW_SMALL_INT(-num_dig)); mp_obj_t half_mult = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, mult, MP_OBJ_NEW_SMALL_INT(2)); mp_obj_t modulo = mp_binary_op(MP_BINARY_OP_MODULO, o_in, mult); mp_obj_t rounded = mp_binary_op(MP_BINARY_OP_SUBTRACT, o_in, modulo); if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, half_mult, modulo))) { return rounded; } else if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, modulo, half_mult))) { return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); } else { // round to even number mp_obj_t floor = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, o_in, mult); if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_AND, floor, MP_OBJ_NEW_SMALL_INT(1)))) { return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); } else { return rounded; } } #endif } #if MICROPY_PY_BUILTINS_FLOAT mp_float_t val = mp_obj_get_float(o_in); if (n_args > 1) { mp_int_t num_dig = mp_obj_get_int(args[1]); mp_float_t mult = MICROPY_FLOAT_C_FUN(pow)(10, (mp_float_t)num_dig); // TODO may lead to overflow mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val * mult) / mult; return mp_obj_new_float(rounded); } mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val); return mp_obj_new_int_from_float(rounded); #else mp_int_t r = mp_obj_get_int(o_in); return mp_obj_new_int(r); #endif } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj, 1, 2, mp_builtin_round); STATIC mp_obj_t mp_builtin_sum(size_t n_args, const mp_obj_t *args) { mp_obj_t value; switch (n_args) { case 1: value = MP_OBJ_NEW_SMALL_INT(0); break; default: value = args[1]; break; } mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(args[0], &iter_buf); mp_obj_t item; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { value = mp_binary_op(MP_BINARY_OP_ADD, value, item); } return value; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum); STATIC mp_obj_t mp_builtin_sorted(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { if (n_args > 1) { mp_raise_TypeError(MP_ERROR_TEXT("must use keyword argument for key function")); } mp_obj_t self = mp_type_list.make_new(&mp_type_list, 1, 0, args); mp_obj_list_sort(1, &self, kwargs); return self; } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); // See mp_load_attr() if making any changes static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { mp_obj_t dest[2]; // use load_method, raising or not raising exception if (defval == MP_OBJ_NULL) { mp_load_method(base, attr, dest); } else { mp_load_method_protected(base, attr, dest, false); } if (dest[0] == MP_OBJ_NULL) { return defval; } else if (dest[1] == MP_OBJ_NULL) { // load_method returned just a normal attribute return dest[0]; } else { // load_method returned a method, so build a bound method object return mp_obj_new_bound_meth(dest[0], dest[1]); } } STATIC mp_obj_t mp_builtin_getattr(size_t n_args, const mp_obj_t *args) { mp_obj_t defval = MP_OBJ_NULL; if (n_args > 2) { defval = args[2]; } return mp_load_attr_default(args[0], mp_obj_str_get_qstr(args[1]), defval); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr); STATIC mp_obj_t mp_builtin_setattr(mp_obj_t base, mp_obj_t attr, mp_obj_t value) { mp_store_attr(base, mp_obj_str_get_qstr(attr), value); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_3(mp_builtin_setattr_obj, mp_builtin_setattr); #if MICROPY_CPYTHON_COMPAT STATIC mp_obj_t mp_builtin_delattr(mp_obj_t base, mp_obj_t attr) { return mp_builtin_setattr(base, attr, MP_OBJ_NULL); } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_delattr_obj, mp_builtin_delattr); #endif STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) { qstr attr = mp_obj_str_get_qstr(attr_in); mp_obj_t dest[2]; mp_load_method_protected(object_in, attr, dest, false); return mp_obj_new_bool(dest[0] != MP_OBJ_NULL); } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr); STATIC mp_obj_t mp_builtin_globals(void) { return MP_OBJ_FROM_PTR(mp_globals_get()); } MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_builtin_globals); STATIC mp_obj_t mp_builtin_locals(void) { return MP_OBJ_FROM_PTR(mp_locals_get()); } MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_builtin_locals); // These are defined in terms of MicroPython API functions right away MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_id_obj, mp_obj_id); MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_len_obj, mp_obj_len); STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_builtins) }, // built-in core functions { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, // built-in types { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, { MP_ROM_QSTR(MP_QSTR_bytes), MP_ROM_PTR(&mp_type_bytes) }, #if MICROPY_PY_BUILTINS_BYTEARRAY { MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&mp_type_bytearray) }, #endif #if MICROPY_PY_BUILTINS_COMPLEX { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_PTR(&mp_type_complex) }, #endif { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&mp_type_dict) }, #if MICROPY_PY_BUILTINS_ENUMERATE { MP_ROM_QSTR(MP_QSTR_enumerate), MP_ROM_PTR(&mp_type_enumerate) }, #endif #if MICROPY_PY_BUILTINS_FILTER { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&mp_type_filter) }, #endif #if MICROPY_PY_BUILTINS_FLOAT { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_PTR(&mp_type_float) }, #endif #if MICROPY_PY_BUILTINS_SET && MICROPY_PY_BUILTINS_FROZENSET { MP_ROM_QSTR(MP_QSTR_frozenset), MP_ROM_PTR(&mp_type_frozenset) }, #endif { MP_ROM_QSTR(MP_QSTR_int), MP_ROM_PTR(&mp_type_int) }, { MP_ROM_QSTR(MP_QSTR_list), MP_ROM_PTR(&mp_type_list) }, { MP_ROM_QSTR(MP_QSTR_map), MP_ROM_PTR(&mp_type_map) }, #if MICROPY_PY_BUILTINS_MEMORYVIEW { MP_ROM_QSTR(MP_QSTR_memoryview), MP_ROM_PTR(&mp_type_memoryview) }, #endif { MP_ROM_QSTR(MP_QSTR_object), MP_ROM_PTR(&mp_type_object) }, #if MICROPY_PY_BUILTINS_PROPERTY { MP_ROM_QSTR(MP_QSTR_property), MP_ROM_PTR(&mp_type_property) }, #endif { MP_ROM_QSTR(MP_QSTR_range), MP_ROM_PTR(&mp_type_range) }, #if MICROPY_PY_BUILTINS_REVERSED { MP_ROM_QSTR(MP_QSTR_reversed), MP_ROM_PTR(&mp_type_reversed) }, #endif #if MICROPY_PY_BUILTINS_SET { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mp_type_set) }, #endif #if MICROPY_PY_BUILTINS_SLICE { MP_ROM_QSTR(MP_QSTR_slice), MP_ROM_PTR(&mp_type_slice) }, #endif { MP_ROM_QSTR(MP_QSTR_str), MP_ROM_PTR(&mp_type_str) }, { MP_ROM_QSTR(MP_QSTR_super), MP_ROM_PTR(&mp_type_super) }, { MP_ROM_QSTR(MP_QSTR_tuple), MP_ROM_PTR(&mp_type_tuple) }, { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&mp_type_type) }, { MP_ROM_QSTR(MP_QSTR_zip), MP_ROM_PTR(&mp_type_zip) }, { MP_ROM_QSTR(MP_QSTR_classmethod), MP_ROM_PTR(&mp_type_classmethod) }, { MP_ROM_QSTR(MP_QSTR_staticmethod), MP_ROM_PTR(&mp_type_staticmethod) }, // built-in objects { MP_ROM_QSTR(MP_QSTR_Ellipsis), MP_ROM_PTR(&mp_const_ellipsis_obj) }, #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED { MP_ROM_QSTR(MP_QSTR_NotImplemented), MP_ROM_PTR(&mp_const_notimplemented_obj) }, #endif // built-in user functions { MP_ROM_QSTR(MP_QSTR_abs), MP_ROM_PTR(&mp_builtin_abs_obj) }, { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&mp_builtin_all_obj) }, { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&mp_builtin_any_obj) }, { MP_ROM_QSTR(MP_QSTR_bin), MP_ROM_PTR(&mp_builtin_bin_obj) }, { MP_ROM_QSTR(MP_QSTR_callable), MP_ROM_PTR(&mp_builtin_callable_obj) }, #if MICROPY_PY_BUILTINS_COMPILE { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mp_builtin_compile_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_chr), MP_ROM_PTR(&mp_builtin_chr_obj) }, #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, #if MICROPY_PY_BUILTINS_EVAL_EXEC { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&mp_builtin_exec_obj) }, #endif #if MICROPY_PY_BUILTINS_EXECFILE { MP_ROM_QSTR(MP_QSTR_execfile), MP_ROM_PTR(&mp_builtin_execfile_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_getattr), MP_ROM_PTR(&mp_builtin_getattr_obj) }, { MP_ROM_QSTR(MP_QSTR_setattr), MP_ROM_PTR(&mp_builtin_setattr_obj) }, { MP_ROM_QSTR(MP_QSTR_globals), MP_ROM_PTR(&mp_builtin_globals_obj) }, { MP_ROM_QSTR(MP_QSTR_hasattr), MP_ROM_PTR(&mp_builtin_hasattr_obj) }, { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mp_builtin_hash_obj) }, #if MICROPY_PY_BUILTINS_HELP { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&mp_builtin_help_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_hex), MP_ROM_PTR(&mp_builtin_hex_obj) }, { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&mp_builtin_id_obj) }, #if MICROPY_PY_BUILTINS_INPUT { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mp_builtin_input_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_isinstance), MP_ROM_PTR(&mp_builtin_isinstance_obj) }, { MP_ROM_QSTR(MP_QSTR_issubclass), MP_ROM_PTR(&mp_builtin_issubclass_obj) }, { MP_ROM_QSTR(MP_QSTR_iter), MP_ROM_PTR(&mp_builtin_iter_obj) }, { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&mp_builtin_len_obj) }, { MP_ROM_QSTR(MP_QSTR_locals), MP_ROM_PTR(&mp_builtin_locals_obj) }, #if MICROPY_PY_BUILTINS_MIN_MAX { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&mp_builtin_max_obj) }, { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&mp_builtin_min_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, { MP_ROM_QSTR(MP_QSTR_repr), MP_ROM_PTR(&mp_builtin_repr_obj) }, { MP_ROM_QSTR(MP_QSTR_round), MP_ROM_PTR(&mp_builtin_round_obj) }, { MP_ROM_QSTR(MP_QSTR_sorted), MP_ROM_PTR(&mp_builtin_sorted_obj) }, { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&mp_builtin_sum_obj) }, // built-in exceptions { MP_ROM_QSTR(MP_QSTR_BaseException), MP_ROM_PTR(&mp_type_BaseException) }, { MP_ROM_QSTR(MP_QSTR_ArithmeticError), MP_ROM_PTR(&mp_type_ArithmeticError) }, { MP_ROM_QSTR(MP_QSTR_AssertionError), MP_ROM_PTR(&mp_type_AssertionError) }, { MP_ROM_QSTR(MP_QSTR_AttributeError), MP_ROM_PTR(&mp_type_AttributeError) }, { MP_ROM_QSTR(MP_QSTR_EOFError), MP_ROM_PTR(&mp_type_EOFError) }, { MP_ROM_QSTR(MP_QSTR_Exception), MP_ROM_PTR(&mp_type_Exception) }, { MP_ROM_QSTR(MP_QSTR_GeneratorExit), MP_ROM_PTR(&mp_type_GeneratorExit) }, { MP_ROM_QSTR(MP_QSTR_ImportError), MP_ROM_PTR(&mp_type_ImportError) }, { MP_ROM_QSTR(MP_QSTR_IndentationError), MP_ROM_PTR(&mp_type_IndentationError) }, { MP_ROM_QSTR(MP_QSTR_IndexError), MP_ROM_PTR(&mp_type_IndexError) }, { MP_ROM_QSTR(MP_QSTR_KeyboardInterrupt), MP_ROM_PTR(&mp_type_KeyboardInterrupt) }, { MP_ROM_QSTR(MP_QSTR_KeyError), MP_ROM_PTR(&mp_type_KeyError) }, { MP_ROM_QSTR(MP_QSTR_LookupError), MP_ROM_PTR(&mp_type_LookupError) }, { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_MemoryError) }, { MP_ROM_QSTR(MP_QSTR_NameError), MP_ROM_PTR(&mp_type_NameError) }, { MP_ROM_QSTR(MP_QSTR_NotImplementedError), MP_ROM_PTR(&mp_type_NotImplementedError) }, { MP_ROM_QSTR(MP_QSTR_OSError), MP_ROM_PTR(&mp_type_OSError) }, { MP_ROM_QSTR(MP_QSTR_OverflowError), MP_ROM_PTR(&mp_type_OverflowError) }, { MP_ROM_QSTR(MP_QSTR_RuntimeError), MP_ROM_PTR(&mp_type_RuntimeError) }, #if MICROPY_PY_ASYNC_AWAIT { MP_ROM_QSTR(MP_QSTR_StopAsyncIteration), MP_ROM_PTR(&mp_type_StopAsyncIteration) }, #endif { MP_ROM_QSTR(MP_QSTR_StopIteration), MP_ROM_PTR(&mp_type_StopIteration) }, { MP_ROM_QSTR(MP_QSTR_SyntaxError), MP_ROM_PTR(&mp_type_SyntaxError) }, { MP_ROM_QSTR(MP_QSTR_SystemExit), MP_ROM_PTR(&mp_type_SystemExit) }, { MP_ROM_QSTR(MP_QSTR_TypeError), MP_ROM_PTR(&mp_type_TypeError) }, #if MICROPY_PY_BUILTINS_STR_UNICODE { MP_ROM_QSTR(MP_QSTR_UnicodeError), MP_ROM_PTR(&mp_type_UnicodeError) }, #endif { MP_ROM_QSTR(MP_QSTR_ValueError), MP_ROM_PTR(&mp_type_ValueError) }, #if MICROPY_EMIT_NATIVE { MP_ROM_QSTR(MP_QSTR_ViperTypeError), MP_ROM_PTR(&mp_type_ViperTypeError) }, #endif { MP_ROM_QSTR(MP_QSTR_ZeroDivisionError), MP_ROM_PTR(&mp_type_ZeroDivisionError) }, // Extra builtins as defined by a port MICROPY_PORT_BUILTINS MICROPY_PORT_EXTRA_BUILTINS }; MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); const mp_obj_module_t mp_module_builtins = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_builtins_globals, }; #include "py/builtin.h" #if MICROPY_PY_COLLECTIONS STATIC const mp_rom_map_elem_t mp_module_collections_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ucollections) }, #if MICROPY_PY_COLLECTIONS_DEQUE { MP_ROM_QSTR(MP_QSTR_deque), MP_ROM_PTR(&mp_type_deque) }, #endif { MP_ROM_QSTR(MP_QSTR_namedtuple), MP_ROM_PTR(&mp_namedtuple_obj) }, #if MICROPY_PY_COLLECTIONS_ORDEREDDICT { MP_ROM_QSTR(MP_QSTR_OrderedDict), MP_ROM_PTR(&mp_type_ordereddict) }, #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_collections_globals, mp_module_collections_globals_table); const mp_obj_module_t mp_module_collections = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_collections_globals, }; #endif // MICROPY_PY_COLLECTIONS #include "py/mpstate.h" #include "py/obj.h" #include "py/gc.h" #if MICROPY_PY_GC && MICROPY_ENABLE_GC // collect(): run a garbage collection STATIC mp_obj_t py_gc_collect(void) { gc_collect(); #if MICROPY_PY_GC_COLLECT_RETVAL return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(gc_collected)); #else return mp_const_none; #endif } MP_DEFINE_CONST_FUN_OBJ_0(gc_collect_obj, py_gc_collect); // disable(): disable the garbage collector STATIC mp_obj_t gc_disable(void) { MP_STATE_MEM(gc_auto_collect_enabled) = 0; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_0(gc_disable_obj, gc_disable); // enable(): enable the garbage collector STATIC mp_obj_t gc_enable(void) { MP_STATE_MEM(gc_auto_collect_enabled) = 1; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_0(gc_enable_obj, gc_enable); STATIC mp_obj_t gc_isenabled(void) { return mp_obj_new_bool(MP_STATE_MEM(gc_auto_collect_enabled)); } MP_DEFINE_CONST_FUN_OBJ_0(gc_isenabled_obj, gc_isenabled); // mem_free(): return the number of bytes of available heap RAM STATIC mp_obj_t gc_mem_free(void) { gc_info_t info; gc_info(&info); return MP_OBJ_NEW_SMALL_INT(info.free); } MP_DEFINE_CONST_FUN_OBJ_0(gc_mem_free_obj, gc_mem_free); // mem_alloc(): return the number of bytes of heap RAM that are allocated STATIC mp_obj_t gc_mem_alloc(void) { gc_info_t info; gc_info(&info); return MP_OBJ_NEW_SMALL_INT(info.used); } MP_DEFINE_CONST_FUN_OBJ_0(gc_mem_alloc_obj, gc_mem_alloc); #if MICROPY_GC_ALLOC_THRESHOLD STATIC mp_obj_t gc_threshold(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { if (MP_STATE_MEM(gc_alloc_threshold) == (size_t)-1) { return MP_OBJ_NEW_SMALL_INT(-1); } return mp_obj_new_int(MP_STATE_MEM(gc_alloc_threshold) * MICROPY_BYTES_PER_GC_BLOCK); } mp_int_t val = mp_obj_get_int(args[0]); if (val < 0) { MP_STATE_MEM(gc_alloc_threshold) = (size_t)-1; } else { MP_STATE_MEM(gc_alloc_threshold) = val / MICROPY_BYTES_PER_GC_BLOCK; } return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gc_threshold_obj, 0, 1, gc_threshold); #endif STATIC const mp_rom_map_elem_t mp_module_gc_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_gc) }, { MP_ROM_QSTR(MP_QSTR_collect), MP_ROM_PTR(&gc_collect_obj) }, { MP_ROM_QSTR(MP_QSTR_disable), MP_ROM_PTR(&gc_disable_obj) }, { MP_ROM_QSTR(MP_QSTR_enable), MP_ROM_PTR(&gc_enable_obj) }, { MP_ROM_QSTR(MP_QSTR_isenabled), MP_ROM_PTR(&gc_isenabled_obj) }, { MP_ROM_QSTR(MP_QSTR_mem_free), MP_ROM_PTR(&gc_mem_free_obj) }, { MP_ROM_QSTR(MP_QSTR_mem_alloc), MP_ROM_PTR(&gc_mem_alloc_obj) }, #if MICROPY_GC_ALLOC_THRESHOLD { MP_ROM_QSTR(MP_QSTR_threshold), MP_ROM_PTR(&gc_threshold_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_gc_globals, mp_module_gc_globals_table); const mp_obj_module_t mp_module_gc = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_gc_globals, }; #endif #include #include #include "py/runtime.h" #include "py/builtin.h" #include "py/stream.h" #include "py/binary.h" #include "py/objarray.h" #include "py/objstringio.h" #include "py/frozenmod.h" #if MICROPY_PY_IO extern const mp_obj_type_t mp_type_fileio; extern const mp_obj_type_t mp_type_textio; #if MICROPY_PY_IO_IOBASE STATIC const mp_obj_type_t mp_type_iobase; STATIC const mp_obj_base_t iobase_singleton = {&mp_type_iobase}; STATIC mp_obj_t iobase_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type; (void)n_args; (void)n_kw; (void)args; return MP_OBJ_FROM_PTR(&iobase_singleton); } STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode, qstr qst) { mp_obj_t dest[3]; mp_load_method(obj, qst, dest); mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, size, buf}; dest[2] = MP_OBJ_FROM_PTR(&ar); mp_obj_t ret_obj = mp_call_method_n_kw(1, 0, dest); if (ret_obj == mp_const_none) { *errcode = MP_EAGAIN; return MP_STREAM_ERROR; } mp_int_t ret = mp_obj_get_int(ret_obj); if (ret >= 0) { return ret; } else { *errcode = -ret; return MP_STREAM_ERROR; } } STATIC mp_uint_t iobase_read(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode) { return iobase_read_write(obj, buf, size, errcode, MP_QSTR_readinto); } STATIC mp_uint_t iobase_write(mp_obj_t obj, const void *buf, mp_uint_t size, int *errcode) { return iobase_read_write(obj, (void *)buf, size, errcode, MP_QSTR_write); } STATIC mp_uint_t iobase_ioctl(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode) { mp_obj_t dest[4]; mp_load_method(obj, MP_QSTR_ioctl, dest); dest[2] = mp_obj_new_int_from_uint(request); dest[3] = mp_obj_new_int_from_uint(arg); mp_int_t ret = mp_obj_get_int(mp_call_method_n_kw(2, 0, dest)); if (ret >= 0) { return ret; } else { *errcode = -ret; return MP_STREAM_ERROR; } } STATIC const mp_stream_p_t iobase_p = { .read = iobase_read, .write = iobase_write, .ioctl = iobase_ioctl, }; STATIC const mp_obj_type_t mp_type_iobase = { { &mp_type_type }, .name = MP_QSTR_IOBase, .make_new = iobase_make_new, .protocol = &iobase_p, }; #endif // MICROPY_PY_IO_IOBASE #if MICROPY_PY_IO_BUFFEREDWRITER typedef struct _mp_obj_bufwriter_t { mp_obj_base_t base; mp_obj_t stream; size_t alloc; size_t len; byte buf[0]; } mp_obj_bufwriter_t; STATIC mp_obj_t bufwriter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 2, false); size_t alloc = mp_obj_get_int(args[1]); mp_obj_bufwriter_t *o = m_new_obj_var(mp_obj_bufwriter_t, byte, alloc); o->base.type = type; o->stream = args[0]; o->alloc = alloc; o->len = 0; return o; } STATIC mp_uint_t bufwriter_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { mp_obj_bufwriter_t *self = MP_OBJ_TO_PTR(self_in); mp_uint_t org_size = size; while (size > 0) { mp_uint_t rem = self->alloc - self->len; if (size < rem) { memcpy(self->buf + self->len, buf, size); self->len += size; return org_size; } // Buffer flushing policy here is to flush entire buffer all the time. // This allows e.g. to have a block device as backing storage and write // entire block to it. memcpy below is not ideal and could be optimized // in some cases. But the way it is now it at least ensures that buffer // is word-aligned, to guard against obscure cases when it matters, e.g. // https://github.com/micropython/micropython/issues/1863 memcpy(self->buf + self->len, buf, rem); buf = (byte *)buf + rem; size -= rem; mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->alloc, errcode); (void)out_sz; if (*errcode != 0) { return MP_STREAM_ERROR; } // TODO: try to recover from a case of non-blocking stream, e.g. move // remaining chunk to the beginning of buffer. assert(out_sz == self->alloc); self->len = 0; } return org_size; } STATIC mp_obj_t bufwriter_flush(mp_obj_t self_in) { mp_obj_bufwriter_t *self = MP_OBJ_TO_PTR(self_in); if (self->len != 0) { int err; mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->len, &err); (void)out_sz; // TODO: try to recover from a case of non-blocking stream, e.g. move // remaining chunk to the beginning of buffer. assert(out_sz == self->len); self->len = 0; if (err != 0) { mp_raise_OSError(err); } } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(bufwriter_flush_obj, bufwriter_flush); STATIC const mp_rom_map_elem_t bufwriter_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&bufwriter_flush_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bufwriter_locals_dict, bufwriter_locals_dict_table); STATIC const mp_stream_p_t bufwriter_stream_p = { .write = bufwriter_write, }; STATIC const mp_obj_type_t mp_type_bufwriter = { { &mp_type_type }, .name = MP_QSTR_BufferedWriter, .make_new = bufwriter_make_new, .protocol = &bufwriter_stream_p, .locals_dict = (mp_obj_dict_t *)&bufwriter_locals_dict, }; #endif // MICROPY_PY_IO_BUFFEREDWRITER STATIC const mp_rom_map_elem_t mp_module_io_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uio) }, // Note: mp_builtin_open_obj should be defined by port, it's not // part of the core. { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, #if MICROPY_PY_IO_IOBASE { MP_ROM_QSTR(MP_QSTR_IOBase), MP_ROM_PTR(&mp_type_iobase) }, #endif #if MICROPY_PY_IO_FILEIO { MP_ROM_QSTR(MP_QSTR_FileIO), MP_ROM_PTR(&mp_type_fileio) }, #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_TextIOWrapper), MP_ROM_PTR(&mp_type_textio) }, #endif #endif { MP_ROM_QSTR(MP_QSTR_StringIO), MP_ROM_PTR(&mp_type_stringio) }, #if MICROPY_PY_IO_BYTESIO { MP_ROM_QSTR(MP_QSTR_BytesIO), MP_ROM_PTR(&mp_type_bytesio) }, #endif #if MICROPY_PY_IO_BUFFEREDWRITER { MP_ROM_QSTR(MP_QSTR_BufferedWriter), MP_ROM_PTR(&mp_type_bufwriter) }, #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_io_globals, mp_module_io_globals_table); const mp_obj_module_t mp_module_io = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_io_globals, }; #endif #include "py/builtin.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH #include // M_PI is not part of the math.h standard and may not be defined // And by defining our own we can ensure it uses the correct const format. #define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) #define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) #define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) STATIC NORETURN void math_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); } STATIC mp_obj_t math_generic_1(mp_obj_t x_obj, mp_float_t (*f)(mp_float_t)) { mp_float_t x = mp_obj_get_float(x_obj); mp_float_t ans = f(x); if ((isnan(ans) && !isnan(x)) || (isinf(ans) && !isinf(x))) { math_error(); } return mp_obj_new_float(ans); } STATIC mp_obj_t math_generic_2(mp_obj_t x_obj, mp_obj_t y_obj, mp_float_t (*f)(mp_float_t, mp_float_t)) { mp_float_t x = mp_obj_get_float(x_obj); mp_float_t y = mp_obj_get_float(y_obj); mp_float_t ans = f(x, y); if ((isnan(ans) && !isnan(x) && !isnan(y)) || (isinf(ans) && !isinf(x))) { math_error(); } return mp_obj_new_float(ans); } #define MATH_FUN_1(py_name, c_name) \ STATIC mp_obj_t mp_math_##py_name(mp_obj_t x_obj) { \ return math_generic_1(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ } \ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_##py_name##_obj, mp_math_##py_name); #define MATH_FUN_1_TO_BOOL(py_name, c_name) \ STATIC mp_obj_t mp_math_##py_name(mp_obj_t x_obj) { return mp_obj_new_bool(c_name(mp_obj_get_float(x_obj))); } \ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_##py_name##_obj, mp_math_##py_name); #define MATH_FUN_1_TO_INT(py_name, c_name) \ STATIC mp_obj_t mp_math_##py_name(mp_obj_t x_obj) { return mp_obj_new_int_from_float(MICROPY_FLOAT_C_FUN(c_name)(mp_obj_get_float(x_obj))); } \ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_##py_name##_obj, mp_math_##py_name); #define MATH_FUN_2(py_name, c_name) \ STATIC mp_obj_t mp_math_##py_name(mp_obj_t x_obj, mp_obj_t y_obj) { \ return math_generic_2(x_obj, y_obj, MICROPY_FLOAT_C_FUN(c_name)); \ } \ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_math_##py_name##_obj, mp_math_##py_name); #define MATH_FUN_2_FLT_INT(py_name, c_name) \ STATIC mp_obj_t mp_math_##py_name(mp_obj_t x_obj, mp_obj_t y_obj) { \ return mp_obj_new_float(MICROPY_FLOAT_C_FUN(c_name)(mp_obj_get_float(x_obj), mp_obj_get_int(y_obj))); \ } \ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_math_##py_name##_obj, mp_math_##py_name); #if MP_NEED_LOG2 #undef log2 #undef log2f // 1.442695040888963407354163704 is 1/_M_LN2 mp_float_t MICROPY_FLOAT_C_FUN(log2)(mp_float_t x) { return MICROPY_FLOAT_C_FUN(log)(x) * MICROPY_FLOAT_CONST(1.442695040888963407354163704); } #endif // sqrt(x): returns the square root of x MATH_FUN_1(sqrt, sqrt) // pow(x, y): returns x to the power of y #if MICROPY_PY_MATH_POW_FIX_NAN mp_float_t pow_func(mp_float_t x, mp_float_t y) { // pow(base, 0) returns 1 for any base, even when base is NaN // pow(+1, exponent) returns 1 for any exponent, even when exponent is NaN if (x == MICROPY_FLOAT_CONST(1.0) || y == MICROPY_FLOAT_CONST(0.0)) { return MICROPY_FLOAT_CONST(1.0); } return MICROPY_FLOAT_C_FUN(pow)(x, y); } MATH_FUN_2(pow, pow_func) #else MATH_FUN_2(pow, pow) #endif // exp(x) MATH_FUN_1(exp, exp) #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS // expm1(x) MATH_FUN_1(expm1, expm1) // log2(x) MATH_FUN_1(log2, log2) // log10(x) MATH_FUN_1(log10, log10) // cosh(x) MATH_FUN_1(cosh, cosh) // sinh(x) MATH_FUN_1(sinh, sinh) // tanh(x) MATH_FUN_1(tanh, tanh) // acosh(x) MATH_FUN_1(acosh, acosh) // asinh(x) MATH_FUN_1(asinh, asinh) // atanh(x) MATH_FUN_1(atanh, atanh) #endif // cos(x) MATH_FUN_1(cos, cos) // sin(x) MATH_FUN_1(sin, sin) // tan(x) MATH_FUN_1(tan, tan) // acos(x) MATH_FUN_1(acos, acos) // asin(x) MATH_FUN_1(asin, asin) // atan(x) MATH_FUN_1(atan, atan) // atan2(y, x) #if MICROPY_PY_MATH_ATAN2_FIX_INFNAN mp_float_t atan2_func(mp_float_t x, mp_float_t y) { if (isinf(x) && isinf(y)) { return copysign(y < 0 ? MP_3_PI_4 : MP_PI_4, x); } return atan2(x, y); } MATH_FUN_2(atan2, atan2_func) #else MATH_FUN_2(atan2, atan2) #endif // ceil(x) MATH_FUN_1_TO_INT(ceil, ceil) // copysign(x, y) STATIC mp_float_t MICROPY_FLOAT_C_FUN(copysign_func)(mp_float_t x, mp_float_t y) { return MICROPY_FLOAT_C_FUN(copysign)(x, y); } MATH_FUN_2(copysign, copysign_func) // fabs(x) STATIC mp_float_t MICROPY_FLOAT_C_FUN(fabs_func)(mp_float_t x) { return MICROPY_FLOAT_C_FUN(fabs)(x); } MATH_FUN_1(fabs, fabs_func) // floor(x) MATH_FUN_1_TO_INT(floor, floor) // TODO: delegate to x.__floor__() if x is not a float // fmod(x, y) #if MICROPY_PY_MATH_FMOD_FIX_INFNAN mp_float_t fmod_func(mp_float_t x, mp_float_t y) { return (!isinf(x) && isinf(y)) ? x : fmod(x, y); } MATH_FUN_2(fmod, fmod_func) #else MATH_FUN_2(fmod, fmod) #endif // isfinite(x) MATH_FUN_1_TO_BOOL(isfinite, isfinite) // isinf(x) MATH_FUN_1_TO_BOOL(isinf, isinf) // isnan(x) MATH_FUN_1_TO_BOOL(isnan, isnan) // trunc(x) MATH_FUN_1_TO_INT(trunc, trunc) // ldexp(x, exp) MATH_FUN_2_FLT_INT(ldexp, ldexp) #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS // erf(x): return the error function of x MATH_FUN_1(erf, erf) // erfc(x): return the complementary error function of x MATH_FUN_1(erfc, erfc) // gamma(x): return the gamma function of x MATH_FUN_1(gamma, tgamma) // lgamma(x): return the natural logarithm of the gamma function of x MATH_FUN_1(lgamma, lgamma) #endif // TODO: fsum #if MICROPY_PY_MATH_ISCLOSE STATIC mp_obj_t mp_math_isclose(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_rel_tol, ARG_abs_tol }; static const mp_arg_t allowed_args[] = { {MP_QSTR_rel_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_abs_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const mp_float_t a = mp_obj_get_float(pos_args[0]); const mp_float_t b = mp_obj_get_float(pos_args[1]); const mp_float_t rel_tol = args[ARG_rel_tol].u_obj == MP_OBJ_NULL ? (mp_float_t)1e-9 : mp_obj_get_float(args[ARG_rel_tol].u_obj); const mp_float_t abs_tol = mp_obj_get_float(args[ARG_abs_tol].u_obj); if (rel_tol < (mp_float_t)0.0 || abs_tol < (mp_float_t)0.0) { math_error(); } if (a == b) { return mp_const_true; } const mp_float_t difference = MICROPY_FLOAT_C_FUN(fabs)(a - b); if (isinf(difference)) { // Either a or b is inf return mp_const_false; } if ((difference <= abs_tol) || (difference <= MICROPY_FLOAT_C_FUN(fabs)(rel_tol * a)) || (difference <= MICROPY_FLOAT_C_FUN(fabs)(rel_tol * b))) { return mp_const_true; } return mp_const_false; } MP_DEFINE_CONST_FUN_OBJ_KW(mp_math_isclose_obj, 2, mp_math_isclose); #endif // Function that takes a variable number of arguments // log(x[, base]) STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { mp_float_t x = mp_obj_get_float(args[0]); if (x <= (mp_float_t)0.0) { math_error(); } mp_float_t l = MICROPY_FLOAT_C_FUN(log)(x); if (n_args == 1) { return mp_obj_new_float(l); } else { mp_float_t base = mp_obj_get_float(args[1]); if (base <= (mp_float_t)0.0) { math_error(); } else if (base == (mp_float_t)1.0) { mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } return mp_obj_new_float(l / MICROPY_FLOAT_C_FUN(log)(base)); } } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_math_log_obj, 1, 2, mp_math_log); // Functions that return a tuple // frexp(x): converts a floating-point number to fractional and integral components STATIC mp_obj_t mp_math_frexp(mp_obj_t x_obj) { int int_exponent = 0; mp_float_t significand = MICROPY_FLOAT_C_FUN(frexp)(mp_obj_get_float(x_obj), &int_exponent); mp_obj_t tuple[2]; tuple[0] = mp_obj_new_float(significand); tuple[1] = mp_obj_new_int(int_exponent); return mp_obj_new_tuple(2, tuple); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_frexp_obj, mp_math_frexp); // modf(x) STATIC mp_obj_t mp_math_modf(mp_obj_t x_obj) { mp_float_t int_part = 0.0; mp_float_t x = mp_obj_get_float(x_obj); mp_float_t fractional_part = MICROPY_FLOAT_C_FUN(modf)(x, &int_part); #if MICROPY_PY_MATH_MODF_FIX_NEGZERO if (fractional_part == MICROPY_FLOAT_CONST(0.0)) { fractional_part = copysign(fractional_part, x); } #endif mp_obj_t tuple[2]; tuple[0] = mp_obj_new_float(fractional_part); tuple[1] = mp_obj_new_float(int_part); return mp_obj_new_tuple(2, tuple); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_modf_obj, mp_math_modf); // Angular conversions // radians(x) STATIC mp_obj_t mp_math_radians(mp_obj_t x_obj) { return mp_obj_new_float(mp_obj_get_float(x_obj) * (MP_PI / MICROPY_FLOAT_CONST(180.0))); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_radians_obj, mp_math_radians); // degrees(x) STATIC mp_obj_t mp_math_degrees(mp_obj_t x_obj) { return mp_obj_new_float(mp_obj_get_float(x_obj) * (MICROPY_FLOAT_CONST(180.0) / MP_PI)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_degrees_obj, mp_math_degrees); #if MICROPY_PY_MATH_FACTORIAL #if MICROPY_OPT_MATH_FACTORIAL // factorial(x): slightly efficient recursive implementation STATIC mp_obj_t mp_math_factorial_inner(mp_uint_t start, mp_uint_t end) { if (start == end) { return mp_obj_new_int(start); } else if (end - start == 1) { return mp_binary_op(MP_BINARY_OP_MULTIPLY, MP_OBJ_NEW_SMALL_INT(start), MP_OBJ_NEW_SMALL_INT(end)); } else if (end - start == 2) { mp_obj_t left = MP_OBJ_NEW_SMALL_INT(start); mp_obj_t middle = MP_OBJ_NEW_SMALL_INT(start + 1); mp_obj_t right = MP_OBJ_NEW_SMALL_INT(end); mp_obj_t tmp = mp_binary_op(MP_BINARY_OP_MULTIPLY, left, middle); return mp_binary_op(MP_BINARY_OP_MULTIPLY, tmp, right); } else { mp_uint_t middle = start + ((end - start) >> 1); mp_obj_t left = mp_math_factorial_inner(start, middle); mp_obj_t right = mp_math_factorial_inner(middle + 1, end); return mp_binary_op(MP_BINARY_OP_MULTIPLY, left, right); } } STATIC mp_obj_t mp_math_factorial(mp_obj_t x_obj) { mp_int_t max = mp_obj_get_int(x_obj); if (max < 0) { mp_raise_ValueError(MP_ERROR_TEXT("negative factorial")); } else if (max == 0) { return MP_OBJ_NEW_SMALL_INT(1); } return mp_math_factorial_inner(1, max); } #else // factorial(x): squared difference implementation // based on http://www.luschny.de/math/factorial/index.html STATIC mp_obj_t mp_math_factorial(mp_obj_t x_obj) { mp_int_t max = mp_obj_get_int(x_obj); if (max < 0) { mp_raise_ValueError(MP_ERROR_TEXT("negative factorial")); } else if (max <= 1) { return MP_OBJ_NEW_SMALL_INT(1); } mp_int_t h = max >> 1; mp_int_t q = h * h; mp_int_t r = q << 1; if (max & 1) { r *= max; } mp_obj_t prod = MP_OBJ_NEW_SMALL_INT(r); for (mp_int_t num = 1; num < max - 2; num += 2) { q -= num; prod = mp_binary_op(MP_BINARY_OP_MULTIPLY, prod, MP_OBJ_NEW_SMALL_INT(q)); } return prod; } #endif STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_factorial_obj, mp_math_factorial); #endif STATIC const mp_rom_map_elem_t mp_module_math_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_math) }, { MP_ROM_QSTR(MP_QSTR_e), mp_const_float_e }, { MP_ROM_QSTR(MP_QSTR_pi), mp_const_float_pi }, #if MICROPY_PY_MATH_CONSTANTS { MP_ROM_QSTR(MP_QSTR_tau), mp_const_float_tau }, { MP_ROM_QSTR(MP_QSTR_inf), mp_const_float_inf }, { MP_ROM_QSTR(MP_QSTR_nan), mp_const_float_nan }, #endif { MP_ROM_QSTR(MP_QSTR_sqrt), MP_ROM_PTR(&mp_math_sqrt_obj) }, { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_math_pow_obj) }, { MP_ROM_QSTR(MP_QSTR_exp), MP_ROM_PTR(&mp_math_exp_obj) }, #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS { MP_ROM_QSTR(MP_QSTR_expm1), MP_ROM_PTR(&mp_math_expm1_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_log), MP_ROM_PTR(&mp_math_log_obj) }, #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS { MP_ROM_QSTR(MP_QSTR_log2), MP_ROM_PTR(&mp_math_log2_obj) }, { MP_ROM_QSTR(MP_QSTR_log10), MP_ROM_PTR(&mp_math_log10_obj) }, { MP_ROM_QSTR(MP_QSTR_cosh), MP_ROM_PTR(&mp_math_cosh_obj) }, { MP_ROM_QSTR(MP_QSTR_sinh), MP_ROM_PTR(&mp_math_sinh_obj) }, { MP_ROM_QSTR(MP_QSTR_tanh), MP_ROM_PTR(&mp_math_tanh_obj) }, { MP_ROM_QSTR(MP_QSTR_acosh), MP_ROM_PTR(&mp_math_acosh_obj) }, { MP_ROM_QSTR(MP_QSTR_asinh), MP_ROM_PTR(&mp_math_asinh_obj) }, { MP_ROM_QSTR(MP_QSTR_atanh), MP_ROM_PTR(&mp_math_atanh_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_cos), MP_ROM_PTR(&mp_math_cos_obj) }, { MP_ROM_QSTR(MP_QSTR_sin), MP_ROM_PTR(&mp_math_sin_obj) }, { MP_ROM_QSTR(MP_QSTR_tan), MP_ROM_PTR(&mp_math_tan_obj) }, { MP_ROM_QSTR(MP_QSTR_acos), MP_ROM_PTR(&mp_math_acos_obj) }, { MP_ROM_QSTR(MP_QSTR_asin), MP_ROM_PTR(&mp_math_asin_obj) }, { MP_ROM_QSTR(MP_QSTR_atan), MP_ROM_PTR(&mp_math_atan_obj) }, { MP_ROM_QSTR(MP_QSTR_atan2), MP_ROM_PTR(&mp_math_atan2_obj) }, { MP_ROM_QSTR(MP_QSTR_ceil), MP_ROM_PTR(&mp_math_ceil_obj) }, { MP_ROM_QSTR(MP_QSTR_copysign), MP_ROM_PTR(&mp_math_copysign_obj) }, { MP_ROM_QSTR(MP_QSTR_fabs), MP_ROM_PTR(&mp_math_fabs_obj) }, { MP_ROM_QSTR(MP_QSTR_floor), MP_ROM_PTR(&mp_math_floor_obj) }, { MP_ROM_QSTR(MP_QSTR_fmod), MP_ROM_PTR(&mp_math_fmod_obj) }, { MP_ROM_QSTR(MP_QSTR_frexp), MP_ROM_PTR(&mp_math_frexp_obj) }, { MP_ROM_QSTR(MP_QSTR_ldexp), MP_ROM_PTR(&mp_math_ldexp_obj) }, { MP_ROM_QSTR(MP_QSTR_modf), MP_ROM_PTR(&mp_math_modf_obj) }, { MP_ROM_QSTR(MP_QSTR_isfinite), MP_ROM_PTR(&mp_math_isfinite_obj) }, { MP_ROM_QSTR(MP_QSTR_isinf), MP_ROM_PTR(&mp_math_isinf_obj) }, { MP_ROM_QSTR(MP_QSTR_isnan), MP_ROM_PTR(&mp_math_isnan_obj) }, #if MICROPY_PY_MATH_ISCLOSE { MP_ROM_QSTR(MP_QSTR_isclose), MP_ROM_PTR(&mp_math_isclose_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_trunc), MP_ROM_PTR(&mp_math_trunc_obj) }, { MP_ROM_QSTR(MP_QSTR_radians), MP_ROM_PTR(&mp_math_radians_obj) }, { MP_ROM_QSTR(MP_QSTR_degrees), MP_ROM_PTR(&mp_math_degrees_obj) }, #if MICROPY_PY_MATH_FACTORIAL { MP_ROM_QSTR(MP_QSTR_factorial), MP_ROM_PTR(&mp_math_factorial_obj) }, #endif #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS { MP_ROM_QSTR(MP_QSTR_erf), MP_ROM_PTR(&mp_math_erf_obj) }, { MP_ROM_QSTR(MP_QSTR_erfc), MP_ROM_PTR(&mp_math_erfc_obj) }, { MP_ROM_QSTR(MP_QSTR_gamma), MP_ROM_PTR(&mp_math_gamma_obj) }, { MP_ROM_QSTR(MP_QSTR_lgamma), MP_ROM_PTR(&mp_math_lgamma_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_math_globals, mp_module_math_globals_table); const mp_obj_module_t mp_module_math = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_math_globals, }; #endif // MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH #include "py/builtin.h" #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH #include // phase(z): returns the phase of the number z in the range (-pi, +pi] STATIC mp_obj_t mp_cmath_phase(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); return mp_obj_new_float(MICROPY_FLOAT_C_FUN(atan2)(imag, real)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_phase_obj, mp_cmath_phase); // polar(z): returns the polar form of z as a tuple STATIC mp_obj_t mp_cmath_polar(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); mp_obj_t tuple[2] = { mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(real * real + imag * imag)), mp_obj_new_float(MICROPY_FLOAT_C_FUN(atan2)(imag, real)), }; return mp_obj_new_tuple(2, tuple); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_polar_obj, mp_cmath_polar); // rect(r, phi): returns the complex number with modulus r and phase phi STATIC mp_obj_t mp_cmath_rect(mp_obj_t r_obj, mp_obj_t phi_obj) { mp_float_t r = mp_obj_get_float(r_obj); mp_float_t phi = mp_obj_get_float(phi_obj); return mp_obj_new_complex(r * MICROPY_FLOAT_C_FUN(cos)(phi), r * MICROPY_FLOAT_C_FUN(sin)(phi)); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_cmath_rect_obj, mp_cmath_rect); // exp(z): return the exponential of z STATIC mp_obj_t mp_cmath_exp(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); mp_float_t exp_real = MICROPY_FLOAT_C_FUN(exp)(real); return mp_obj_new_complex(exp_real * MICROPY_FLOAT_C_FUN(cos)(imag), exp_real * MICROPY_FLOAT_C_FUN(sin)(imag)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_exp_obj, mp_cmath_exp); // log(z): return the natural logarithm of z, with branch cut along the negative real axis // TODO can take second argument, being the base STATIC mp_obj_t mp_cmath_log(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); return mp_obj_new_complex(MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(log)(real * real + imag * imag), MICROPY_FLOAT_C_FUN(atan2)(imag, real)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_log_obj, mp_cmath_log); #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS // log10(z): return the base-10 logarithm of z, with branch cut along the negative real axis STATIC mp_obj_t mp_cmath_log10(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); return mp_obj_new_complex(MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(log10)(real * real + imag * imag), MICROPY_FLOAT_CONST(0.4342944819032518) * MICROPY_FLOAT_C_FUN(atan2)(imag, real)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_log10_obj, mp_cmath_log10); #endif // sqrt(z): return the square-root of z STATIC mp_obj_t mp_cmath_sqrt(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); mp_float_t sqrt_abs = MICROPY_FLOAT_C_FUN(pow)(real * real + imag * imag, MICROPY_FLOAT_CONST(0.25)); mp_float_t theta = MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(atan2)(imag, real); return mp_obj_new_complex(sqrt_abs * MICROPY_FLOAT_C_FUN(cos)(theta), sqrt_abs * MICROPY_FLOAT_C_FUN(sin)(theta)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_sqrt_obj, mp_cmath_sqrt); // cos(z): return the cosine of z STATIC mp_obj_t mp_cmath_cos(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); return mp_obj_new_complex(MICROPY_FLOAT_C_FUN(cos)(real) * MICROPY_FLOAT_C_FUN(cosh)(imag), -MICROPY_FLOAT_C_FUN(sin)(real) * MICROPY_FLOAT_C_FUN(sinh)(imag)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_cos_obj, mp_cmath_cos); // sin(z): return the sine of z STATIC mp_obj_t mp_cmath_sin(mp_obj_t z_obj) { mp_float_t real, imag; mp_obj_get_complex(z_obj, &real, &imag); return mp_obj_new_complex(MICROPY_FLOAT_C_FUN(sin)(real) * MICROPY_FLOAT_C_FUN(cosh)(imag), MICROPY_FLOAT_C_FUN(cos)(real) * MICROPY_FLOAT_C_FUN(sinh)(imag)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_sin_obj, mp_cmath_sin); STATIC const mp_rom_map_elem_t mp_module_cmath_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cmath) }, { MP_ROM_QSTR(MP_QSTR_e), mp_const_float_e }, { MP_ROM_QSTR(MP_QSTR_pi), mp_const_float_pi }, { MP_ROM_QSTR(MP_QSTR_phase), MP_ROM_PTR(&mp_cmath_phase_obj) }, { MP_ROM_QSTR(MP_QSTR_polar), MP_ROM_PTR(&mp_cmath_polar_obj) }, { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&mp_cmath_rect_obj) }, { MP_ROM_QSTR(MP_QSTR_exp), MP_ROM_PTR(&mp_cmath_exp_obj) }, { MP_ROM_QSTR(MP_QSTR_log), MP_ROM_PTR(&mp_cmath_log_obj) }, #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS { MP_ROM_QSTR(MP_QSTR_log10), MP_ROM_PTR(&mp_cmath_log10_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_sqrt), MP_ROM_PTR(&mp_cmath_sqrt_obj) }, // { MP_ROM_QSTR(MP_QSTR_acos), MP_ROM_PTR(&mp_cmath_acos_obj) }, // { MP_ROM_QSTR(MP_QSTR_asin), MP_ROM_PTR(&mp_cmath_asin_obj) }, // { MP_ROM_QSTR(MP_QSTR_atan), MP_ROM_PTR(&mp_cmath_atan_obj) }, { MP_ROM_QSTR(MP_QSTR_cos), MP_ROM_PTR(&mp_cmath_cos_obj) }, { MP_ROM_QSTR(MP_QSTR_sin), MP_ROM_PTR(&mp_cmath_sin_obj) }, // { MP_ROM_QSTR(MP_QSTR_tan), MP_ROM_PTR(&mp_cmath_tan_obj) }, // { MP_ROM_QSTR(MP_QSTR_acosh), MP_ROM_PTR(&mp_cmath_acosh_obj) }, // { MP_ROM_QSTR(MP_QSTR_asinh), MP_ROM_PTR(&mp_cmath_asinh_obj) }, // { MP_ROM_QSTR(MP_QSTR_atanh), MP_ROM_PTR(&mp_cmath_atanh_obj) }, // { MP_ROM_QSTR(MP_QSTR_cosh), MP_ROM_PTR(&mp_cmath_cosh_obj) }, // { MP_ROM_QSTR(MP_QSTR_sinh), MP_ROM_PTR(&mp_cmath_sinh_obj) }, // { MP_ROM_QSTR(MP_QSTR_tanh), MP_ROM_PTR(&mp_cmath_tanh_obj) }, // { MP_ROM_QSTR(MP_QSTR_isfinite), MP_ROM_PTR(&mp_cmath_isfinite_obj) }, // { MP_ROM_QSTR(MP_QSTR_isinf), MP_ROM_PTR(&mp_cmath_isinf_obj) }, // { MP_ROM_QSTR(MP_QSTR_isnan), MP_ROM_PTR(&mp_cmath_isnan_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_cmath_globals, mp_module_cmath_globals_table); const mp_obj_module_t mp_module_cmath = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_cmath_globals, }; #endif // MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_CMATH #ifndef __IPU__ #include #endif #include "py/builtin.h" #include "py/stackctrl.h" #include "py/runtime.h" #include "py/gc.h" #include "py/mphal.h" // Various builtins specific to MicroPython runtime, // living in micropython module #if MICROPY_ENABLE_COMPILER STATIC mp_obj_t mp_micropython_opt_level(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { return MP_OBJ_NEW_SMALL_INT(MP_STATE_VM(mp_optimise_value)); } else { MP_STATE_VM(mp_optimise_value) = mp_obj_get_int(args[0]); return mp_const_none; } } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_opt_level_obj, 0, 1, mp_micropython_opt_level); #endif #if MICROPY_PY_MICROPYTHON_MEM_INFO #if MICROPY_MEM_STATS STATIC mp_obj_t mp_micropython_mem_total(void) { return MP_OBJ_NEW_SMALL_INT(m_get_total_bytes_allocated()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_total_obj, mp_micropython_mem_total); STATIC mp_obj_t mp_micropython_mem_current(void) { return MP_OBJ_NEW_SMALL_INT(m_get_current_bytes_allocated()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_current_obj, mp_micropython_mem_current); STATIC mp_obj_t mp_micropython_mem_peak(void) { return MP_OBJ_NEW_SMALL_INT(m_get_peak_bytes_allocated()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_peak_obj, mp_micropython_mem_peak); #endif mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args) { (void)args; #if MICROPY_MEM_STATS mp_printf(&mp_plat_print, "mem: total=" UINT_FMT ", current=" UINT_FMT ", peak=" UINT_FMT "\n", (mp_uint_t)m_get_total_bytes_allocated(), (mp_uint_t)m_get_current_bytes_allocated(), (mp_uint_t)m_get_peak_bytes_allocated()); #endif #if MICROPY_STACK_CHECK mp_printf(&mp_plat_print, "stack: " UINT_FMT " out of " UINT_FMT "\n", mp_stack_usage(), (mp_uint_t)MP_STATE_THREAD(stack_limit)); #else mp_printf(&mp_plat_print, "stack: " UINT_FMT "\n", mp_stack_usage()); #endif #if MICROPY_ENABLE_GC gc_dump_info(); if (n_args == 1) { // arg given means dump gc allocation table gc_dump_alloc_table(); } #else (void)n_args; #endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_mem_info_obj, 0, 1, mp_micropython_mem_info); STATIC mp_obj_t mp_micropython_qstr_info(size_t n_args, const mp_obj_t *args) { (void)args; size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); mp_printf(&mp_plat_print, "qstr pool: n_pool=%u, n_qstr=%u, n_str_data_bytes=%u, n_total_bytes=%u\n", n_pool, n_qstr, n_str_data_bytes, n_total_bytes); if (n_args == 1) { // arg given means dump qstr data qstr_dump_data(); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_qstr_info_obj, 0, 1, mp_micropython_qstr_info); #endif // MICROPY_PY_MICROPYTHON_MEM_INFO #if MICROPY_PY_MICROPYTHON_STACK_USE STATIC mp_obj_t mp_micropython_stack_use(void) { return MP_OBJ_NEW_SMALL_INT(mp_stack_usage()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_stack_use_obj, mp_micropython_stack_use); #endif #if MICROPY_ENABLE_PYSTACK STATIC mp_obj_t mp_micropython_pystack_use(void) { return MP_OBJ_NEW_SMALL_INT(mp_pystack_usage()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_pystack_use_obj, mp_micropython_pystack_use); #endif #if MICROPY_ENABLE_GC STATIC mp_obj_t mp_micropython_heap_lock(void) { gc_lock(); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_lock_obj, mp_micropython_heap_lock); STATIC mp_obj_t mp_micropython_heap_unlock(void) { gc_unlock(); return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_unlock_obj, mp_micropython_heap_unlock); #if MICROPY_PY_MICROPYTHON_HEAP_LOCKED STATIC mp_obj_t mp_micropython_heap_locked(void) { return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_locked_obj, mp_micropython_heap_locked); #endif #endif #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0) STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_alloc_emergency_exception_buf_obj, mp_alloc_emergency_exception_buf); #endif #if MICROPY_KBD_EXCEPTION STATIC mp_obj_t mp_micropython_kbd_intr(mp_obj_t int_chr_in) { mp_hal_set_interrupt_char(mp_obj_get_int(int_chr_in)); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_micropython_kbd_intr_obj, mp_micropython_kbd_intr); #endif #if MICROPY_ENABLE_SCHEDULER STATIC mp_obj_t mp_micropython_schedule(mp_obj_t function, mp_obj_t arg) { if (!mp_sched_schedule(function, arg)) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("schedule queue full")); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_micropython_schedule_obj, mp_micropython_schedule); #endif STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_micropython) }, { MP_ROM_QSTR(MP_QSTR_const), MP_ROM_PTR(&mp_identity_obj) }, #if MICROPY_ENABLE_COMPILER { MP_ROM_QSTR(MP_QSTR_opt_level), MP_ROM_PTR(&mp_micropython_opt_level_obj) }, #endif #if MICROPY_PY_MICROPYTHON_MEM_INFO #if MICROPY_MEM_STATS { MP_ROM_QSTR(MP_QSTR_mem_total), MP_ROM_PTR(&mp_micropython_mem_total_obj) }, { MP_ROM_QSTR(MP_QSTR_mem_current), MP_ROM_PTR(&mp_micropython_mem_current_obj) }, { MP_ROM_QSTR(MP_QSTR_mem_peak), MP_ROM_PTR(&mp_micropython_mem_peak_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_mem_info), MP_ROM_PTR(&mp_micropython_mem_info_obj) }, { MP_ROM_QSTR(MP_QSTR_qstr_info), MP_ROM_PTR(&mp_micropython_qstr_info_obj) }, #endif #if MICROPY_PY_MICROPYTHON_STACK_USE { MP_ROM_QSTR(MP_QSTR_stack_use), MP_ROM_PTR(&mp_micropython_stack_use_obj) }, #endif #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0) { MP_ROM_QSTR(MP_QSTR_alloc_emergency_exception_buf), MP_ROM_PTR(&mp_alloc_emergency_exception_buf_obj) }, #endif #if MICROPY_ENABLE_PYSTACK { MP_ROM_QSTR(MP_QSTR_pystack_use), MP_ROM_PTR(&mp_micropython_pystack_use_obj) }, #endif #if MICROPY_ENABLE_GC { MP_ROM_QSTR(MP_QSTR_heap_lock), MP_ROM_PTR(&mp_micropython_heap_lock_obj) }, { MP_ROM_QSTR(MP_QSTR_heap_unlock), MP_ROM_PTR(&mp_micropython_heap_unlock_obj) }, #if MICROPY_PY_MICROPYTHON_HEAP_LOCKED { MP_ROM_QSTR(MP_QSTR_heap_locked), MP_ROM_PTR(&mp_micropython_heap_locked_obj) }, #endif #endif #if MICROPY_KBD_EXCEPTION { MP_ROM_QSTR(MP_QSTR_kbd_intr), MP_ROM_PTR(&mp_micropython_kbd_intr_obj) }, #endif #if MICROPY_ENABLE_SCHEDULER { MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_micropython_globals, mp_module_micropython_globals_table); const mp_obj_module_t mp_module_micropython = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_micropython_globals, }; #include #include #include "py/runtime.h" #include "py/builtin.h" #include "py/objtuple.h" #include "py/binary.h" #include "py/parsenum.h" #if MICROPY_PY_STRUCT /* This module implements most of character typecodes from CPython, with some extensions: O - (Pointer to) an arbitrary Python object. This is useful for callback data, etc. Note that you must keep reference to passed object in your Python application, otherwise it may be garbage-collected, and then when you get back this value from callback it may be invalid (and lead to crash). S - Pointer to a string (returned as a Python string). Note the difference from "Ns", - the latter says "in this place of structure is character data of up to N bytes length", while "S" means "in this place of a structure is a pointer to zero-terminated character data". */ STATIC char get_fmt_type(const char **fmt) { char t = **fmt; switch (t) { case '!': t = '>'; break; case '@': case '=': case '<': case '>': break; default: return '@'; } // Skip type char (*fmt)++; return t; } STATIC mp_uint_t get_fmt_num(const char **p) { const char *num = *p; uint len = 1; while (unichar_isdigit(*++num)) { len++; } mp_uint_t val = (mp_uint_t)MP_OBJ_SMALL_INT_VALUE(mp_parse_num_integer(*p, len, 10, NULL)); *p = num; return val; } STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { char fmt_type = get_fmt_type(&fmt); size_t total_cnt = 0; size_t size; for (size = 0; *fmt; fmt++) { mp_uint_t cnt = 1; if (unichar_isdigit(*fmt)) { cnt = get_fmt_num(&fmt); } if (*fmt == 's') { total_cnt += 1; size += cnt; } else { total_cnt += cnt; size_t align; size_t sz = mp_binary_get_size(fmt_type, *fmt, &align); while (cnt--) { // Apply alignment size = (size + align - 1) & ~(align - 1); size += sz; } } } *total_sz = size; return total_cnt; } STATIC mp_obj_t struct_calcsize(mp_obj_t fmt_in) { const char *fmt = mp_obj_str_get_str(fmt_in); size_t size; calc_size_items(fmt, &size); return MP_OBJ_NEW_SMALL_INT(size); } MP_DEFINE_CONST_FUN_OBJ_1(struct_calcsize_obj, struct_calcsize); STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { // unpack requires that the buffer be exactly the right size. // unpack_from requires that the buffer be "big enough". // Since we implement unpack and unpack_from using the same function // we relax the "exact" requirement, and only implement "big enough". const char *fmt = mp_obj_str_get_str(args[0]); size_t total_sz; size_t num_items = calc_size_items(fmt, &total_sz); char fmt_type = get_fmt_type(&fmt); mp_obj_tuple_t *res = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_items, NULL)); mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); byte *p = bufinfo.buf; byte *end_p = &p[bufinfo.len]; mp_int_t offset = 0; if (n_args > 2) { // offset arg provided offset = mp_obj_get_int(args[2]); if (offset < 0) { // negative offsets are relative to the end of the buffer offset = bufinfo.len + offset; if (offset < 0) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } } p += offset; } byte *p_base = p; // Check that the input buffer is big enough to unpack all the values if (p + total_sz > end_p) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } for (size_t i = 0; i < num_items;) { mp_uint_t cnt = 1; if (unichar_isdigit(*fmt)) { cnt = get_fmt_num(&fmt); } mp_obj_t item; if (*fmt == 's') { item = mp_obj_new_bytes(p, cnt); p += cnt; res->items[i++] = item; } else { while (cnt--) { item = mp_binary_get_val(fmt_type, *fmt, p_base, &p); res->items[i++] = item; } } fmt++; } return MP_OBJ_FROM_PTR(res); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_unpack_from_obj, 2, 3, struct_unpack_from); // This function assumes there is enough room in p to store all the values STATIC void struct_pack_into_internal(mp_obj_t fmt_in, byte *p, size_t n_args, const mp_obj_t *args) { const char *fmt = mp_obj_str_get_str(fmt_in); char fmt_type = get_fmt_type(&fmt); byte *p_base = p; size_t i; for (i = 0; i < n_args;) { mp_uint_t cnt = 1; if (*fmt == '\0') { // more arguments given than used by format string; CPython raises struct.error here break; } if (unichar_isdigit(*fmt)) { cnt = get_fmt_num(&fmt); } if (*fmt == 's') { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[i++], &bufinfo, MP_BUFFER_READ); mp_uint_t to_copy = cnt; if (bufinfo.len < to_copy) { to_copy = bufinfo.len; } memcpy(p, bufinfo.buf, to_copy); memset(p + to_copy, 0, cnt - to_copy); p += cnt; } else { // If we run out of args then we just finish; CPython would raise struct.error while (cnt-- && i < n_args) { mp_binary_set_val(fmt_type, *fmt, args[i++], p_base, &p); } } fmt++; } } STATIC mp_obj_t struct_pack(size_t n_args, const mp_obj_t *args) { // TODO: "The arguments must match the values required by the format exactly." mp_int_t size = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); vstr_t vstr; vstr_init_len(&vstr, size); byte *p = (byte *)vstr.buf; memset(p, 0, size); struct_pack_into_internal(args[0], p, n_args - 1, &args[1]); return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_pack_obj, 1, MP_OBJ_FUN_ARGS_MAX, struct_pack); STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); mp_int_t offset = mp_obj_get_int(args[2]); if (offset < 0) { // negative offsets are relative to the end of the buffer offset = (mp_int_t)bufinfo.len + offset; if (offset < 0) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } } byte *p = (byte *)bufinfo.buf; byte *end_p = &p[bufinfo.len]; p += offset; // Check that the output buffer is big enough to hold all the values mp_int_t sz = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); if (p + sz > end_p) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } struct_pack_into_internal(args[0], p, n_args - 3, &args[3]); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_pack_into_obj, 3, MP_OBJ_FUN_ARGS_MAX, struct_pack_into); STATIC const mp_rom_map_elem_t mp_module_struct_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ustruct) }, { MP_ROM_QSTR(MP_QSTR_calcsize), MP_ROM_PTR(&struct_calcsize_obj) }, { MP_ROM_QSTR(MP_QSTR_pack), MP_ROM_PTR(&struct_pack_obj) }, { MP_ROM_QSTR(MP_QSTR_pack_into), MP_ROM_PTR(&struct_pack_into_obj) }, { MP_ROM_QSTR(MP_QSTR_unpack), MP_ROM_PTR(&struct_unpack_from_obj) }, { MP_ROM_QSTR(MP_QSTR_unpack_from), MP_ROM_PTR(&struct_unpack_from_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_struct_globals, mp_module_struct_globals_table); const mp_obj_module_t mp_module_ustruct = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_struct_globals, }; #endif /* * Copyright (c) 2013, 2014 Damien P. George * Copyright (c) 2014-2017 Paul Sokolovsky */ #include "py/builtin.h" #include "py/objlist.h" #include "py/objmodule.h" #include "py/objtuple.h" #include "py/objstr.h" #include "py/objint.h" #include "py/objtype.h" #include "py/stream.h" #include "py/smallint.h" #include "py/runtime.h" #include "py/persistentcode.h" #if MICROPY_PY_SYS_SETTRACE #include "py/objmodule.h" #include "py/profile.h" #endif #if MICROPY_PY_SYS // defined per port; type of these is irrelevant, just need pointer extern struct _mp_dummy_t mp_sys_stdin_obj; extern struct _mp_dummy_t mp_sys_stdout_obj; extern struct _mp_dummy_t mp_sys_stderr_obj; #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES const mp_print_t mp_sys_stdout_print = {&mp_sys_stdout_obj, mp_stream_write_adaptor}; #endif // version - Python language version that this implementation conforms to, as a string STATIC const MP_DEFINE_STR_OBJ(mp_sys_version_obj, "3.4.0"); // version_info - Python language version that this implementation conforms to, as a tuple of ints #define I(n) MP_OBJ_NEW_SMALL_INT(n) // TODO: CPython is now at 5-element array, but save 2 els so far... STATIC const mp_obj_tuple_t mp_sys_version_info_obj = {{&mp_type_tuple}, 3, {I(3), I(4), I(0)}}; // sys.implementation object // this holds the MicroPython version STATIC const mp_obj_tuple_t mp_sys_implementation_version_info_obj = { {&mp_type_tuple}, 3, { I(MICROPY_VERSION_MAJOR), I(MICROPY_VERSION_MINOR), I(MICROPY_VERSION_MICRO) } }; #if MICROPY_PERSISTENT_CODE_LOAD #define SYS_IMPLEMENTATION_ELEMS \ MP_ROM_QSTR(MP_QSTR_micropython), \ MP_ROM_PTR(&mp_sys_implementation_version_info_obj), \ MP_ROM_INT(MPY_FILE_HEADER_INT) #else #define SYS_IMPLEMENTATION_ELEMS \ MP_ROM_QSTR(MP_QSTR_micropython), \ MP_ROM_PTR(&mp_sys_implementation_version_info_obj) #endif #if MICROPY_PY_ATTRTUPLE STATIC const qstr impl_fields[] = { MP_QSTR_name, MP_QSTR_version, #if MICROPY_PERSISTENT_CODE_LOAD MP_QSTR_mpy, #endif }; STATIC MP_DEFINE_ATTRTUPLE( mp_sys_implementation_obj, impl_fields, 2 + MICROPY_PERSISTENT_CODE_LOAD, SYS_IMPLEMENTATION_ELEMS ); #else STATIC const mp_rom_obj_tuple_t mp_sys_implementation_obj = { {&mp_type_tuple}, 2 + MICROPY_PERSISTENT_CODE_LOAD, { SYS_IMPLEMENTATION_ELEMS } }; #endif #undef I #ifdef MICROPY_PY_SYS_PLATFORM // platform - the platform that MicroPython is running on STATIC const MP_DEFINE_STR_OBJ(mp_sys_platform_obj, MICROPY_PY_SYS_PLATFORM); #endif // exit([retval]): raise SystemExit, with optional argument given to the exception STATIC mp_obj_t mp_sys_exit(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { mp_raise_type(&mp_type_SystemExit); } else { mp_raise_type_arg(&mp_type_SystemExit, args[0]); } } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_exit_obj, 0, 1, mp_sys_exit); STATIC mp_obj_t mp_sys_print_exception(size_t n_args, const mp_obj_t *args) { #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES void *stream_obj = &mp_sys_stdout_obj; if (n_args > 1) { mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE); stream_obj = MP_OBJ_TO_PTR(args[1]); } mp_print_t print = {stream_obj, mp_stream_write_adaptor}; mp_obj_print_exception(&print, args[0]); #else (void)n_args; mp_obj_print_exception(&mp_plat_print, args[0]); #endif return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_print_exception_obj, 1, 2, mp_sys_print_exception); #if MICROPY_PY_SYS_EXC_INFO STATIC mp_obj_t mp_sys_exc_info(void) { mp_obj_t cur_exc = MP_OBJ_FROM_PTR(MP_STATE_VM(cur_exception)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); if (cur_exc == MP_OBJ_NULL) { t->items[0] = mp_const_none; t->items[1] = mp_const_none; t->items[2] = mp_const_none; return MP_OBJ_FROM_PTR(t); } t->items[0] = MP_OBJ_FROM_PTR(mp_obj_get_type(cur_exc)); t->items[1] = cur_exc; t->items[2] = mp_const_none; return MP_OBJ_FROM_PTR(t); } MP_DEFINE_CONST_FUN_OBJ_0(mp_sys_exc_info_obj, mp_sys_exc_info); #endif #if MICROPY_PY_SYS_GETSIZEOF STATIC mp_obj_t mp_sys_getsizeof(mp_obj_t obj) { return mp_unary_op(MP_UNARY_OP_SIZEOF, obj); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_getsizeof_obj, mp_sys_getsizeof); #endif #if MICROPY_PY_SYS_ATEXIT // atexit(callback): Callback is called when sys.exit is called. STATIC mp_obj_t mp_sys_atexit(mp_obj_t obj) { mp_obj_t old = MP_STATE_VM(sys_exitfunc); MP_STATE_VM(sys_exitfunc) = obj; return old; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_atexit_obj, mp_sys_atexit); #endif #if MICROPY_PY_SYS_SETTRACE // settrace(tracefunc): Set the system's trace function. STATIC mp_obj_t mp_sys_settrace(mp_obj_t obj) { return mp_prof_settrace(obj); } MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); #endif // MICROPY_PY_SYS_SETTRACE #if MICROPY_PY_SYS_ATTR_DELEGATION STATIC const uint16_t sys_mutable_keys[] = { #if MICROPY_PY_SYS_PS1_PS2 MP_QSTR_ps1, MP_QSTR_ps2, #endif #if MICROPY_PY_SYS_TRACEBACKLIMIT MP_QSTR_tracebacklimit, #endif MP_QSTRnull, }; STATIC void mp_module_sys_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { MP_STATIC_ASSERT(MP_ARRAY_SIZE(sys_mutable_keys) == MP_SYS_MUTABLE_NUM + 1); MP_STATIC_ASSERT(MP_ARRAY_SIZE(MP_STATE_VM(sys_mutable)) == MP_SYS_MUTABLE_NUM); mp_module_generic_attr(attr, dest, sys_mutable_keys, MP_STATE_VM(sys_mutable)); } #endif STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys) }, { MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&MP_STATE_VM(mp_sys_path_obj)) }, { MP_ROM_QSTR(MP_QSTR_argv), MP_ROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)) }, { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&mp_sys_version_obj) }, { MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&mp_sys_version_info_obj) }, { MP_ROM_QSTR(MP_QSTR_implementation), MP_ROM_PTR(&mp_sys_implementation_obj) }, #ifdef MICROPY_PY_SYS_PLATFORM { MP_ROM_QSTR(MP_QSTR_platform), MP_ROM_PTR(&mp_sys_platform_obj) }, #endif #if MP_ENDIANNESS_LITTLE { MP_ROM_QSTR(MP_QSTR_byteorder), MP_ROM_QSTR(MP_QSTR_little) }, #else { MP_ROM_QSTR(MP_QSTR_byteorder), MP_ROM_QSTR(MP_QSTR_big) }, #endif #if MICROPY_PY_SYS_MAXSIZE #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE // Maximum mp_int_t value is not representable as small int, so we have // little choice but to use MP_SMALL_INT_MAX. Apps also should be careful // to not try to compare sys.maxsize to some literal number (as this // number might not fit in available int size), but instead count number // of "one" bits in sys.maxsize. { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_INT(MP_SMALL_INT_MAX) }, #else { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_PTR(&mp_sys_maxsize_obj) }, #endif #endif #if MICROPY_PY_SYS_EXIT { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&mp_sys_exit_obj) }, #endif #if MICROPY_PY_SYS_SETTRACE { MP_ROM_QSTR(MP_QSTR_settrace), MP_ROM_PTR(&mp_sys_settrace_obj) }, #endif #if MICROPY_PY_SYS_STDFILES { MP_ROM_QSTR(MP_QSTR_stdin), MP_ROM_PTR(&mp_sys_stdin_obj) }, { MP_ROM_QSTR(MP_QSTR_stdout), MP_ROM_PTR(&mp_sys_stdout_obj) }, { MP_ROM_QSTR(MP_QSTR_stderr), MP_ROM_PTR(&mp_sys_stderr_obj) }, #endif #if MICROPY_PY_SYS_MODULES { MP_ROM_QSTR(MP_QSTR_modules), MP_ROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)) }, #endif #if MICROPY_PY_SYS_EXC_INFO { MP_ROM_QSTR(MP_QSTR_exc_info), MP_ROM_PTR(&mp_sys_exc_info_obj) }, #endif #if MICROPY_PY_SYS_GETSIZEOF { MP_ROM_QSTR(MP_QSTR_getsizeof), MP_ROM_PTR(&mp_sys_getsizeof_obj) }, #endif /* * Extensions to CPython */ { MP_ROM_QSTR(MP_QSTR_print_exception), MP_ROM_PTR(&mp_sys_print_exception_obj) }, #if MICROPY_PY_SYS_ATEXIT { MP_ROM_QSTR(MP_QSTR_atexit), MP_ROM_PTR(&mp_sys_atexit_obj) }, #endif #if MICROPY_PY_SYS_ATTR_DELEGATION // Delegation of attr lookup. MP_MODULE_ATTR_DELEGATION_ENTRY(&mp_module_sys_attr), #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_sys_globals, mp_module_sys_globals_table); const mp_obj_module_t mp_module_sys = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_sys_globals, }; #endif #include #include #include "py/obj.h" #include "py/mperrno.h" #if MICROPY_PY_UERRNO // This list can be defined per port in mpconfigport.h to tailor it to a // specific port's needs. If it's not defined then we provide a default. #ifndef MICROPY_PY_UERRNO_LIST #define MICROPY_PY_UERRNO_LIST \ X(EPERM) \ X(ENOENT) \ X(EIO) \ X(EBADF) \ X(EAGAIN) \ X(ENOMEM) \ X(EACCES) \ X(EEXIST) \ X(ENODEV) \ X(EISDIR) \ X(EINVAL) \ X(EOPNOTSUPP) \ X(EADDRINUSE) \ X(ECONNABORTED) \ X(ECONNRESET) \ X(ENOBUFS) \ X(ENOTCONN) \ X(ETIMEDOUT) \ X(ECONNREFUSED) \ X(EHOSTUNREACH) \ X(EALREADY) \ X(EINPROGRESS) \ #endif #if MICROPY_PY_UERRNO_ERRORCODE STATIC const mp_rom_map_elem_t errorcode_table[] = { #define X(e) { MP_ROM_INT(MP_##e), MP_ROM_QSTR(MP_QSTR_##e) }, MICROPY_PY_UERRNO_LIST #undef X }; STATIC const mp_obj_dict_t errorcode_dict = { .base = {&mp_type_dict}, .map = { .all_keys_are_qstrs = 0, // keys are integers .is_fixed = 1, .is_ordered = 1, .used = MP_ARRAY_SIZE(errorcode_table), .alloc = MP_ARRAY_SIZE(errorcode_table), .table = (mp_map_elem_t *)(mp_rom_map_elem_t *)errorcode_table, }, }; #endif STATIC const mp_rom_map_elem_t mp_module_uerrno_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uerrno) }, #if MICROPY_PY_UERRNO_ERRORCODE { MP_ROM_QSTR(MP_QSTR_errorcode), MP_ROM_PTR(&errorcode_dict) }, #endif #define X(e) { MP_ROM_QSTR(MP_QSTR_##e), MP_ROM_INT(MP_##e) }, MICROPY_PY_UERRNO_LIST #undef X }; STATIC MP_DEFINE_CONST_DICT(mp_module_uerrno_globals, mp_module_uerrno_globals_table); const mp_obj_module_t mp_module_uerrno = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_uerrno_globals, }; qstr mp_errno_to_str(mp_obj_t errno_val) { #if MICROPY_PY_UERRNO_ERRORCODE // We have the errorcode dict so can do a lookup using the hash map mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&errorcode_dict.map, errno_val, MP_MAP_LOOKUP); if (elem == NULL) { return MP_QSTRnull; } else { return MP_OBJ_QSTR_VALUE(elem->value); } #else // We don't have the errorcode dict so do a simple search in the modules dict for (size_t i = 0; i < MP_ARRAY_SIZE(mp_module_uerrno_globals_table); ++i) { if (errno_val == mp_module_uerrno_globals_table[i].value) { return MP_OBJ_QSTR_VALUE(mp_module_uerrno_globals_table[i].key); } } return MP_QSTRnull; #endif } #endif // MICROPY_PY_UERRNO /* * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd */ #ifndef __IPU__ #include #endif #include #include "py/runtime.h" #include "py/stackctrl.h" #if MICROPY_PY_THREAD #include "py/mpthread.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif /****************************************************************/ // Lock object STATIC const mp_obj_type_t mp_type_thread_lock; typedef struct _mp_obj_thread_lock_t { mp_obj_base_t base; mp_thread_mutex_t mutex; volatile bool locked; } mp_obj_thread_lock_t; STATIC mp_obj_thread_lock_t *mp_obj_new_thread_lock(void) { mp_obj_thread_lock_t *self = m_new_obj(mp_obj_thread_lock_t); self->base.type = &mp_type_thread_lock; mp_thread_mutex_init(&self->mutex); self->locked = false; return self; } STATIC mp_obj_t thread_lock_acquire(size_t n_args, const mp_obj_t *args) { mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(args[0]); bool wait = true; if (n_args > 1) { wait = mp_obj_get_int(args[1]); // TODO support timeout arg } MP_THREAD_GIL_EXIT(); int ret = mp_thread_mutex_lock(&self->mutex, wait); MP_THREAD_GIL_ENTER(); if (ret == 0) { return mp_const_false; } else if (ret == 1) { self->locked = true; return mp_const_true; } else { mp_raise_OSError(-ret); } } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock_acquire_obj, 1, 3, thread_lock_acquire); STATIC mp_obj_t thread_lock_release(mp_obj_t self_in) { mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(self_in); if (!self->locked) { mp_raise_msg(&mp_type_RuntimeError, NULL); } self->locked = false; MP_THREAD_GIL_EXIT(); mp_thread_mutex_unlock(&self->mutex); MP_THREAD_GIL_ENTER(); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_release_obj, thread_lock_release); STATIC mp_obj_t thread_lock_locked(mp_obj_t self_in) { mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(self_in); return mp_obj_new_bool(self->locked); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_locked_obj, thread_lock_locked); STATIC mp_obj_t thread_lock___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; // unused return thread_lock_release(args[0]); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock___exit___obj, 4, 4, thread_lock___exit__); STATIC const mp_rom_map_elem_t thread_lock_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_acquire), MP_ROM_PTR(&thread_lock_acquire_obj) }, { MP_ROM_QSTR(MP_QSTR_release), MP_ROM_PTR(&thread_lock_release_obj) }, { MP_ROM_QSTR(MP_QSTR_locked), MP_ROM_PTR(&thread_lock_locked_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&thread_lock_acquire_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&thread_lock___exit___obj) }, }; STATIC MP_DEFINE_CONST_DICT(thread_lock_locals_dict, thread_lock_locals_dict_table); STATIC const mp_obj_type_t mp_type_thread_lock = { { &mp_type_type }, .name = MP_QSTR_lock, .locals_dict = (mp_obj_dict_t *)&thread_lock_locals_dict, }; /****************************************************************/ // _thread module STATIC size_t thread_stack_size = 0; STATIC mp_obj_t mod_thread_get_ident(void) { return mp_obj_new_int_from_uint((uintptr_t)mp_thread_get_state()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_get_ident_obj, mod_thread_get_ident); STATIC mp_obj_t mod_thread_stack_size(size_t n_args, const mp_obj_t *args) { mp_obj_t ret = mp_obj_new_int_from_uint(thread_stack_size); if (n_args == 0) { thread_stack_size = 0; } else { thread_stack_size = mp_obj_get_int(args[0]); } return ret; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_stack_size_obj, 0, 1, mod_thread_stack_size); typedef struct _thread_entry_args_t { mp_obj_dict_t *dict_locals; mp_obj_dict_t *dict_globals; size_t stack_size; mp_obj_t fun; size_t n_args; size_t n_kw; mp_obj_t args[]; } thread_entry_args_t; STATIC void *thread_entry(void *args_in) { // Execution begins here for a new thread. We do not have the GIL. thread_entry_args_t *args = (thread_entry_args_t *)args_in; mp_state_thread_t ts; mp_thread_set_state(&ts); mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan mp_stack_set_limit(args->stack_size); #if MICROPY_ENABLE_PYSTACK // TODO threading and pystack is not fully supported, for now just make a small stack mp_obj_t mini_pystack[128]; mp_pystack_init(mini_pystack, &mini_pystack[128]); #endif // The GC starts off unlocked on this thread. ts.gc_lock_depth = 0; ts.mp_pending_exception = MP_OBJ_NULL; // set locals and globals from the calling context mp_locals_set(args->dict_locals); mp_globals_set(args->dict_globals); MP_THREAD_GIL_ENTER(); // signal that we are set up and running mp_thread_start(); // TODO set more thread-specific state here: // cur_exception (root pointer) DEBUG_printf("[thread] start ts=%p args=%p stack=%p\n", &ts, &args, MP_STATE_THREAD(stack_top)); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); nlr_pop(); } else { // uncaught exception // check for SystemExit mp_obj_base_t *exc = (mp_obj_base_t *)nlr.ret_val; if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // swallow exception silently } else { // print exception out mp_printf(MICROPY_ERROR_PRINTER, "Unhandled exception in thread started by "); mp_obj_print_helper(MICROPY_ERROR_PRINTER, args->fun, PRINT_REPR); mp_printf(MICROPY_ERROR_PRINTER, "\n"); mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(exc)); } } DEBUG_printf("[thread] finish ts=%p\n", &ts); // signal that we are finished mp_thread_finish(); MP_THREAD_GIL_EXIT(); return NULL; } STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) { // This structure holds the Python function and arguments for thread entry. // We copy all arguments into this structure to keep ownership of them. // We must be very careful about root pointers because this pointer may // disappear from our address space before the thread is created. thread_entry_args_t *th_args; // get positional arguments size_t pos_args_len; mp_obj_t *pos_args_items; mp_obj_get_array(args[1], &pos_args_len, &pos_args_items); // check for keyword arguments if (n_args == 2) { // just position arguments th_args = m_new_obj_var(thread_entry_args_t, mp_obj_t, pos_args_len); th_args->n_kw = 0; } else { // positional and keyword arguments if (mp_obj_get_type(args[2]) != &mp_type_dict) { mp_raise_TypeError(MP_ERROR_TEXT("expecting a dict for keyword args")); } mp_map_t *map = &((mp_obj_dict_t *)MP_OBJ_TO_PTR(args[2]))->map; th_args = m_new_obj_var(thread_entry_args_t, mp_obj_t, pos_args_len + 2 * map->used); th_args->n_kw = map->used; // copy across the keyword arguments for (size_t i = 0, n = pos_args_len; i < map->alloc; ++i) { if (mp_map_slot_is_filled(map, i)) { th_args->args[n++] = map->table[i].key; th_args->args[n++] = map->table[i].value; } } } // copy across the positional arguments th_args->n_args = pos_args_len; memcpy(th_args->args, pos_args_items, pos_args_len * sizeof(mp_obj_t)); // pass our locals and globals into the new thread th_args->dict_locals = mp_locals_get(); th_args->dict_globals = mp_globals_get(); // set the stack size to use th_args->stack_size = thread_stack_size; // set the function for thread entry th_args->fun = args[0]; // spawn the thread! mp_thread_create(thread_entry, th_args, &th_args->stack_size); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3, mod_thread_start_new_thread); STATIC mp_obj_t mod_thread_exit(void) { mp_raise_type(&mp_type_SystemExit); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_exit_obj, mod_thread_exit); STATIC mp_obj_t mod_thread_allocate_lock(void) { return MP_OBJ_FROM_PTR(mp_obj_new_thread_lock()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_allocate_lock_obj, mod_thread_allocate_lock); STATIC const mp_rom_map_elem_t mp_module_thread_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__thread) }, { MP_ROM_QSTR(MP_QSTR_LockType), MP_ROM_PTR(&mp_type_thread_lock) }, { MP_ROM_QSTR(MP_QSTR_get_ident), MP_ROM_PTR(&mod_thread_get_ident_obj) }, { MP_ROM_QSTR(MP_QSTR_stack_size), MP_ROM_PTR(&mod_thread_stack_size_obj) }, { MP_ROM_QSTR(MP_QSTR_start_new_thread), MP_ROM_PTR(&mod_thread_start_new_thread_obj) }, { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&mod_thread_exit_obj) }, { MP_ROM_QSTR(MP_QSTR_allocate_lock), MP_ROM_PTR(&mod_thread_allocate_lock_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_thread_globals, mp_module_thread_globals_table); const mp_obj_module_t mp_module_thread = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_thread_globals, }; #endif // MICROPY_PY_THREAD /* * Copyright (c) 2013-2019 Damien P. George * Copyright (c) 2014-2015 Paul Sokolovsky */ #ifndef __IPU__ #include #endif #include #include #include "py/emitglue.h" #include "py/objtype.h" #include "py/objfun.h" #include "py/runtime.h" #include "py/bc0.h" #include "py/profile.h" // *FORMAT-OFF* #if 0 #if MICROPY_PY_THREAD #define TRACE_PREFIX mp_printf(&mp_plat_print, "ts=%p sp=%d ", mp_thread_get_state(), (int)(sp - &code_state->state[0] + 1)) #else #define TRACE_PREFIX mp_printf(&mp_plat_print, "sp=%d ", (int)(sp - &code_state->state[0] + 1)) #endif #define TRACE(ip) TRACE_PREFIX; mp_bytecode_print2(&mp_plat_print, ip, 1, code_state->fun_bc->child_table, &code_state->fun_bc->context->constants); #else #define TRACE(ip) #endif // Value stack grows up (this makes it incompatible with native C stack, but // makes sure that arguments to functions are in natural order arg1..argN // (Python semantics mandates left-to-right evaluation order, including for // function arguments). Stack pointer is pre-incremented and points at the // top element. // Exception stack also grows up, top element is also pointed at. #define DECODE_UINT \ mp_uint_t unum = 0; \ do { \ unum = (unum << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0) #define DECODE_ULABEL size_t ulab = (ip[0] | (ip[1] << 8)); ip += 2 #define DECODE_SLABEL size_t slab = (ip[0] | (ip[1] << 8)) - 0x8000; ip += 2 #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE #define DECODE_QSTR \ DECODE_UINT; \ qstr qst = qstr_table[unum] #else #define DECODE_QSTR \ DECODE_UINT; \ qstr qst = unum; #endif #define DECODE_PTR \ DECODE_UINT; \ void *ptr = (void *)(uintptr_t)code_state->fun_bc->child_table[unum] #define DECODE_OBJ \ DECODE_UINT; \ mp_obj_t obj = (mp_obj_t)code_state->fun_bc->context->constants.obj_table[unum] #define PUSH(val) *++sp = (val) #define POP() (*sp--) #define TOP() (*sp) #define SET_TOP(val) *sp = (val) #if MICROPY_PY_SYS_EXC_INFO #define CLEAR_SYS_EXC_INFO() MP_STATE_VM(cur_exception) = NULL; #else #define CLEAR_SYS_EXC_INFO() #endif #define PUSH_EXC_BLOCK(with_or_finally) do { \ DECODE_ULABEL; /* except labels are always forward */ \ ++exc_sp; \ exc_sp->handler = ip + ulab; \ exc_sp->val_sp = MP_TAGPTR_MAKE(sp, ((with_or_finally) << 1)); \ exc_sp->prev_exc = NULL; \ } while (0) #define POP_EXC_BLOCK() \ exc_sp--; /* pop back to previous exception handler */ \ CLEAR_SYS_EXC_INFO() /* just clear sys.exc_info(), not compliant, but it shouldn't be used in 1st place */ #define CANCEL_ACTIVE_FINALLY(sp) do { \ if (mp_obj_is_small_int(sp[-1])) { \ /* Stack: (..., prev_dest_ip, prev_cause, dest_ip) */ \ /* Cancel the unwind through the previous finally, replace with current one */ \ sp[-2] = sp[0]; \ sp -= 2; \ } else { \ assert(sp[-1] == mp_const_none || mp_obj_is_exception_instance(sp[-1])); \ /* Stack: (..., None/exception, dest_ip) */ \ /* Silence the finally's exception value (may be None or an exception) */ \ sp[-1] = sp[0]; \ --sp; \ } \ } while (0) #if MICROPY_PY_SYS_SETTRACE #define FRAME_SETUP() do { \ assert(code_state != code_state->prev_state); \ MP_STATE_THREAD(current_code_state) = code_state; \ assert(code_state != code_state->prev_state); \ } while(0) #define FRAME_ENTER() do { \ assert(code_state != code_state->prev_state); \ code_state->prev_state = MP_STATE_THREAD(current_code_state); \ assert(code_state != code_state->prev_state); \ if (!mp_prof_is_executing) { \ mp_prof_frame_enter(code_state); \ } \ } while(0) #define FRAME_LEAVE() do { \ assert(code_state != code_state->prev_state); \ MP_STATE_THREAD(current_code_state) = code_state->prev_state; \ assert(code_state != code_state->prev_state); \ } while(0) #define FRAME_UPDATE() do { \ assert(MP_STATE_THREAD(current_code_state) == code_state); \ if (!mp_prof_is_executing) { \ code_state->frame = MP_OBJ_TO_PTR(mp_prof_frame_update(code_state)); \ } \ } while(0) #define TRACE_TICK(current_ip, current_sp, is_exception) do { \ assert(code_state != code_state->prev_state); \ assert(MP_STATE_THREAD(current_code_state) == code_state); \ if (!mp_prof_is_executing && code_state->frame && MP_STATE_THREAD(prof_trace_callback)) { \ MP_PROF_INSTR_DEBUG_PRINT(code_state->ip); \ } \ if (!mp_prof_is_executing && code_state->frame && code_state->frame->callback) { \ mp_prof_instr_tick(code_state, is_exception); \ } \ } while(0) #else // MICROPY_PY_SYS_SETTRACE #define FRAME_SETUP() #define FRAME_ENTER() #define FRAME_LEAVE() #define FRAME_UPDATE() #define TRACE_TICK(current_ip, current_sp, is_exception) #endif // MICROPY_PY_SYS_SETTRACE // fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc) // sp points to bottom of stack which grows up // returns: // MP_VM_RETURN_NORMAL, sp valid, return value in *sp // MP_VM_RETURN_YIELD, ip, sp valid, yielded value in *sp // MP_VM_RETURN_EXCEPTION, exception in state[0] mp_vm_return_kind_t MICROPY_WRAP_MP_EXECUTE_BYTECODE(mp_execute_bytecode)(mp_code_state_t *code_state, volatile mp_obj_t inject_exc) { #define SELECTIVE_EXC_IP (0) #if SELECTIVE_EXC_IP #define MARK_EXC_IP_SELECTIVE() { code_state->ip = ip; } /* stores ip 1 byte past last opcode */ #define MARK_EXC_IP_GLOBAL() #else #define MARK_EXC_IP_SELECTIVE() #define MARK_EXC_IP_GLOBAL() { code_state->ip = ip; } /* stores ip pointing to last opcode */ #endif #if MICROPY_OPT_COMPUTED_GOTO #include "py/vmentrytable.h" #define DISPATCH() do { \ TRACE(ip); \ MARK_EXC_IP_GLOBAL(); \ TRACE_TICK(ip, sp, false); \ goto *entry_table[*ip++]; \ } while (0) #define DISPATCH_WITH_PEND_EXC_CHECK() goto pending_exception_check #define ENTRY(op) entry_##op #define ENTRY_DEFAULT entry_default #else #define DISPATCH() goto dispatch_loop #define DISPATCH_WITH_PEND_EXC_CHECK() goto pending_exception_check #define ENTRY(op) case op #define ENTRY_DEFAULT default #endif // nlr_raise needs to be implemented as a goto, so that the C compiler's flow analyser // sees that it's possible for us to jump from the dispatch loop to the exception // handler. Without this, the code may have a different stack layout in the dispatch // loop and the exception handler, leading to very obscure bugs. #define RAISE(o) do { nlr_pop(); nlr.ret_val = MP_OBJ_TO_PTR(o); goto exception_handler; } while (0) #if MICROPY_STACKLESS run_code_state: ; #endif FRAME_ENTER(); #if MICROPY_STACKLESS run_code_state_from_return: ; #endif FRAME_SETUP(); // Pointers which are constant for particular invocation of mp_execute_bytecode() mp_obj_t * /*const*/ fastn; mp_exc_stack_t * /*const*/ exc_stack; { size_t n_state = code_state->n_state; fastn = &code_state->state[n_state - 1]; exc_stack = (mp_exc_stack_t*)(code_state->state + n_state); } // variables that are visible to the exception handler (declared volatile) mp_exc_stack_t *volatile exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(exc_stack, code_state->exc_sp_idx); // stack grows up, exc_sp points to top of stack #if MICROPY_PY_THREAD_GIL && MICROPY_PY_THREAD_GIL_VM_DIVISOR // This needs to be volatile and outside the VM loop so it persists across handling // of any exceptions. Otherwise it's possible that the VM never gives up the GIL. volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; #endif // outer exception handling loop for (;;) { nlr_buf_t nlr; outer_dispatch_loop: if (nlr_push(&nlr) == 0) { // local variables that are not visible to the exception handler const byte *ip = code_state->ip; mp_obj_t *sp = code_state->sp; #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE const qstr_short_t *qstr_table = code_state->fun_bc->context->constants.qstr_table; #endif mp_obj_t obj_shared; MICROPY_VM_HOOK_INIT // If we have exception to inject, now that we finish setting up // execution context, raise it. This works as if MP_BC_RAISE_OBJ // bytecode was executed. // Injecting exc into yield from generator is a special case, // handled by MP_BC_YIELD_FROM itself if (inject_exc != MP_OBJ_NULL && *ip != MP_BC_YIELD_FROM) { mp_obj_t exc = inject_exc; inject_exc = MP_OBJ_NULL; exc = mp_make_raise_obj(exc); RAISE(exc); } // loop to execute byte code for (;;) { dispatch_loop: #if MICROPY_OPT_COMPUTED_GOTO DISPATCH(); #else TRACE(ip); MARK_EXC_IP_GLOBAL(); TRACE_TICK(ip, sp, false); switch (*ip++) { #endif ENTRY(MP_BC_LOAD_CONST_FALSE): PUSH(mp_const_false); DISPATCH(); ENTRY(MP_BC_LOAD_CONST_NONE): PUSH(mp_const_none); DISPATCH(); ENTRY(MP_BC_LOAD_CONST_TRUE): PUSH(mp_const_true); DISPATCH(); ENTRY(MP_BC_LOAD_CONST_SMALL_INT): { mp_uint_t num = 0; if ((ip[0] & 0x40) != 0) { // Number is negative num--; } do { num = (num << 7) | (*ip & 0x7f); } while ((*ip++ & 0x80) != 0); PUSH(MP_OBJ_NEW_SMALL_INT(num)); DISPATCH(); } ENTRY(MP_BC_LOAD_CONST_STRING): { DECODE_QSTR; PUSH(MP_OBJ_NEW_QSTR(qst)); DISPATCH(); } ENTRY(MP_BC_LOAD_CONST_OBJ): { DECODE_OBJ; PUSH(obj); DISPATCH(); } ENTRY(MP_BC_LOAD_NULL): PUSH(MP_OBJ_NULL); DISPATCH(); ENTRY(MP_BC_LOAD_FAST_N): { DECODE_UINT; obj_shared = fastn[-unum]; load_check: if (obj_shared == MP_OBJ_NULL) { local_name_error: { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NameError, MP_ERROR_TEXT("local variable referenced before assignment")); RAISE(obj); } } PUSH(obj_shared); DISPATCH(); } ENTRY(MP_BC_LOAD_DEREF): { DECODE_UINT; obj_shared = mp_obj_cell_get(fastn[-unum]); goto load_check; } ENTRY(MP_BC_LOAD_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); DISPATCH(); } ENTRY(MP_BC_LOAD_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_global(qst)); DISPATCH(); } ENTRY(MP_BC_LOAD_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t top = TOP(); mp_obj_t obj; #if MICROPY_OPT_LOAD_ATTR_FAST_PATH // For the specific case of an instance type, it implements .attr // and forwards to its members map. Attribute lookups on instance // types are extremely common, so avoid all the other checks and // calls that normally happen first. mp_map_elem_t *elem = NULL; if (mp_obj_is_instance_type(mp_obj_get_type(top))) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); } if (elem) { obj = elem->value; } else #endif { obj = mp_load_attr(top, qst); } SET_TOP(obj); DISPATCH(); } ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_load_method(*sp, qst, sp); sp += 1; DISPATCH(); } ENTRY(MP_BC_LOAD_SUPER_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; sp -= 1; mp_load_super_method(qst, sp - 1); DISPATCH(); } ENTRY(MP_BC_LOAD_BUILD_CLASS): MARK_EXC_IP_SELECTIVE(); PUSH(mp_load_build_class()); DISPATCH(); ENTRY(MP_BC_LOAD_SUBSCR): { MARK_EXC_IP_SELECTIVE(); mp_obj_t index = POP(); SET_TOP(mp_obj_subscr(TOP(), index, MP_OBJ_SENTINEL)); DISPATCH(); } ENTRY(MP_BC_STORE_FAST_N): { DECODE_UINT; fastn[-unum] = POP(); DISPATCH(); } ENTRY(MP_BC_STORE_DEREF): { DECODE_UINT; mp_obj_cell_set(fastn[-unum], POP()); DISPATCH(); } ENTRY(MP_BC_STORE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_name(qst, POP()); DISPATCH(); } ENTRY(MP_BC_STORE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_global(qst, POP()); DISPATCH(); } ENTRY(MP_BC_STORE_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_attr(sp[0], qst, sp[-1]); sp -= 2; DISPATCH(); } ENTRY(MP_BC_STORE_SUBSCR): MARK_EXC_IP_SELECTIVE(); mp_obj_subscr(sp[-1], sp[0], sp[-2]); sp -= 3; DISPATCH(); ENTRY(MP_BC_DELETE_FAST): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; if (fastn[-unum] == MP_OBJ_NULL) { goto local_name_error; } fastn[-unum] = MP_OBJ_NULL; DISPATCH(); } ENTRY(MP_BC_DELETE_DEREF): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; if (mp_obj_cell_get(fastn[-unum]) == MP_OBJ_NULL) { goto local_name_error; } mp_obj_cell_set(fastn[-unum], MP_OBJ_NULL); DISPATCH(); } ENTRY(MP_BC_DELETE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_delete_name(qst); DISPATCH(); } ENTRY(MP_BC_DELETE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_delete_global(qst); DISPATCH(); } ENTRY(MP_BC_DUP_TOP): { mp_obj_t top = TOP(); PUSH(top); DISPATCH(); } ENTRY(MP_BC_DUP_TOP_TWO): sp += 2; sp[0] = sp[-2]; sp[-1] = sp[-3]; DISPATCH(); ENTRY(MP_BC_POP_TOP): sp -= 1; DISPATCH(); ENTRY(MP_BC_ROT_TWO): { mp_obj_t top = sp[0]; sp[0] = sp[-1]; sp[-1] = top; DISPATCH(); } ENTRY(MP_BC_ROT_THREE): { mp_obj_t top = sp[0]; sp[0] = sp[-1]; sp[-1] = sp[-2]; sp[-2] = top; DISPATCH(); } ENTRY(MP_BC_LONGYIELD): { int is_reentering = setjmp(IPUpy_checkpoint_env); if (!is_reentering) { longjmp(IPUpy_exit_env, 1); } DISPATCH(); } ENTRY(MP_BC_IPUSYSCALL): { if (IPUpy_syscall_callback != NULL) { IPUpy_syscall_callback(); } DISPATCH(); } ENTRY(MP_BC_JUMP): { DECODE_SLABEL; ip += slab; DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_POP_JUMP_IF_TRUE): { DECODE_SLABEL; if (mp_obj_is_true(POP())) { ip += slab; } DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_POP_JUMP_IF_FALSE): { DECODE_SLABEL; if (!mp_obj_is_true(POP())) { ip += slab; } DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP): { DECODE_SLABEL; if (mp_obj_is_true(TOP())) { ip += slab; } else { sp--; } DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP): { DECODE_SLABEL; if (mp_obj_is_true(TOP())) { sp--; } else { ip += slab; } DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_SETUP_WITH): { MARK_EXC_IP_SELECTIVE(); // stack: (..., ctx_mgr) mp_obj_t obj = TOP(); mp_load_method(obj, MP_QSTR___exit__, sp); mp_load_method(obj, MP_QSTR___enter__, sp + 2); mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 2); sp += 1; PUSH_EXC_BLOCK(1); PUSH(ret); // stack: (..., __exit__, ctx_mgr, as_value) DISPATCH(); } ENTRY(MP_BC_WITH_CLEANUP): { MARK_EXC_IP_SELECTIVE(); // Arriving here, there's "exception control block" on top of stack, // and __exit__ method (with self) underneath it. Bytecode calls __exit__, // and "deletes" it off stack, shifting "exception control block" // to its place. // The bytecode emitter ensures that there is enough space on the Python // value stack to hold the __exit__ method plus an additional 4 entries. if (TOP() == mp_const_none) { // stack: (..., __exit__, ctx_mgr, None) sp[1] = mp_const_none; sp[2] = mp_const_none; sp -= 2; mp_call_method_n_kw(3, 0, sp); SET_TOP(mp_const_none); } else if (mp_obj_is_small_int(TOP())) { // Getting here there are two distinct cases: // - unwind return, stack: (..., __exit__, ctx_mgr, ret_val, SMALL_INT(-1)) // - unwind jump, stack: (..., __exit__, ctx_mgr, dest_ip, SMALL_INT(num_exc)) // For both cases we do exactly the same thing. mp_obj_t data = sp[-1]; mp_obj_t cause = sp[0]; sp[-1] = mp_const_none; sp[0] = mp_const_none; sp[1] = mp_const_none; mp_call_method_n_kw(3, 0, sp - 3); sp[-3] = data; sp[-2] = cause; sp -= 2; // we removed (__exit__, ctx_mgr) } else { assert(mp_obj_is_exception_instance(TOP())); // stack: (..., __exit__, ctx_mgr, exc_instance) // Need to pass (exc_type, exc_instance, None) as arguments to __exit__. sp[1] = sp[0]; sp[0] = MP_OBJ_FROM_PTR(mp_obj_get_type(sp[0])); sp[2] = mp_const_none; sp -= 2; mp_obj_t ret_value = mp_call_method_n_kw(3, 0, sp); if (mp_obj_is_true(ret_value)) { // We need to silence/swallow the exception. This is done // by popping the exception and the __exit__ handler and // replacing it with None, which signals END_FINALLY to just // execute the finally handler normally. SET_TOP(mp_const_none); } else { // We need to re-raise the exception. We pop __exit__ handler // by copying the exception instance down to the new top-of-stack. sp[0] = sp[3]; } } DISPATCH(); } ENTRY(MP_BC_UNWIND_JUMP): { MARK_EXC_IP_SELECTIVE(); DECODE_SLABEL; PUSH((mp_obj_t)(mp_uint_t)(uintptr_t)(ip + slab)); // push destination ip for jump PUSH((mp_obj_t)(mp_uint_t)(*ip)); // push number of exception handlers to unwind (0x80 bit set if we also need to pop stack) unwind_jump:; mp_uint_t unum = (mp_uint_t)POP(); // get number of exception handlers to unwind while ((unum & 0x7f) > 0) { unum -= 1; assert(exc_sp >= exc_stack); if (MP_TAGPTR_TAG1(exc_sp->val_sp)) { if (exc_sp->handler > ip) { // Found a finally handler that isn't active; run it. // Getting here the stack looks like: // (..., X, dest_ip) // where X is pointed to by exc_sp->val_sp and in the case // of a "with" block contains the context manager info. assert(&sp[-1] == MP_TAGPTR_PTR(exc_sp->val_sp)); // We're going to run "finally" code as a coroutine // (not calling it recursively). Set up a sentinel // on the stack so it can return back to us when it is // done (when WITH_CLEANUP or END_FINALLY reached). // The sentinel is the number of exception handlers left to // unwind, which is a non-negative integer. PUSH(MP_OBJ_NEW_SMALL_INT(unum)); ip = exc_sp->handler; goto dispatch_loop; } else { // Found a finally handler that is already active; cancel it. CANCEL_ACTIVE_FINALLY(sp); } } POP_EXC_BLOCK(); } ip = (const byte*)MP_OBJ_TO_PTR(POP()); // pop destination ip for jump if (unum != 0) { // pop the exhausted iterator sp -= MP_OBJ_ITER_BUF_NSLOTS; } DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_SETUP_EXCEPT): ENTRY(MP_BC_SETUP_FINALLY): { MARK_EXC_IP_SELECTIVE(); #if SELECTIVE_EXC_IP PUSH_EXC_BLOCK((code_state->ip[-1] == MP_BC_SETUP_FINALLY) ? 1 : 0); #else PUSH_EXC_BLOCK((code_state->ip[0] == MP_BC_SETUP_FINALLY) ? 1 : 0); #endif DISPATCH(); } ENTRY(MP_BC_END_FINALLY): MARK_EXC_IP_SELECTIVE(); // if TOS is None, just pops it and continues // if TOS is an integer, finishes coroutine and returns control to caller // if TOS is an exception, reraises the exception assert(exc_sp >= exc_stack); POP_EXC_BLOCK(); if (TOP() == mp_const_none) { sp--; } else if (mp_obj_is_small_int(TOP())) { // We finished "finally" coroutine and now dispatch back // to our caller, based on TOS value mp_int_t cause = MP_OBJ_SMALL_INT_VALUE(POP()); if (cause < 0) { // A negative cause indicates unwind return goto unwind_return; } else { // Otherwise it's an unwind jump and we must push as a raw // number the number of exception handlers to unwind PUSH((mp_obj_t)cause); goto unwind_jump; } } else { assert(mp_obj_is_exception_instance(TOP())); RAISE(TOP()); } DISPATCH(); ENTRY(MP_BC_GET_ITER): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_getiter(TOP(), NULL)); DISPATCH(); // An iterator for a for-loop takes MP_OBJ_ITER_BUF_NSLOTS slots on // the Python value stack. These slots are either used to store the // iterator object itself, or the first slot is MP_OBJ_NULL and // the second slot holds a reference to the iterator object. ENTRY(MP_BC_GET_ITER_STACK): { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = TOP(); mp_obj_iter_buf_t *iter_buf = (mp_obj_iter_buf_t*)sp; sp += MP_OBJ_ITER_BUF_NSLOTS - 1; obj = mp_getiter(obj, iter_buf); if (obj != MP_OBJ_FROM_PTR(iter_buf)) { // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; sp[-MP_OBJ_ITER_BUF_NSLOTS + 2] = obj; } DISPATCH(); } ENTRY(MP_BC_FOR_ITER): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward code_state->sp = sp; mp_obj_t obj; if (sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] == MP_OBJ_NULL) { obj = sp[-MP_OBJ_ITER_BUF_NSLOTS + 2]; } else { obj = MP_OBJ_FROM_PTR(&sp[-MP_OBJ_ITER_BUF_NSLOTS + 1]); } mp_obj_t value = mp_iternext_allow_raise(obj); if (value == MP_OBJ_STOP_ITERATION) { sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator ip += ulab; // jump to after for-block } else { PUSH(value); // push the next iteration value #if MICROPY_PY_SYS_SETTRACE // LINE event should trigger for every iteration so invalidate last trigger if (code_state->frame) { code_state->frame->lineno = 0; } #endif } DISPATCH(); } ENTRY(MP_BC_POP_EXCEPT_JUMP): { assert(exc_sp >= exc_stack); POP_EXC_BLOCK(); DECODE_ULABEL; ip += ulab; DISPATCH_WITH_PEND_EXC_CHECK(); } ENTRY(MP_BC_BUILD_TUPLE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_tuple(unum, sp)); DISPATCH(); } ENTRY(MP_BC_BUILD_LIST): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_list(unum, sp)); DISPATCH(); } ENTRY(MP_BC_BUILD_MAP): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; PUSH(mp_obj_new_dict(unum)); DISPATCH(); } ENTRY(MP_BC_STORE_MAP): MARK_EXC_IP_SELECTIVE(); sp -= 2; mp_obj_dict_store(sp[0], sp[2], sp[1]); DISPATCH(); #if MICROPY_PY_BUILTINS_SET ENTRY(MP_BC_BUILD_SET): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_set(unum, sp)); DISPATCH(); } #endif #if MICROPY_PY_BUILTINS_SLICE ENTRY(MP_BC_BUILD_SLICE): { MARK_EXC_IP_SELECTIVE(); mp_obj_t step = mp_const_none; if (*ip++ == 3) { // 3-argument slice includes step step = POP(); } mp_obj_t stop = POP(); mp_obj_t start = TOP(); SET_TOP(mp_obj_new_slice(start, stop, step)); DISPATCH(); } #endif ENTRY(MP_BC_STORE_COMP): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_obj_t obj = sp[-(unum >> 2)]; if ((unum & 3) == 0) { mp_obj_list_append(obj, sp[0]); sp--; } else if (!MICROPY_PY_BUILTINS_SET || (unum & 3) == 1) { mp_obj_dict_store(obj, sp[0], sp[-1]); sp -= 2; #if MICROPY_PY_BUILTINS_SET } else { mp_obj_set_store(obj, sp[0]); sp--; #endif } DISPATCH(); } ENTRY(MP_BC_UNPACK_SEQUENCE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_unpack_sequence(sp[0], unum, sp); sp += unum - 1; DISPATCH(); } ENTRY(MP_BC_UNPACK_EX): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_unpack_ex(sp[0], unum, sp); sp += (unum & 0xff) + ((unum >> 8) & 0xff); DISPATCH(); } ENTRY(MP_BC_MAKE_FUNCTION): { DECODE_PTR; PUSH(mp_make_function_from_raw_code(ptr, code_state->fun_bc->context, NULL)); DISPATCH(); } ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS): { DECODE_PTR; // Stack layout: def_tuple def_dict <- TOS sp -= 1; SET_TOP(mp_make_function_from_raw_code(ptr, code_state->fun_bc->context, sp)); DISPATCH(); } ENTRY(MP_BC_MAKE_CLOSURE): { DECODE_PTR; size_t n_closed_over = *ip++; // Stack layout: closed_overs <- TOS sp -= n_closed_over - 1; SET_TOP(mp_make_closure_from_raw_code(ptr, code_state->fun_bc->context, n_closed_over, sp)); DISPATCH(); } ENTRY(MP_BC_MAKE_CLOSURE_DEFARGS): { DECODE_PTR; size_t n_closed_over = *ip++; // Stack layout: def_tuple def_dict closed_overs <- TOS sp -= 2 + n_closed_over - 1; SET_TOP(mp_make_closure_from_raw_code(ptr, code_state->fun_bc->context, 0x100 | n_closed_over, sp)); DISPATCH(); } ENTRY(MP_BC_CALL_FUNCTION): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe); #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1); #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT deep_recursion_error: mp_raise_recursion_depth(); #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1)); DISPATCH(); } ENTRY(MP_BC_CALL_FUNCTION_VAR_KW): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword // We have following stack layout here: // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; mp_call_prepare_args_n_kw_var(false, unum, sp, &out_args); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); #if !MICROPY_ENABLE_PYSTACK // Freeing args at this point does not follow a LIFO order so only do it if // pystack is not enabled. For pystack, they are freed when code_state is. mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); #endif #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT goto deep_recursion_error; #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(false, unum, sp)); DISPATCH(); } ENTRY(MP_BC_CALL_METHOD): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); size_t n_args = unum & 0xff; size_t n_kw = (unum >> 8) & 0xff; int adjust = (sp[1] == MP_OBJ_NULL) ? 0 : 1; mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, n_args + adjust, n_kw, sp + 2 - adjust); #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT goto deep_recursion_error; #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp)); DISPATCH(); } ENTRY(MP_BC_CALL_METHOD_VAR_KW): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword // We have following stack layout here: // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 3; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; mp_call_prepare_args_n_kw_var(true, unum, sp, &out_args); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); #if !MICROPY_ENABLE_PYSTACK // Freeing args at this point does not follow a LIFO order so only do it if // pystack is not enabled. For pystack, they are freed when code_state is. mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); #endif #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT goto deep_recursion_error; #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(true, unum, sp)); DISPATCH(); } ENTRY(MP_BC_RETURN_VALUE): MARK_EXC_IP_SELECTIVE(); unwind_return: // Search for and execute finally handlers that aren't already active while (exc_sp >= exc_stack) { if (MP_TAGPTR_TAG1(exc_sp->val_sp)) { if (exc_sp->handler > ip) { // Found a finally handler that isn't active; run it. // Getting here the stack looks like: // (..., X, [iter0, iter1, ...,] ret_val) // where X is pointed to by exc_sp->val_sp and in the case // of a "with" block contains the context manager info. // There may be 0 or more for-iterators between X and the // return value, and these must be removed before control can // pass to the finally code. We simply copy the ret_value down // over these iterators, if they exist. If they don't then the // following is a null operation. mp_obj_t *finally_sp = MP_TAGPTR_PTR(exc_sp->val_sp); finally_sp[1] = sp[0]; sp = &finally_sp[1]; // We're going to run "finally" code as a coroutine // (not calling it recursively). Set up a sentinel // on a stack so it can return back to us when it is // done (when WITH_CLEANUP or END_FINALLY reached). PUSH(MP_OBJ_NEW_SMALL_INT(-1)); ip = exc_sp->handler; goto dispatch_loop; } else { // Found a finally handler that is already active; cancel it. CANCEL_ACTIVE_FINALLY(sp); } } POP_EXC_BLOCK(); } nlr_pop(); code_state->sp = sp; assert(exc_sp == exc_stack - 1); MICROPY_VM_HOOK_RETURN #if MICROPY_STACKLESS if (code_state->prev != NULL) { mp_obj_t res = *sp; mp_globals_set(code_state->old_globals); mp_code_state_t *new_code_state = code_state->prev; #if MICROPY_ENABLE_PYSTACK // Free code_state, and args allocated by mp_call_prepare_args_n_kw_var // (The latter is implicitly freed when using pystack due to its LIFO nature.) // The sizeof in the following statement does not include the size of the variable // part of the struct. This arg is anyway not used if pystack is enabled. mp_nonlocal_free(code_state, sizeof(mp_code_state_t)); #endif code_state = new_code_state; *code_state->sp = res; goto run_code_state_from_return; } #endif FRAME_LEAVE(); return MP_VM_RETURN_NORMAL; ENTRY(MP_BC_RAISE_LAST): { MARK_EXC_IP_SELECTIVE(); // search for the inner-most previous exception, to reraise it mp_obj_t obj = MP_OBJ_NULL; for (mp_exc_stack_t *e = exc_sp; e >= exc_stack; --e) { if (e->prev_exc != NULL) { obj = MP_OBJ_FROM_PTR(e->prev_exc); break; } } if (obj == MP_OBJ_NULL) { obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("no active exception to reraise")); } RAISE(obj); } ENTRY(MP_BC_RAISE_OBJ): { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = mp_make_raise_obj(TOP()); RAISE(obj); } ENTRY(MP_BC_RAISE_FROM): { MARK_EXC_IP_SELECTIVE(); mp_warning(NULL, "exception chaining not supported"); sp--; // ignore (pop) "from" argument mp_obj_t obj = mp_make_raise_obj(TOP()); RAISE(obj); } ENTRY(MP_BC_YIELD_VALUE): yield: nlr_pop(); code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); FRAME_LEAVE(); return MP_VM_RETURN_YIELD; ENTRY(MP_BC_YIELD_FROM): { MARK_EXC_IP_SELECTIVE(); //#define EXC_MATCH(exc, type) mp_obj_is_type(exc, type) #define EXC_MATCH(exc, type) mp_obj_exception_match(exc, type) #define GENERATOR_EXIT_IF_NEEDED(t) if (t != MP_OBJ_NULL && EXC_MATCH(t, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { mp_obj_t raise_t = mp_make_raise_obj(t); RAISE(raise_t); } mp_vm_return_kind_t ret_kind; mp_obj_t send_value = POP(); mp_obj_t t_exc = MP_OBJ_NULL; mp_obj_t ret_value; code_state->sp = sp; // Save sp because it's needed if mp_resume raises StopIteration if (inject_exc != MP_OBJ_NULL) { t_exc = inject_exc; inject_exc = MP_OBJ_NULL; ret_kind = mp_resume(TOP(), MP_OBJ_NULL, t_exc, &ret_value); } else { ret_kind = mp_resume(TOP(), send_value, MP_OBJ_NULL, &ret_value); } if (ret_kind == MP_VM_RETURN_YIELD) { ip--; PUSH(ret_value); goto yield; } else if (ret_kind == MP_VM_RETURN_NORMAL) { // The generator has finished, and returned a value via StopIteration // Replace exhausted generator with the returned value SET_TOP(ret_value); // If we injected GeneratorExit downstream, then even // if it was swallowed, we re-raise GeneratorExit GENERATOR_EXIT_IF_NEEDED(t_exc); DISPATCH(); } else { assert(ret_kind == MP_VM_RETURN_EXCEPTION); assert(!EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))); // Pop exhausted gen sp--; RAISE(ret_value); } } ENTRY(MP_BC_IMPORT_NAME): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t obj = POP(); SET_TOP(mp_import_name(qst, obj, TOP())); DISPATCH(); } ENTRY(MP_BC_IMPORT_FROM): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t obj = mp_import_from(TOP(), qst); PUSH(obj); DISPATCH(); } ENTRY(MP_BC_IMPORT_STAR): MARK_EXC_IP_SELECTIVE(); mp_import_all(POP()); DISPATCH(); #if MICROPY_OPT_COMPUTED_GOTO ENTRY(MP_BC_LOAD_CONST_SMALL_INT_MULTI): PUSH(MP_OBJ_NEW_SMALL_INT((mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS)); DISPATCH(); ENTRY(MP_BC_LOAD_FAST_MULTI): obj_shared = fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)ip[-1]]; goto load_check; ENTRY(MP_BC_STORE_FAST_MULTI): fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)ip[-1]] = POP(); DISPATCH(); ENTRY(MP_BC_UNARY_OP_MULTI): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); DISPATCH(); ENTRY(MP_BC_BINARY_OP_MULTI): { MARK_EXC_IP_SELECTIVE(); mp_obj_t rhs = POP(); mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); DISPATCH(); } ENTRY_DEFAULT: MARK_EXC_IP_SELECTIVE(); #else ENTRY_DEFAULT: if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM) { PUSH(MP_OBJ_NEW_SMALL_INT((mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS)); DISPATCH(); } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + MP_BC_LOAD_FAST_MULTI_NUM) { obj_shared = fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)ip[-1]]; goto load_check; } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + MP_BC_STORE_FAST_MULTI_NUM) { fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)ip[-1]] = POP(); DISPATCH(); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_BC_UNARY_OP_MULTI_NUM) { SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); DISPATCH(); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BC_BINARY_OP_MULTI_NUM) { mp_obj_t rhs = POP(); mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); DISPATCH(); } else #endif { mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, MP_ERROR_TEXT("opcode")); nlr_pop(); code_state->state[0] = obj; FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; } #if !MICROPY_OPT_COMPUTED_GOTO } // switch #endif pending_exception_check: MICROPY_VM_HOOK_LOOP #if MICROPY_ENABLE_SCHEDULER // This is an inlined variant of mp_handle_pending if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); // Re-check state is still pending now that we're in the atomic section. if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); if (obj != MP_OBJ_NULL) { MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; if (!mp_sched_num_pending()) { MP_STATE_VM(sched_state) = MP_SCHED_IDLE; } MICROPY_END_ATOMIC_SECTION(atomic_state); RAISE(obj); } mp_handle_pending_tail(atomic_state); } else { MICROPY_END_ATOMIC_SECTION(atomic_state); } } #else // This is an inlined variant of mp_handle_pending if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; RAISE(obj); } #endif #if MICROPY_PY_THREAD_GIL #if MICROPY_PY_THREAD_GIL_VM_DIVISOR if (--gil_divisor == 0) #endif { #if MICROPY_PY_THREAD_GIL_VM_DIVISOR gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; #endif #if MICROPY_ENABLE_SCHEDULER // can only switch threads if the scheduler is unlocked if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) #endif { MP_THREAD_GIL_EXIT(); MP_THREAD_GIL_ENTER(); } } #endif } // for loop } else { exception_handler: // exception occurred #if MICROPY_PY_SYS_EXC_INFO MP_STATE_VM(cur_exception) = nlr.ret_val; #endif #if SELECTIVE_EXC_IP // with selective ip, we store the ip 1 byte past the opcode, so move ptr back code_state->ip -= 1; #endif if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { if (code_state->ip) { // check if it's a StopIteration within a for block if (*code_state->ip == MP_BC_FOR_ITER) { const byte *ip = code_state->ip + 1; DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward code_state->ip = ip + ulab; // jump to after for-block code_state->sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator goto outer_dispatch_loop; // continue with dispatch loop } else if (*code_state->ip == MP_BC_YIELD_FROM) { // StopIteration inside yield from call means return a value of // yield from, so inject exception's value as yield from's result // (Instead of stack pop then push we just replace exhausted gen with value) *code_state->sp = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val)); code_state->ip++; // yield from is over, move to next instruction goto outer_dispatch_loop; // continue with dispatch loop } } } #if MICROPY_PY_SYS_SETTRACE // Exceptions are traced here if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_Exception))) { TRACE_TICK(code_state->ip, code_state->sp, true /* yes, it's an exception */); } #endif #if MICROPY_STACKLESS unwind_loop: #endif // Set traceback info (file and line number) where the exception occurred, but not for: // - constant GeneratorExit object, because it's const // - exceptions re-raised by END_FINALLY // - exceptions re-raised explicitly by "raise" if (nlr.ret_val != &mp_const_GeneratorExit_obj && *code_state->ip != MP_BC_END_FINALLY && *code_state->ip != MP_BC_RAISE_LAST) { const byte *ip = code_state->fun_bc->bytecode; MP_BC_PRELUDE_SIG_DECODE(ip); MP_BC_PRELUDE_SIZE_DECODE(ip); const byte *line_info_top = ip + n_info; const byte *bytecode_start = ip + n_info + n_cell; size_t bc = code_state->ip - bytecode_start; qstr block_name = mp_decode_uint_value(ip); for (size_t i = 0; i < 1 + n_pos_args + n_kwonly_args; ++i) { ip = mp_decode_uint_skip(ip); } #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE block_name = code_state->fun_bc->context->constants.qstr_table[block_name]; qstr source_file = code_state->fun_bc->context->constants.qstr_table[0]; #else qstr source_file = code_state->fun_bc->context->constants.source_file; #endif size_t source_line = mp_bytecode_get_source_line(ip, line_info_top, bc); mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(nlr.ret_val), source_file, source_line, block_name); } while (exc_sp >= exc_stack && exc_sp->handler <= code_state->ip) { // nested exception assert(exc_sp >= exc_stack); // TODO make a proper message for nested exception // at the moment we are just raising the very last exception (the one that caused the nested exception) // move up to previous exception handler POP_EXC_BLOCK(); } if (exc_sp >= exc_stack) { // catch exception and pass to byte code code_state->ip = exc_sp->handler; mp_obj_t *sp = MP_TAGPTR_PTR(exc_sp->val_sp); // save this exception in the stack so it can be used in a reraise, if needed exc_sp->prev_exc = nlr.ret_val; // push exception object so it can be handled by bytecode PUSH(MP_OBJ_FROM_PTR(nlr.ret_val)); code_state->sp = sp; #if MICROPY_STACKLESS } else if (code_state->prev != NULL) { mp_globals_set(code_state->old_globals); mp_code_state_t *new_code_state = code_state->prev; #if MICROPY_ENABLE_PYSTACK // Free code_state, and args allocated by mp_call_prepare_args_n_kw_var // (The latter is implicitly freed when using pystack due to its LIFO nature.) // The sizeof in the following statement does not include the size of the variable // part of the struct. This arg is anyway not used if pystack is enabled. mp_nonlocal_free(code_state, sizeof(mp_code_state_t)); #endif code_state = new_code_state; size_t n_state = code_state->n_state; fastn = &code_state->state[n_state - 1]; exc_stack = (mp_exc_stack_t*)(code_state->state + n_state); // variables that are visible to the exception handler (declared volatile) exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(exc_stack, code_state->exc_sp_idx); // stack grows up, exc_sp points to top of stack goto unwind_loop; #endif } else { // propagate exception to higher level // Note: ip and sp don't have usable values at this point code_state->state[0] = MP_OBJ_FROM_PTR(nlr.ret_val); // put exception here because sp is invalid FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; } } } } /* * Copyright (c) 2014 Damien P. George * Copyright (c) 2014 Paul Sokolovsky */ #include #include #include #include "py/bc0.h" #include "py/bc.h" #include "py/objfun.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #else // don't print debugging info #define DEBUG_PRINT (0) #define DEBUG_printf(...) (void)0 #endif void mp_encode_uint(void *env, mp_encode_uint_allocator_t allocator, mp_uint_t val) { // We store each 7 bits in a separate byte, and that's how many bytes needed byte buf[MP_ENCODE_UINT_MAX_BYTES]; byte *p = buf + sizeof(buf); // We encode in little-ending order, but store in big-endian, to help decoding do { *--p = val & 0x7f; val >>= 7; } while (val != 0); byte *c = allocator(env, buf + sizeof(buf) - p); if (c != NULL) { while (p != buf + sizeof(buf) - 1) { *c++ = *p++ | 0x80; } *c = *p; } } mp_uint_t mp_decode_uint(const byte **ptr) { mp_uint_t unum = 0; byte val; const byte *p = *ptr; do { val = *p++; unum = (unum << 7) | (val & 0x7f); } while ((val & 0x80) != 0); *ptr = p; return unum; } // This function is used to help reduce stack usage at the caller, for the case when // the caller doesn't need to increase the ptr argument. If ptr is a local variable // and the caller uses mp_decode_uint(&ptr) instead of this function, then the compiler // must allocate a slot on the stack for ptr, and this slot cannot be reused for // anything else in the function because the pointer may have been stored in a global // and reused later in the function. mp_uint_t mp_decode_uint_value(const byte *ptr) { return mp_decode_uint(&ptr); } // This function is used to help reduce stack usage at the caller, for the case when // the caller doesn't need the actual value and just wants to skip over it. const byte *mp_decode_uint_skip(const byte *ptr) { while ((*ptr++) & 0x80) { } return ptr; } STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues (void)f; (void)expected; (void)given; mp_arg_error_terse_mismatch(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL (void)f; mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), expected, given); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("%q() takes %d positional arguments but %d were given"), mp_obj_fun_get_name(MP_OBJ_FROM_PTR(f)), expected, given); #endif } #if DEBUG_PRINT STATIC void dump_args(const mp_obj_t *a, size_t sz) { DEBUG_printf("%p: ", a); for (size_t i = 0; i < sz; i++) { DEBUG_printf("%p ", a[i]); } DEBUG_printf("\n"); } #else #define dump_args(...) (void)0 #endif // On entry code_state should be allocated somewhere (stack/heap) and // contain the following valid entries: // - code_state->fun_bc should contain a pointer to the function object // - code_state->ip should contain a pointer to the beginning of the prelude // - code_state->n_state should be the number of objects in the local state void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { // This function is pretty complicated. It's main aim is to be efficient in speed and RAM // usage for the common case of positional only args. // get the function object that we want to set up (could be bytecode or native code) mp_obj_fun_bc_t *self = code_state->fun_bc; #if MICROPY_STACKLESS code_state->prev = NULL; #endif #if MICROPY_PY_SYS_SETTRACE code_state->prev_state = NULL; code_state->frame = NULL; #endif // Get cached n_state (rather than decode it again) size_t n_state = code_state->n_state; // Decode prelude size_t n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args; MP_BC_PRELUDE_SIG_DECODE_INTO(code_state->ip, n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args); MP_BC_PRELUDE_SIZE_DECODE(code_state->ip); (void)n_state_unused; (void)n_exc_stack_unused; code_state->sp = &code_state->state[0] - 1; code_state->exc_sp_idx = 0; // zero out the local stack to begin with memset(code_state->state, 0, n_state * sizeof(*code_state->state)); const mp_obj_t *kwargs = args + n_args; // var_pos_kw_args points to the stack where the var-args tuple, and var-kw dict, should go (if they are needed) mp_obj_t *var_pos_kw_args = &code_state->state[n_state - 1 - n_pos_args - n_kwonly_args]; // check positional arguments if (n_args > n_pos_args) { // given more than enough arguments if ((scope_flags & MP_SCOPE_FLAG_VARARGS) == 0) { fun_pos_args_mismatch(self, n_pos_args, n_args); } // put extra arguments in varargs tuple *var_pos_kw_args-- = mp_obj_new_tuple(n_args - n_pos_args, args + n_pos_args); n_args = n_pos_args; } else { if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { DEBUG_printf("passing empty tuple as *args\n"); *var_pos_kw_args-- = mp_const_empty_tuple; } // Apply processing and check below only if we don't have kwargs, // otherwise, kw handling code below has own extensive checks. if (n_kw == 0 && (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) == 0) { if (n_args >= (size_t)(n_pos_args - n_def_pos_args)) { // given enough arguments, but may need to use some default arguments for (size_t i = n_args; i < n_pos_args; i++) { code_state->state[n_state - 1 - i] = self->extra_args[i - (n_pos_args - n_def_pos_args)]; } } else { fun_pos_args_mismatch(self, n_pos_args - n_def_pos_args, n_args); } } } // copy positional args into state for (size_t i = 0; i < n_args; i++) { code_state->state[n_state - 1 - i] = args[i]; } // check keyword arguments if (n_kw != 0 || (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { DEBUG_printf("Initial args: "); dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); mp_obj_t dict = MP_OBJ_NULL; if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { dict = mp_obj_new_dict(n_kw); // TODO: better go conservative with 0? *var_pos_kw_args = dict; } for (size_t i = 0; i < n_kw; i++) { // the keys in kwargs are expected to be qstr objects mp_obj_t wanted_arg_name = kwargs[2 * i]; // get pointer to arg_names array const uint8_t *arg_names = code_state->ip; arg_names = mp_decode_uint_skip(arg_names); for (size_t j = 0; j < n_pos_args + n_kwonly_args; j++) { qstr arg_qstr = mp_decode_uint(&arg_names); #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE arg_qstr = self->context->constants.qstr_table[arg_qstr]; #endif if (wanted_arg_name == MP_OBJ_NEW_QSTR(arg_qstr)) { if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function got multiple values for argument '%q'"), MP_OBJ_QSTR_VALUE(wanted_arg_name)); } code_state->state[n_state - 1 - j] = kwargs[2 * i + 1]; goto continue2; } } // Didn't find name match with positional args if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) == 0) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("unexpected keyword argument")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unexpected keyword argument '%q'"), MP_OBJ_QSTR_VALUE(wanted_arg_name)); #endif } mp_obj_dict_store(dict, kwargs[2 * i], kwargs[2 * i + 1]); continue2:; } DEBUG_printf("Args with kws flattened: "); dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); // fill in defaults for positional args mp_obj_t *d = &code_state->state[n_state - n_pos_args]; mp_obj_t *s = &self->extra_args[n_def_pos_args - 1]; for (size_t i = n_def_pos_args; i > 0; i--, d++, s--) { if (*d == MP_OBJ_NULL) { *d = *s; } } DEBUG_printf("Args after filling default positional: "); dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); // Check that all mandatory positional args are specified while (d < &code_state->state[n_state]) { if (*d++ == MP_OBJ_NULL) { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function missing required positional argument #%d"), &code_state->state[n_state] - d); } } // Check that all mandatory keyword args are specified // Fill in default kw args if we have them const uint8_t *arg_names = mp_decode_uint_skip(code_state->ip); for (size_t i = 0; i < n_pos_args; i++) { arg_names = mp_decode_uint_skip(arg_names); } for (size_t i = 0; i < n_kwonly_args; i++) { qstr arg_qstr = mp_decode_uint(&arg_names); #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE arg_qstr = self->context->constants.qstr_table[arg_qstr]; #endif if (code_state->state[n_state - 1 - n_pos_args - i] == MP_OBJ_NULL) { mp_map_elem_t *elem = NULL; if ((scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { elem = mp_map_lookup(&((mp_obj_dict_t *)MP_OBJ_TO_PTR(self->extra_args[n_def_pos_args]))->map, MP_OBJ_NEW_QSTR(arg_qstr), MP_MAP_LOOKUP); } if (elem != NULL) { code_state->state[n_state - 1 - n_pos_args - i] = elem->value; } else { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function missing required keyword argument '%q'"), arg_qstr); } } } } else { // no keyword arguments given if (n_kwonly_args != 0) { mp_raise_TypeError(MP_ERROR_TEXT("function missing keyword-only argument")); } if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { *var_pos_kw_args = mp_obj_new_dict(0); } } // jump over code info (source file, argument names and line-number mapping) const uint8_t *ip = code_state->ip + n_info; // bytecode prelude: initialise closed over variables for (; n_cell; --n_cell) { size_t local_num = *ip++; code_state->state[n_state - 1 - local_num] = mp_obj_new_cell(code_state->state[n_state - 1 - local_num]); } // now that we skipped over the prelude, set the ip for the VM code_state->ip = ip; DEBUG_printf("Calling: n_pos_args=%d, n_kwonly_args=%d\n", n_pos_args, n_kwonly_args); dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); dump_args(code_state->state, n_state); } #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE // The following table encodes the number of bytes that a specific opcode // takes up. Some opcodes have an extra byte, defined by MP_BC_MASK_EXTRA_BYTE. uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) { uint f = MP_BC_FORMAT(*ip); const byte *ip_start = ip; if (f == MP_BC_FORMAT_QSTR) { ip += 3; } else { int extra_byte = (*ip & MP_BC_MASK_EXTRA_BYTE) == 0; ip += 1; if (f == MP_BC_FORMAT_VAR_UINT) { if (count_var_uint) { while ((*ip++ & 0x80) != 0) { } } } else if (f == MP_BC_FORMAT_OFFSET) { ip += 2; } ip += extra_byte; } *opcode_size = ip - ip_start; return f; } #endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE #ifndef __IPU__ #include #endif #include #include "py/bc0.h" #include "py/emitglue.h" #if MICROPY_DEBUG_PRINTERS #define DECODE_UINT { \ unum = 0; \ do { \ unum = (unum << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0); \ } #define DECODE_ULABEL do { unum = (ip[0] | (ip[1] << 8)); ip += 2; } while (0) #define DECODE_SLABEL do { unum = (ip[0] | (ip[1] << 8)) - 0x8000; ip += 2; } while (0) #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE #define DECODE_QSTR \ DECODE_UINT; \ qst = qstr_table[unum] #else #define DECODE_QSTR \ DECODE_UINT; \ qst = unum; #endif #define DECODE_PTR \ DECODE_UINT; \ unum = (mp_uint_t)(uintptr_t)child_table[unum] #define DECODE_OBJ \ DECODE_UINT; \ unum = (mp_uint_t)obj_table[unum] void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, const mp_module_constants_t *cm) { const byte *ip_start = rc->fun_data; const byte *ip = rc->fun_data; // Decode prelude MP_BC_PRELUDE_SIG_DECODE(ip); MP_BC_PRELUDE_SIZE_DECODE(ip); const byte *code_info = ip; qstr block_name = mp_decode_uint(&code_info); #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE block_name = cm->qstr_table[block_name]; qstr source_file = cm->qstr_table[0]; #else qstr source_file = cm->source_file; #endif mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p %u bytes)\n", qstr_str(source_file), qstr_str(block_name), rc, ip_start, (unsigned)rc->fun_data_len); // raw bytecode dump size_t prelude_size = ip - ip_start + n_info + n_cell; mp_printf(print, "Raw bytecode (code_info_size=%u, bytecode_size=%u):\n", (unsigned)prelude_size, (unsigned)(rc->fun_data_len - prelude_size)); for (size_t i = 0; i < rc->fun_data_len; i++) { if (i > 0 && i % 16 == 0) { mp_printf(print, "\n"); } mp_printf(print, " %02x", ip_start[i]); } mp_printf(print, "\n"); // bytecode prelude: arg names (as qstr objects) mp_printf(print, "arg names:"); for (mp_uint_t i = 0; i < n_pos_args + n_kwonly_args; i++) { qstr qst = mp_decode_uint(&code_info); #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE qst = cm->qstr_table[qst]; #endif mp_printf(print, " %s", qstr_str(qst)); } mp_printf(print, "\n"); mp_printf(print, "(N_STATE %u)\n", (unsigned)n_state); mp_printf(print, "(N_EXC_STACK %u)\n", (unsigned)n_exc_stack); // skip over code_info ip += n_info; const byte *line_info_top = ip; // bytecode prelude: initialise closed over variables for (size_t i = 0; i < n_cell; ++i) { uint local_num = *ip++; mp_printf(print, "(INIT_CELL %u)\n", local_num); } // print out line number info { mp_int_t bc = 0; mp_uint_t source_line = 1; mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); for (const byte *ci = code_info; ci < line_info_top;) { if ((ci[0] & 0x80) == 0) { // 0b0LLBBBBB encoding bc += ci[0] & 0x1f; source_line += ci[0] >> 5; ci += 1; } else { // 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) bc += ci[0] & 0xf; source_line += ((ci[0] << 4) & 0x700) | ci[1]; ci += 2; } mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); } } mp_bytecode_print2(print, ip, rc->fun_data_len - prelude_size, rc->children, cm); } const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, const byte *ip, mp_raw_code_t *const *child_table, const mp_module_constants_t *cm) { #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE const qstr_short_t *qstr_table = cm->qstr_table; #endif const mp_obj_t *obj_table = cm->obj_table; mp_uint_t unum; qstr qst; switch (*ip++) { case MP_BC_LOAD_CONST_FALSE: mp_printf(print, "LOAD_CONST_FALSE"); break; case MP_BC_LOAD_CONST_NONE: mp_printf(print, "LOAD_CONST_NONE"); break; case MP_BC_LOAD_CONST_TRUE: mp_printf(print, "LOAD_CONST_TRUE"); break; case MP_BC_LOAD_CONST_SMALL_INT: { mp_int_t num = 0; if ((ip[0] & 0x40) != 0) { // Number is negative num--; } do { num = ((mp_uint_t)num << 7) | (*ip & 0x7f); } while ((*ip++ & 0x80) != 0); mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, num); break; } case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; mp_printf(print, "LOAD_CONST_STRING '%s'", qstr_str(qst)); break; case MP_BC_LOAD_CONST_OBJ: DECODE_OBJ; mp_printf(print, "LOAD_CONST_OBJ %p=", MP_OBJ_TO_PTR(unum)); mp_obj_print_helper(print, (mp_obj_t)unum, PRINT_REPR); break; case MP_BC_LOAD_NULL: mp_printf(print, "LOAD_NULL"); break; case MP_BC_LOAD_FAST_N: DECODE_UINT; mp_printf(print, "LOAD_FAST_N " UINT_FMT, unum); break; case MP_BC_LOAD_DEREF: DECODE_UINT; mp_printf(print, "LOAD_DEREF " UINT_FMT, unum); break; case MP_BC_LOAD_NAME: DECODE_QSTR; mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); break; case MP_BC_LOAD_ATTR: DECODE_QSTR; mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); break; case MP_BC_LOAD_METHOD: DECODE_QSTR; mp_printf(print, "LOAD_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; mp_printf(print, "LOAD_SUPER_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_BUILD_CLASS: mp_printf(print, "LOAD_BUILD_CLASS"); break; case MP_BC_LOAD_SUBSCR: mp_printf(print, "LOAD_SUBSCR"); break; case MP_BC_STORE_FAST_N: DECODE_UINT; mp_printf(print, "STORE_FAST_N " UINT_FMT, unum); break; case MP_BC_STORE_DEREF: DECODE_UINT; mp_printf(print, "STORE_DEREF " UINT_FMT, unum); break; case MP_BC_STORE_NAME: DECODE_QSTR; mp_printf(print, "STORE_NAME %s", qstr_str(qst)); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; mp_printf(print, "STORE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_STORE_ATTR: DECODE_QSTR; mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); break; case MP_BC_STORE_SUBSCR: mp_printf(print, "STORE_SUBSCR"); break; case MP_BC_DELETE_FAST: DECODE_UINT; mp_printf(print, "DELETE_FAST " UINT_FMT, unum); break; case MP_BC_DELETE_DEREF: DECODE_UINT; mp_printf(print, "DELETE_DEREF " UINT_FMT, unum); break; case MP_BC_DELETE_NAME: DECODE_QSTR; mp_printf(print, "DELETE_NAME %s", qstr_str(qst)); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; mp_printf(print, "DELETE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_DUP_TOP: mp_printf(print, "DUP_TOP"); break; case MP_BC_DUP_TOP_TWO: mp_printf(print, "DUP_TOP_TWO"); break; case MP_BC_POP_TOP: mp_printf(print, "POP_TOP"); break; case MP_BC_ROT_TWO: mp_printf(print, "ROT_TWO"); break; case MP_BC_ROT_THREE: mp_printf(print, "ROT_THREE"); break; case MP_BC_JUMP: DECODE_SLABEL; mp_printf(print, "JUMP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_JUMP_IF_TRUE: DECODE_SLABEL; mp_printf(print, "POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_JUMP_IF_FALSE: DECODE_SLABEL; mp_printf(print, "POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_JUMP_IF_TRUE_OR_POP: DECODE_SLABEL; mp_printf(print, "JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_JUMP_IF_FALSE_OR_POP: DECODE_SLABEL; mp_printf(print, "JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_SETUP_WITH: DECODE_ULABEL; // loop-like labels are always forward mp_printf(print, "SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_WITH_CLEANUP: mp_printf(print, "WITH_CLEANUP"); break; case MP_BC_UNWIND_JUMP: DECODE_SLABEL; mp_printf(print, "UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - ip_start), *ip); ip += 1; break; case MP_BC_SETUP_EXCEPT: DECODE_ULABEL; // except labels are always forward mp_printf(print, "SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_SETUP_FINALLY: DECODE_ULABEL; // except labels are always forward mp_printf(print, "SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_END_FINALLY: // if TOS is an exception, reraises the exception (3 values on TOS) // if TOS is an integer, does something else // if TOS is None, just pops it and continues // else error mp_printf(print, "END_FINALLY"); break; case MP_BC_GET_ITER: mp_printf(print, "GET_ITER"); break; case MP_BC_GET_ITER_STACK: mp_printf(print, "GET_ITER_STACK"); break; case MP_BC_FOR_ITER: DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward mp_printf(print, "FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_EXCEPT_JUMP: DECODE_ULABEL; // these labels are always forward mp_printf(print, "POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_BUILD_TUPLE: DECODE_UINT; mp_printf(print, "BUILD_TUPLE " UINT_FMT, unum); break; case MP_BC_BUILD_LIST: DECODE_UINT; mp_printf(print, "BUILD_LIST " UINT_FMT, unum); break; case MP_BC_BUILD_MAP: DECODE_UINT; mp_printf(print, "BUILD_MAP " UINT_FMT, unum); break; case MP_BC_STORE_MAP: mp_printf(print, "STORE_MAP"); break; case MP_BC_BUILD_SET: DECODE_UINT; mp_printf(print, "BUILD_SET " UINT_FMT, unum); break; #if MICROPY_PY_BUILTINS_SLICE case MP_BC_BUILD_SLICE: DECODE_UINT; mp_printf(print, "BUILD_SLICE " UINT_FMT, unum); break; #endif case MP_BC_STORE_COMP: DECODE_UINT; mp_printf(print, "STORE_COMP " UINT_FMT, unum); break; case MP_BC_UNPACK_SEQUENCE: DECODE_UINT; mp_printf(print, "UNPACK_SEQUENCE " UINT_FMT, unum); break; case MP_BC_UNPACK_EX: DECODE_UINT; mp_printf(print, "UNPACK_EX " UINT_FMT, unum); break; case MP_BC_MAKE_FUNCTION: DECODE_PTR; mp_printf(print, "MAKE_FUNCTION %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_FUNCTION_DEFARGS: DECODE_PTR; mp_printf(print, "MAKE_FUNCTION_DEFARGS %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_CLOSURE: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; mp_printf(print, "MAKE_CLOSURE %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_MAKE_CLOSURE_DEFARGS: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; mp_printf(print, "MAKE_CLOSURE_DEFARGS %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_CALL_FUNCTION: DECODE_UINT; mp_printf(print, "CALL_FUNCTION n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_FUNCTION_VAR_KW: DECODE_UINT; mp_printf(print, "CALL_FUNCTION_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD: DECODE_UINT; mp_printf(print, "CALL_METHOD n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD_VAR_KW: DECODE_UINT; mp_printf(print, "CALL_METHOD_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_RETURN_VALUE: mp_printf(print, "RETURN_VALUE"); break; case MP_BC_RAISE_LAST: mp_printf(print, "RAISE_LAST"); break; case MP_BC_RAISE_OBJ: mp_printf(print, "RAISE_OBJ"); break; case MP_BC_RAISE_FROM: mp_printf(print, "RAISE_FROM"); break; case MP_BC_YIELD_VALUE: mp_printf(print, "YIELD_VALUE"); break; case MP_BC_YIELD_FROM: mp_printf(print, "YIELD_FROM"); break; case MP_BC_IMPORT_NAME: DECODE_QSTR; mp_printf(print, "IMPORT_NAME '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; mp_printf(print, "IMPORT_FROM '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_STAR: mp_printf(print, "IMPORT_STAR"); break; default: if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16); } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { mp_printf(print, "LOAD_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI); } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_UNARY_OP_MULTI; mp_printf(print, "UNARY_OP " UINT_FMT " %s", op, qstr_str(mp_unary_op_method_name[op])); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); } else { mp_printf(print, "code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); return ip; } break; } return ip; } void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, mp_raw_code_t *const *child_table, const mp_module_constants_t *cm) { const byte *ip_start = ip; while (ip < ip_start + len) { mp_printf(print, "%02u ", (uint)(ip - ip_start)); ip = mp_bytecode_print_str(print, ip_start, ip, child_table, cm); mp_printf(print, "\n"); } } #endif // MICROPY_DEBUG_PRINTERS #ifdef __IPU__ #include #endif #include #include "py/obj.h" #include "py/objmodule.h" #include "py/runtime.h" #include "py/builtin.h" #include "py/repl.h" #if MICROPY_HELPER_REPL #if MICROPY_PY_SYS_PS1_PS2 const char *mp_repl_get_psx(unsigned int entry) { if (mp_obj_is_str(MP_STATE_VM(sys_mutable)[entry])) { return mp_obj_str_get_str(MP_STATE_VM(sys_mutable)[entry]); } else { return ""; } } #endif STATIC bool str_startswith_word(const char *str, const char *head) { size_t i; for (i = 0; str[i] && head[i]; i++) { if (str[i] != head[i]) { return false; } } return head[i] == '\0' && (str[i] == '\0' || !unichar_isident(str[i])); } bool mp_repl_continue_with_input(const char *input) { // check for blank input if (input[0] == '\0') { return false; } // check if input starts with a certain keyword bool starts_with_compound_keyword = input[0] == '@' || str_startswith_word(input, "if") || str_startswith_word(input, "while") || str_startswith_word(input, "for") || str_startswith_word(input, "try") || str_startswith_word(input, "with") || str_startswith_word(input, "def") || str_startswith_word(input, "class") #if MICROPY_PY_ASYNC_AWAIT || str_startswith_word(input, "async") #endif ; // check for unmatched open bracket, quote or escape quote #define Q_NONE (0) #define Q_1_SINGLE (1) #define Q_1_DOUBLE (2) #define Q_3_SINGLE (3) #define Q_3_DOUBLE (4) int n_paren = 0; int n_brack = 0; int n_brace = 0; int in_quote = Q_NONE; const char *i; for (i = input; *i; i++) { if (*i == '\'') { if ((in_quote == Q_NONE || in_quote == Q_3_SINGLE) && i[1] == '\'' && i[2] == '\'') { i += 2; in_quote = Q_3_SINGLE - in_quote; } else if (in_quote == Q_NONE || in_quote == Q_1_SINGLE) { in_quote = Q_1_SINGLE - in_quote; } } else if (*i == '"') { if ((in_quote == Q_NONE || in_quote == Q_3_DOUBLE) && i[1] == '"' && i[2] == '"') { i += 2; in_quote = Q_3_DOUBLE - in_quote; } else if (in_quote == Q_NONE || in_quote == Q_1_DOUBLE) { in_quote = Q_1_DOUBLE - in_quote; } } else if (*i == '\\' && (i[1] == '\'' || i[1] == '"' || i[1] == '\\')) { if (in_quote != Q_NONE) { i++; } } else if (in_quote == Q_NONE) { switch (*i) { case '(': n_paren += 1; break; case ')': n_paren -= 1; break; case '[': n_brack += 1; break; case ']': n_brack -= 1; break; case '{': n_brace += 1; break; case '}': n_brace -= 1; break; default: break; } } } // continue if unmatched 3-quotes if (in_quote == Q_3_SINGLE || in_quote == Q_3_DOUBLE) { return true; } // continue if unmatched brackets, but only if not in a 1-quote if ((n_paren > 0 || n_brack > 0 || n_brace > 0) && in_quote == Q_NONE) { return true; } // continue if last character was backslash (for line continuation) if (i[-1] == '\\') { return true; } // continue if compound keyword and last line was not empty if (starts_with_compound_keyword && i[-1] != '\n') { return true; } // otherwise, don't continue return false; } STATIC bool test_qstr(mp_obj_t obj, qstr name) { if (obj) { // try object member mp_obj_t dest[2]; mp_load_method_protected(obj, name, dest, true); return dest[0] != MP_OBJ_NULL; } else { // try builtin module return mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP); } } STATIC const char *find_completions(const char *s_start, size_t s_len, mp_obj_t obj, size_t *match_len, qstr *q_first, qstr *q_last) { const char *match_str = NULL; *match_len = 0; *q_first = *q_last = 0; size_t nqstr = QSTR_TOTAL(); for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) { size_t d_len; const char *d_str = (const char *)qstr_data(q, &d_len); // special case; filter out words that begin with underscore // unless there's already a partial match if (s_len == 0 && d_str[0] == '_') { continue; } if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) { if (test_qstr(obj, q)) { if (match_str == NULL) { match_str = d_str; *match_len = d_len; } else { // search for longest common prefix of match_str and d_str // (assumes these strings are null-terminated) for (size_t j = s_len; j <= *match_len && j <= d_len; ++j) { if (match_str[j] != d_str[j]) { *match_len = j; break; } } } if (*q_first == 0) { *q_first = q; } *q_last = q; } } } return match_str; } STATIC void print_completions(const mp_print_t *print, const char *s_start, size_t s_len, mp_obj_t obj, qstr q_first, qstr q_last) { #define WORD_SLOT_LEN (16) #define MAX_LINE_LEN (4 * WORD_SLOT_LEN) int line_len = MAX_LINE_LEN; // force a newline for first word for (qstr q = q_first; q <= q_last; ++q) { size_t d_len; const char *d_str = (const char *)qstr_data(q, &d_len); if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) { if (test_qstr(obj, q)) { int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len; if (gap < 2) { gap += WORD_SLOT_LEN; } if (line_len + gap + d_len <= MAX_LINE_LEN) { // TODO optimise printing of gap? for (int j = 0; j < gap; ++j) { mp_print_str(print, " "); } mp_print_str(print, d_str); line_len += gap + d_len; } else { mp_printf(print, "\n%s", d_str); line_len = d_len; } } } } mp_print_str(print, "\n"); } size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str) { // scan backwards to find start of "a.b.c" chain const char *org_str = str; const char *top = str + len; for (const char *s = top; --s >= str;) { if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) { ++s; str = s; break; } } // begin search in outer global dict which is accessed from __main__ mp_obj_t obj = MP_OBJ_FROM_PTR(&mp_module___main__); mp_obj_t dest[2]; const char *s_start; size_t s_len; for (;;) { // get next word in string to complete s_start = str; while (str < top && *str != '.') { ++str; } s_len = str - s_start; if (str == top) { // end of string, do completion on this partial name break; } // a complete word, lookup in current object qstr q = qstr_find_strn(s_start, s_len); if (q == MP_QSTRnull) { // lookup will fail return 0; } mp_load_method_protected(obj, q, dest, true); obj = dest[0]; // attribute, method, or MP_OBJ_NULL if nothing found if (obj == MP_OBJ_NULL) { // lookup failed return 0; } // skip '.' to move to next word ++str; } // after "import", suggest built-in modules static const char import_str[] = "import "; if (len >= 7 && !memcmp(org_str, import_str, 7)) { obj = MP_OBJ_NULL; } // look for matches size_t match_len; qstr q_first, q_last; const char *match_str = find_completions(s_start, s_len, obj, &match_len, &q_first, &q_last); // nothing found if (q_first == 0) { // If there're no better alternatives, and if it's first word // in the line, try to complete "import". if (s_start == org_str && s_len > 0 && s_len < sizeof(import_str) - 1) { if (memcmp(s_start, import_str, s_len) == 0) { *compl_str = import_str + s_len; return sizeof(import_str) - 1 - s_len; } } if (q_first == 0) { *compl_str = " "; return s_len ? 0 : 4; } } // 1 match found, or multiple matches with a common prefix if (q_first == q_last || match_len > s_len) { *compl_str = match_str + s_len; return match_len - s_len; } // multiple matches found, print them out print_completions(print, s_start, s_len, obj, q_first, q_last); return (size_t)(-1); // indicate many matches } #endif // MICROPY_HELPER_REPL #include "py/smallint.h" bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y) { // Check for multiply overflow; see CERT INT32-C if (x > 0) { // x is positive if (y > 0) { // x and y are positive if (x > (MP_SMALL_INT_MAX / y)) { return true; } } else { // x positive, y nonpositive if (y < (MP_SMALL_INT_MIN / x)) { return true; } } // x positive, y nonpositive } else { // x is nonpositive if (y > 0) { // x is nonpositive, y is positive if (x < (MP_SMALL_INT_MIN / y)) { return true; } } else { // x and y are nonpositive if (x != 0 && y < (MP_SMALL_INT_MAX / x)) { return true; } } // End if x and y are nonpositive } // End if x is nonpositive return false; } mp_int_t mp_small_int_modulo(mp_int_t dividend, mp_int_t divisor) { // Python specs require that mod has same sign as second operand dividend %= divisor; if ((dividend < 0 && divisor > 0) || (dividend > 0 && divisor < 0)) { dividend += divisor; } return dividend; } mp_int_t mp_small_int_floor_divide(mp_int_t num, mp_int_t denom) { if (num >= 0) { if (denom < 0) { num += -denom - 1; } } else { if (denom >= 0) { num += -denom + 1; } } return num / denom; } /* * Copyright (c) 2015 Paul Sokolovsky * Copyright (c) 2016 Damien P. George * Copyright (c) 2021 Jim Mussared */ #include #include #include "py/lexer.h" #include "py/frozenmod.h" #if MICROPY_MODULE_FROZEN // Null-separated frozen file names. All string-type entries are listed first, // followed by mpy-type entries. Use mp_frozen_str_sizes to determine how // many string entries. extern const char mp_frozen_names[]; #if MICROPY_MODULE_FROZEN_STR #ifndef MICROPY_MODULE_FROZEN_LEXER #define MICROPY_MODULE_FROZEN_LEXER mp_lexer_new_from_str_len #else mp_lexer_t *MICROPY_MODULE_FROZEN_LEXER(qstr src_name, const char *str, mp_uint_t len, mp_uint_t free_len); #endif // Size in bytes of each string entry, followed by a zero (terminator). extern const uint32_t mp_frozen_str_sizes[]; // Null-separated string content. extern const char mp_frozen_str_content[]; #endif // MICROPY_MODULE_FROZEN_STR #if MICROPY_MODULE_FROZEN_MPY #include "py/emitglue.h" extern const mp_frozen_module_t *const mp_frozen_mpy_content[]; #endif // MICROPY_MODULE_FROZEN_MPY // Search for "str" as a frozen entry, returning the stat result // (no-exist/file/dir), as well as the type (none/str/mpy) and data. // frozen_type can be NULL if its value isn't needed (and then data is assumed to be NULL). mp_import_stat_t mp_find_frozen_module(const char *str, int *frozen_type, void **data) { size_t len = strlen(str); const char *name = mp_frozen_names; if (frozen_type != NULL) { *frozen_type = MP_FROZEN_NONE; } // Count the number of str lengths we have to find how many str entries. size_t num_str = 0; #if MICROPY_MODULE_FROZEN_STR && MICROPY_MODULE_FROZEN_MPY for (const uint32_t *s = mp_frozen_str_sizes; *s != 0; ++s) { ++num_str; } #endif for (size_t i = 0; *name != 0; i++) { size_t entry_len = strlen(name); if (entry_len >= len && memcmp(str, name, len) == 0) { // Query is a prefix of the current entry. if (entry_len == len) { // Exact match --> file. if (frozen_type != NULL) { #if MICROPY_MODULE_FROZEN_STR if (i < num_str) { *frozen_type = MP_FROZEN_STR; // Use the size table to figure out where this index starts. size_t offset = 0; for (size_t j = 0; j < i; ++j) { offset += mp_frozen_str_sizes[j] + 1; } size_t content_len = mp_frozen_str_sizes[i]; const char *content = &mp_frozen_str_content[offset]; // Note: str & len have been updated by find_frozen_entry to strip // the ".frozen/" prefix (to avoid this being a distinct qstr to // the original path QSTR in frozen_content.c). qstr source = qstr_from_strn(str, len); mp_lexer_t *lex = MICROPY_MODULE_FROZEN_LEXER(source, content, content_len, 0); *data = lex; } #endif #if MICROPY_MODULE_FROZEN_MPY if (i >= num_str) { *frozen_type = MP_FROZEN_MPY; // Load the corresponding index as a raw_code, taking // into account any string entries to offset by. *data = (void *)mp_frozen_mpy_content[i - num_str]; } #endif } return MP_IMPORT_STAT_FILE; } else if (name[len] == '/') { // Matches up to directory separator, this is a valid // directory path. return MP_IMPORT_STAT_DIR; } } // Skip null separator. name += entry_len + 1; } return MP_IMPORT_STAT_NO_EXIST; } #endif // MICROPY_MODULE_FROZEN // ------------------------------------------ EXT modules --------------------------------------------- // // Some dodgy stuff I'm adding to make the lfs library happy int printf(const char *format, ...){ return 0; } #define PRIu32 "u" #define PRIx32 "x" #define PRIu16 "u" #define PRIx16 "x" #define LFS2_NO_MALLOC 1 #include "lib/littlefs/lfs2.h" #include "lib/littlefs/lfs2_util.h" // // ----------------------- EXT uos module --------------------------- // #include "py/objstr.h" #include "py/runtime.h" #if MICROPY_PY_UOS #include "extmod/misc.h" #include "extmod/vfs.h" #if MICROPY_VFS_FAT #include "extmod/vfs_fat.h" #endif #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 #include "extmod/vfs_lfs.h" #endif #if MICROPY_VFS_POSIX #include "extmod/vfs_posix.h" #endif #if MICROPY_PY_UOS_UNAME #include "genhdr/mpversion.h" #endif #ifdef MICROPY_PY_UOS_INCLUDEFILE #include MICROPY_PY_UOS_INCLUDEFILE #endif #ifdef MICROPY_BUILD_TYPE #define MICROPY_BUILD_TYPE_PAREN " (" MICROPY_BUILD_TYPE ")" #else #define MICROPY_BUILD_TYPE_PAREN #endif #if MICROPY_PY_UOS_UNAME #if MICROPY_PY_UOS_UNAME_RELEASE_DYNAMIC #define CONST_RELEASE #else #define CONST_RELEASE const #endif STATIC const qstr mp_uos_uname_info_fields[] = { MP_QSTR_sysname, MP_QSTR_nodename, MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine }; STATIC const MP_DEFINE_STR_OBJ(mp_uos_uname_info_sysname_obj, MICROPY_PY_SYS_PLATFORM); STATIC const MP_DEFINE_STR_OBJ(mp_uos_uname_info_nodename_obj, MICROPY_PY_SYS_PLATFORM); STATIC CONST_RELEASE MP_DEFINE_STR_OBJ(mp_uos_uname_info_release_obj, MICROPY_VERSION_STRING); STATIC const MP_DEFINE_STR_OBJ(mp_uos_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE MICROPY_BUILD_TYPE_PAREN); STATIC const MP_DEFINE_STR_OBJ(mp_uos_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); STATIC MP_DEFINE_ATTRTUPLE( mp_uos_uname_info_obj, mp_uos_uname_info_fields, 5, MP_ROM_PTR(&mp_uos_uname_info_sysname_obj), MP_ROM_PTR(&mp_uos_uname_info_nodename_obj), MP_ROM_PTR(&mp_uos_uname_info_release_obj), MP_ROM_PTR(&mp_uos_uname_info_version_obj), MP_ROM_PTR(&mp_uos_uname_info_machine_obj) ); STATIC mp_obj_t mp_uos_uname(void) { #if MICROPY_PY_UOS_UNAME_RELEASE_DYNAMIC const char *release = mp_uos_uname_release(); mp_uos_uname_info_release_obj.len = strlen(release); mp_uos_uname_info_release_obj.data = (const byte *)release; #endif return MP_OBJ_FROM_PTR(&mp_uos_uname_info_obj); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_uos_uname_obj, mp_uos_uname); #endif STATIC const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, #if MICROPY_PY_UOS_GETENV_PUTENV_UNSETENV { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mp_uos_getenv_obj) }, { MP_ROM_QSTR(MP_QSTR_putenv), MP_ROM_PTR(&mp_uos_putenv_obj) }, { MP_ROM_QSTR(MP_QSTR_unsetenv), MP_ROM_PTR(&mp_uos_unsetenv_obj) }, #endif #if MICROPY_PY_UOS_SEP { MP_ROM_QSTR(MP_QSTR_sep), MP_ROM_QSTR(MP_QSTR__slash_) }, #endif #if MICROPY_PY_UOS_SYNC { MP_ROM_QSTR(MP_QSTR_sync), MP_ROM_PTR(&mp_uos_sync_obj) }, #endif #if MICROPY_PY_UOS_SYSTEM { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mp_uos_system_obj) }, #endif #if MICROPY_PY_UOS_UNAME { MP_ROM_QSTR(MP_QSTR_uname), MP_ROM_PTR(&mp_uos_uname_obj) }, #endif #if MICROPY_PY_UOS_URANDOM { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&mp_uos_urandom_obj) }, #endif #if MICROPY_VFS { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mp_vfs_rename_obj) }, { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove #endif // The following are MicroPython extensions. #if MICROPY_PY_OS_DUPTERM { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, #endif #if MICROPY_PY_UOS_DUPTERM_NOTIFY { MP_ROM_QSTR(MP_QSTR_dupterm_notify), MP_ROM_PTR(&mp_uos_dupterm_notify_obj) }, #endif #if MICROPY_PY_UOS_ERRNO { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mp_uos_errno_obj) }, #endif #if MICROPY_VFS { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, #if MICROPY_VFS_FAT { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, #endif #if MICROPY_VFS_LFS1 { MP_ROM_QSTR(MP_QSTR_VfsLfs1), MP_ROM_PTR(&mp_type_vfs_lfs1) }, #endif #if MICROPY_VFS_LFS2 { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, #endif #if MICROPY_VFS_POSIX { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, #endif #endif }; STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); const mp_obj_module_t mp_module_uos = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&os_module_globals, }; #endif // MICROPY_PY_UOS // ---------------- EXT VFS module -------------------- // #include #include #include "py/runtime.h" #include "py/objstr.h" #include "py/mperrno.h" #include "extmod/vfs.h" #if MICROPY_VFS #if MICROPY_VFS_FAT #include "extmod/vfs_fat.h" #endif #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 #include "extmod/vfs_lfs.h" #endif #if MICROPY_VFS_POSIX #include "extmod/vfs_posix.h" #endif // For mp_vfs_proxy_call, the maximum number of additional args that can be passed. // A fixed maximum size is used to avoid the need for a costly variable array. #define PROXY_MAX_ARGS (2) // path is the path to lookup and *path_out holds the path within the VFS // object (starts with / if an absolute path). // Returns MP_VFS_ROOT for root dir (and then path_out is undefined) and // MP_VFS_NONE for path not found. mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out) { if (*path == '/' || MP_STATE_VM(vfs_cur) == MP_VFS_ROOT) { // an absolute path, or the current volume is root, so search root dir bool is_abs = 0; if (*path == '/') { ++path; is_abs = 1; } if (*path == '\0') { // path is "" or "/" so return virtual root return MP_VFS_ROOT; } for (mp_vfs_mount_t *vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { size_t len = vfs->len - 1; if (len == 0) { *path_out = path - is_abs; return vfs; } if (strncmp(path, vfs->str + 1, len) == 0) { if (path[len] == '/') { *path_out = path + len; return vfs; } else if (path[len] == '\0') { *path_out = "/"; return vfs; } } } // if we get here then there's nothing mounted on /, so the path doesn't exist return MP_VFS_NONE; } // a relative path within a mounted device *path_out = path; return MP_STATE_VM(vfs_cur); } // Version of mp_vfs_lookup_path that takes and returns uPy string objects. STATIC mp_vfs_mount_t *lookup_path(mp_obj_t path_in, mp_obj_t *path_out) { const char *path = mp_obj_str_get_str(path_in); const char *p_out; mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path, &p_out); if (vfs != MP_VFS_NONE && vfs != MP_VFS_ROOT) { *path_out = mp_obj_new_str_of_type(mp_obj_get_type(path_in), (const byte *)p_out, strlen(p_out)); } return vfs; } STATIC mp_obj_t mp_vfs_proxy_call(mp_vfs_mount_t *vfs, qstr meth_name, size_t n_args, const mp_obj_t *args) { assert(n_args <= PROXY_MAX_ARGS); if (vfs == MP_VFS_NONE) { // mount point not found mp_raise_OSError(MP_ENODEV); } if (vfs == MP_VFS_ROOT) { // can't do operation on root dir mp_raise_OSError(MP_EPERM); } mp_obj_t meth[2 + PROXY_MAX_ARGS]; mp_load_method(vfs->obj, meth_name, meth); if (args != NULL) { memcpy(meth + 2, args, n_args * sizeof(*args)); } return mp_call_method_n_kw(n_args, 0, meth); } mp_import_stat_t mp_vfs_import_stat(const char *path) { const char *path_out; mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path, &path_out); if (vfs == MP_VFS_NONE || vfs == MP_VFS_ROOT) { return MP_IMPORT_STAT_NO_EXIST; } // If the mounted object has the VFS protocol, call its import_stat helper const mp_vfs_proto_t *proto = mp_obj_get_type(vfs->obj)->protocol; if (proto != NULL) { return proto->import_stat(MP_OBJ_TO_PTR(vfs->obj), path_out); } // delegate to vfs.stat() method mp_obj_t path_o = mp_obj_new_str(path_out, strlen(path_out)); mp_obj_t stat; nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { stat = mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_o); nlr_pop(); } else { // assume an exception means that the path is not found return MP_IMPORT_STAT_NO_EXIST; } mp_obj_t *items; mp_obj_get_array_fixed_n(stat, 10, &items); mp_int_t st_mode = mp_obj_get_int(items[0]); if (st_mode & MP_S_IFDIR) { return MP_IMPORT_STAT_DIR; } else { return MP_IMPORT_STAT_FILE; } } STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { // The superblock for littlefs is in both block 0 and 1, but block 0 may be erased // or partially written, so search both blocks 0 and 1 for the littlefs signature. mp_vfs_blockdev_t blockdev; mp_vfs_blockdev_init(&blockdev, bdev_obj); uint8_t buf[44]; for (size_t block_num = 0; block_num <= 1; ++block_num) { mp_vfs_blockdev_read_ext(&blockdev, block_num, 8, sizeof(buf), buf); #if MICROPY_VFS_LFS1 if (memcmp(&buf[32], "littlefs", 8) == 0) { // LFS1 mp_obj_t vfs = mp_type_vfs_lfs1.make_new(&mp_type_vfs_lfs1, 1, 0, &bdev_obj); nlr_pop(); return vfs; } #endif #if MICROPY_VFS_LFS2 if (memcmp(&buf[0], "littlefs", 8) == 0) { // LFS2 mp_obj_t vfs = mp_type_vfs_lfs2.make_new(&mp_type_vfs_lfs2, 1, 0, &bdev_obj); nlr_pop(); return vfs; } #endif } nlr_pop(); } else { // Ignore exception (eg block device doesn't support extended readblocks) } #endif #if MICROPY_VFS_FAT return mp_fat_vfs_type.make_new(&mp_fat_vfs_type, 1, 0, &bdev_obj); #endif // no filesystem found mp_raise_OSError(MP_ENODEV); } mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_readonly, ARG_mkfs }; static const mp_arg_t allowed_args[] = { { MP_QSTR_readonly, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_FALSE} }, { MP_QSTR_mkfs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_FALSE} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // get the mount point size_t mnt_len; const char *mnt_str = mp_obj_str_get_data(pos_args[1], &mnt_len); // see if we need to auto-detect and create the filesystem mp_obj_t vfs_obj = pos_args[0]; mp_obj_t dest[2]; mp_load_method_maybe(vfs_obj, MP_QSTR_mount, dest); if (dest[0] == MP_OBJ_NULL) { // Input object has no mount method, assume it's a block device and try to // auto-detect the filesystem and create the corresponding VFS entity. vfs_obj = mp_vfs_autodetect(vfs_obj); } // create new object mp_vfs_mount_t *vfs = m_new_obj(mp_vfs_mount_t); vfs->str = mnt_str; vfs->len = mnt_len; vfs->obj = vfs_obj; vfs->next = NULL; // call the underlying object to do any mounting operation mp_vfs_proxy_call(vfs, MP_QSTR_mount, 2, (mp_obj_t *)&args); // check that the destination mount point is unused const char *path_out; mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(mp_obj_str_get_str(pos_args[1]), &path_out); if (existing_mount != MP_VFS_NONE && existing_mount != MP_VFS_ROOT) { if (vfs->len != 1 && existing_mount->len == 1) { // if root dir is mounted, still allow to mount something within a subdir of root } else { // mount point in use mp_raise_OSError(MP_EPERM); } } // insert the vfs into the mount table mp_vfs_mount_t **vfsp = &MP_STATE_VM(vfs_mount_table); while (*vfsp != NULL) { if ((*vfsp)->len == 1) { // make sure anything mounted at the root stays at the end of the list vfs->next = *vfsp; break; } vfsp = &(*vfsp)->next; } *vfsp = vfs; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_KW(mp_vfs_mount_obj, 2, mp_vfs_mount); mp_obj_t mp_vfs_umount(mp_obj_t mnt_in) { // remove vfs from the mount table mp_vfs_mount_t *vfs = NULL; size_t mnt_len; const char *mnt_str = NULL; if (mp_obj_is_str(mnt_in)) { mnt_str = mp_obj_str_get_data(mnt_in, &mnt_len); } for (mp_vfs_mount_t **vfsp = &MP_STATE_VM(vfs_mount_table); *vfsp != NULL; vfsp = &(*vfsp)->next) { if ((mnt_str != NULL && !memcmp(mnt_str, (*vfsp)->str, mnt_len + 1)) || (*vfsp)->obj == mnt_in) { vfs = *vfsp; *vfsp = (*vfsp)->next; break; } } if (vfs == NULL) { mp_raise_OSError(MP_EINVAL); } // if we unmounted the current device then set current to root if (MP_STATE_VM(vfs_cur) == vfs) { MP_STATE_VM(vfs_cur) = MP_VFS_ROOT; } // call the underlying object to do any unmounting operation mp_vfs_proxy_call(vfs, MP_QSTR_umount, 0, NULL); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_umount_obj, mp_vfs_umount); // Note: buffering and encoding args are currently ignored mp_obj_t mp_vfs_open(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_file, ARG_mode, ARG_encoding }; static const mp_arg_t allowed_args[] = { { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_mode, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_r)} }, { MP_QSTR_buffering, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); #if MICROPY_VFS_POSIX // If the file is an integer then delegate straight to the POSIX handler if (mp_obj_is_small_int(args[ARG_file].u_obj)) { return mp_vfs_posix_file_open(&mp_type_textio, args[ARG_file].u_obj, args[ARG_mode].u_obj); } #endif mp_vfs_mount_t *vfs = lookup_path(args[ARG_file].u_obj, &args[ARG_file].u_obj); return mp_vfs_proxy_call(vfs, MP_QSTR_open, 2, (mp_obj_t *)&args); } MP_DEFINE_CONST_FUN_OBJ_KW(mp_vfs_open_obj, 0, mp_vfs_open); mp_obj_t mp_vfs_chdir(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); if (vfs == MP_VFS_ROOT) { // If we change to the root dir and a VFS is mounted at the root then // we must change that VFS's current dir to the root dir so that any // subsequent relative paths begin at the root of that VFS. for (vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { if (vfs->len == 1) { mp_obj_t root = MP_OBJ_NEW_QSTR(MP_QSTR__slash_); mp_vfs_proxy_call(vfs, MP_QSTR_chdir, 1, &root); break; } } vfs = MP_VFS_ROOT; } else { mp_vfs_proxy_call(vfs, MP_QSTR_chdir, 1, &path_out); } MP_STATE_VM(vfs_cur) = vfs; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_chdir_obj, mp_vfs_chdir); mp_obj_t mp_vfs_getcwd(void) { if (MP_STATE_VM(vfs_cur) == MP_VFS_ROOT) { return MP_OBJ_NEW_QSTR(MP_QSTR__slash_); } mp_obj_t cwd_o = mp_vfs_proxy_call(MP_STATE_VM(vfs_cur), MP_QSTR_getcwd, 0, NULL); if (MP_STATE_VM(vfs_cur)->len == 1) { // don't prepend "/" for vfs mounted at root return cwd_o; } const char *cwd = mp_obj_str_get_str(cwd_o); vstr_t vstr; vstr_init(&vstr, MP_STATE_VM(vfs_cur)->len + strlen(cwd) + 1); vstr_add_strn(&vstr, MP_STATE_VM(vfs_cur)->str, MP_STATE_VM(vfs_cur)->len); if (!(cwd[0] == '/' && cwd[1] == 0)) { vstr_add_str(&vstr, cwd); } return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); } MP_DEFINE_CONST_FUN_OBJ_0(mp_vfs_getcwd_obj, mp_vfs_getcwd); typedef struct _mp_vfs_ilistdir_it_t { mp_obj_base_t base; mp_fun_1_t iternext; union { mp_vfs_mount_t *vfs; mp_obj_t iter; } cur; bool is_str; bool is_iter; } mp_vfs_ilistdir_it_t; STATIC mp_obj_t mp_vfs_ilistdir_it_iternext(mp_obj_t self_in) { mp_vfs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); if (self->is_iter) { // continue delegating to root dir return mp_iternext(self->cur.iter); } else if (self->cur.vfs == NULL) { // finished iterating mount points and no root dir is mounted return MP_OBJ_STOP_ITERATION; } else { // continue iterating mount points mp_vfs_mount_t *vfs = self->cur.vfs; self->cur.vfs = vfs->next; if (vfs->len == 1) { // vfs is mounted at root dir, delegate to it mp_obj_t root = MP_OBJ_NEW_QSTR(MP_QSTR__slash_); self->is_iter = true; self->cur.iter = mp_vfs_proxy_call(vfs, MP_QSTR_ilistdir, 1, &root); return mp_iternext(self->cur.iter); } else { // a mounted directory mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); t->items[0] = mp_obj_new_str_of_type( self->is_str ? &mp_type_str : &mp_type_bytes, (const byte *)vfs->str + 1, vfs->len - 1); t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number return MP_OBJ_FROM_PTR(t); } } } mp_obj_t mp_vfs_ilistdir(size_t n_args, const mp_obj_t *args) { mp_obj_t path_in; if (n_args == 1) { path_in = args[0]; } else { path_in = MP_OBJ_NEW_QSTR(MP_QSTR_); } mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); if (vfs == MP_VFS_ROOT) { // list the root directory mp_vfs_ilistdir_it_t *iter = m_new_obj(mp_vfs_ilistdir_it_t); iter->base.type = &mp_type_polymorph_iter; iter->iternext = mp_vfs_ilistdir_it_iternext; iter->cur.vfs = MP_STATE_VM(vfs_mount_table); iter->is_str = mp_obj_get_type(path_in) == &mp_type_str; iter->is_iter = false; return MP_OBJ_FROM_PTR(iter); } return mp_vfs_proxy_call(vfs, MP_QSTR_ilistdir, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_ilistdir_obj, 0, 1, mp_vfs_ilistdir); mp_obj_t mp_vfs_listdir(size_t n_args, const mp_obj_t *args) { mp_obj_t iter = mp_vfs_ilistdir(n_args, args); mp_obj_t dir_list = mp_obj_new_list(0, NULL); mp_obj_t next; while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { mp_obj_list_append(dir_list, mp_obj_subscr(next, MP_OBJ_NEW_SMALL_INT(0), MP_OBJ_SENTINEL)); } return dir_list; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_listdir_obj, 0, 1, mp_vfs_listdir); mp_obj_t mp_vfs_mkdir(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); if (vfs == MP_VFS_ROOT || (vfs != MP_VFS_NONE && !strcmp(mp_obj_str_get_str(path_out), "/"))) { mp_raise_OSError(MP_EEXIST); } return mp_vfs_proxy_call(vfs, MP_QSTR_mkdir, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_mkdir_obj, mp_vfs_mkdir); mp_obj_t mp_vfs_remove(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); return mp_vfs_proxy_call(vfs, MP_QSTR_remove, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_remove_obj, mp_vfs_remove); mp_obj_t mp_vfs_rename(mp_obj_t old_path_in, mp_obj_t new_path_in) { mp_obj_t args[2]; mp_vfs_mount_t *old_vfs = lookup_path(old_path_in, &args[0]); mp_vfs_mount_t *new_vfs = lookup_path(new_path_in, &args[1]); if (old_vfs != new_vfs) { // can't rename across filesystems mp_raise_OSError(MP_EPERM); } return mp_vfs_proxy_call(old_vfs, MP_QSTR_rename, 2, args); } MP_DEFINE_CONST_FUN_OBJ_2(mp_vfs_rename_obj, mp_vfs_rename); mp_obj_t mp_vfs_rmdir(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); return mp_vfs_proxy_call(vfs, MP_QSTR_rmdir, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_rmdir_obj, mp_vfs_rmdir); mp_obj_t mp_vfs_stat(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); if (vfs == MP_VFS_ROOT) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); // st_mode for (int i = 1; i <= 9; ++i) { t->items[i] = MP_OBJ_NEW_SMALL_INT(0); // dev, nlink, uid, gid, size, atime, mtime, ctime } return MP_OBJ_FROM_PTR(t); } return mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_stat_obj, mp_vfs_stat); mp_obj_t mp_vfs_statvfs(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); if (vfs == MP_VFS_ROOT) { // statvfs called on the root directory, see if there's anything mounted there for (vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { if (vfs->len == 1) { break; } } // If there's nothing mounted at root then return a mostly-empty tuple if (vfs == NULL) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); // fill in: bsize, frsize, blocks, bfree, bavail, files, ffree, favail, flags for (int i = 0; i <= 8; ++i) { t->items[i] = MP_OBJ_NEW_SMALL_INT(0); } // Put something sensible in f_namemax t->items[9] = MP_OBJ_NEW_SMALL_INT(MICROPY_ALLOC_PATH_MAX); return MP_OBJ_FROM_PTR(t); } // VFS mounted at root so delegate the call to it path_out = MP_OBJ_NEW_QSTR(MP_QSTR__slash_); } return mp_vfs_proxy_call(vfs, MP_QSTR_statvfs, 1, &path_out); } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj, mp_vfs_statvfs); // This is a C-level helper function for ports to use if needed. int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point) { nlr_buf_t nlr; mp_int_t ret = -MP_EIO; if (nlr_push(&nlr) == 0) { mp_obj_t args[] = { bdev, mount_point }; mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map); mp_vfs_chdir(mount_point); ret = 0; // success nlr_pop(); } else { mp_obj_base_t *exc = nlr.ret_val; if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_OSError))) { mp_obj_t v = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); mp_obj_get_int_maybe(v, &ret); // get errno value ret = -ret; } } return ret; } #endif // MICROPY_VFS // ------------------------------- extmod/vfs_blockdev.c ----------------------- // #include "py/runtime.h" #include "py/binary.h" #include "py/objarray.h" #include "py/mperrno.h" #include "extmod/vfs.h" #if MICROPY_VFS void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) { mp_load_method(bdev, MP_QSTR_readblocks, self->readblocks); mp_load_method_maybe(bdev, MP_QSTR_writeblocks, self->writeblocks); mp_load_method_maybe(bdev, MP_QSTR_ioctl, self->u.ioctl); if (self->u.ioctl[0] != MP_OBJ_NULL) { // Device supports new block protocol, so indicate it self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL; } else { // No ioctl method, so assume the device uses the old block protocol mp_load_method_maybe(bdev, MP_QSTR_sync, self->u.old.sync); mp_load_method(bdev, MP_QSTR_count, self->u.old.count); } } int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf) { if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { mp_uint_t (*f)(uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2]; return f(buf, block_num, num_blocks); } else { mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, buf}; self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); self->readblocks[3] = MP_OBJ_FROM_PTR(&ar); mp_call_method_n_kw(2, 0, self->readblocks); // TODO handle error return return 0; } } int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf) { mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, buf}; self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); self->readblocks[3] = MP_OBJ_FROM_PTR(&ar); self->readblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off); mp_obj_t ret = mp_call_method_n_kw(3, 0, self->readblocks); if (ret == mp_const_none) { return 0; } else { return MP_OBJ_SMALL_INT_VALUE(ret); } } int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, const uint8_t *buf) { if (self->writeblocks[0] == MP_OBJ_NULL) { // read-only block device return -MP_EROFS; } if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { mp_uint_t (*f)(const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2]; return f(buf, block_num, num_blocks); } else { mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, (void *)buf}; self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar); mp_call_method_n_kw(2, 0, self->writeblocks); // TODO handle error return return 0; } } int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf) { if (self->writeblocks[0] == MP_OBJ_NULL) { // read-only block device return -MP_EROFS; } mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, (void *)buf}; self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar); self->writeblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off); mp_obj_t ret = mp_call_method_n_kw(3, 0, self->writeblocks); if (ret == mp_const_none) { return 0; } else { return MP_OBJ_SMALL_INT_VALUE(ret); } } mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) { if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) { // New protocol with ioctl self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd); self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg); return mp_call_method_n_kw(2, 0, self->u.ioctl); } else { // Old protocol with sync and count switch (cmd) { case MP_BLOCKDEV_IOCTL_SYNC: if (self->u.old.sync[0] != MP_OBJ_NULL) { mp_call_method_n_kw(0, 0, self->u.old.sync); } break; case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: return mp_call_method_n_kw(0, 0, self->u.old.count); case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: // Old protocol has fixed sector size of 512 bytes break; case MP_BLOCKDEV_IOCTL_INIT: // Old protocol doesn't have init break; } return mp_const_none; } } #endif // MICROPY_VFS // ------------------ extmod/vfs_lfs.c -------------------------- // #include "py/runtime.h" #include "py/mphal.h" #include "shared/timeutils/timeutils.h" #include "extmod/vfs.h" #include "extmod/vfs_lfs.h" #if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead, LFS_MAKE_ARG_mtime }; static const mp_arg_t lfs_make_allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_readsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_progsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_lookahead, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_mtime, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, }; #if MICROPY_VFS_LFS1 #include "lib/littlefs/lfs1.h" #define LFS_BUILD_VERSION (1) #define LFSx_MACRO(s) LFS1##s #define LFSx_API(s) lfs1_##s #define MP_VFS_LFSx(s) mp_vfs_lfs1_##s #define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs1_t #define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs1_file_t #define MP_TYPE_VFS_LFSx mp_type_vfs_lfs1 #define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs1##s typedef struct _mp_obj_vfs_lfs1_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; vstr_t cur_dir; struct lfs1_config config; lfs1_t lfs; } mp_obj_vfs_lfs1_t; typedef struct _mp_obj_vfs_lfs1_file_t { mp_obj_base_t base; mp_obj_vfs_lfs1_t *vfs; lfs1_file_t file; struct lfs1_file_config cfg; uint8_t file_buffer[0]; } mp_obj_vfs_lfs1_file_t; const char *mp_vfs_lfs1_make_path(mp_obj_vfs_lfs1_t *self, mp_obj_t path_in); mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); #include "extmod/vfs_lfsx.c" #include "extmod/vfs_lfsx_file.c" #undef LFS_BUILD_VERSION #undef LFSx_MACRO #undef LFSx_API #undef MP_VFS_LFSx #undef MP_OBJ_VFS_LFSx #undef MP_OBJ_VFS_LFSx_FILE #undef MP_TYPE_VFS_LFSx #undef MP_TYPE_VFS_LFSx_ #endif // MICROPY_VFS_LFS1 #if MICROPY_VFS_LFS2 #include "lib/littlefs/lfs2.h" #define LFS_BUILD_VERSION (2) #define LFSx_MACRO(s) LFS2##s #define LFSx_API(s) lfs2_##s #define MP_VFS_LFSx(s) mp_vfs_lfs2_##s #define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs2_t #define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs2_file_t #define MP_TYPE_VFS_LFSx mp_type_vfs_lfs2 #define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs2##s // Attribute ids for lfs2_attr.type. #define LFS_ATTR_MTIME (1) // 64-bit little endian, nanoseconds since 1970/1/1 typedef struct _mp_obj_vfs_lfs2_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; bool enable_mtime; vstr_t cur_dir; struct lfs2_config config; lfs2_t lfs; } mp_obj_vfs_lfs2_t; typedef struct _mp_obj_vfs_lfs2_file_t { mp_obj_base_t base; mp_obj_vfs_lfs2_t *vfs; uint8_t mtime[8]; lfs2_file_t file; struct lfs2_file_config cfg; struct lfs2_attr attrs[1]; uint8_t file_buffer[0]; } mp_obj_vfs_lfs2_file_t; const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in); mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); STATIC void lfs_get_mtime(uint8_t buf[8]) { // On-disk storage of timestamps uses 1970 as the Epoch, so convert from host's Epoch. uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mp_hal_time_ns()); // Store "ns" to "buf" in little-endian format (essentially htole64). for (size_t i = 0; i < 8; ++i) { buf[i] = ns; ns >>= 8; } } #include "extmod/vfs_lfsx.c" #include "extmod/vfs_lfsx_file.c" #endif // MICROPY_VFS_LFS2 #endif // MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) // ------------------- lib/lfs2.c --------------------- // #define LFS2_BLOCK_NULL ((lfs2_block_t)-1) #define LFS2_BLOCK_INLINE ((lfs2_block_t)-2) /// Caching block device operations /// static inline void lfs2_cache_drop(lfs2_t *lfs2, lfs2_cache_t *rcache) { // do not zero, cheaper if cache is readonly or only going to be // written with identical data (during relocates) (void)lfs2; rcache->block = LFS2_BLOCK_NULL; } static inline void lfs2_cache_zero(lfs2_t *lfs2, lfs2_cache_t *pcache) { // zero to avoid information leak memset(pcache->buffer, 0xff, lfs2->cfg->cache_size); pcache->block = LFS2_BLOCK_NULL; } static int lfs2_bd_read(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_size_t hint, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { uint8_t *data = buffer; if (block >= lfs2->cfg->block_count || off+size > lfs2->cfg->block_size) { return LFS2_ERR_CORRUPT; } while (size > 0) { lfs2_size_t diff = size; if (pcache && block == pcache->block && off < pcache->off + pcache->size) { if (off >= pcache->off) { // is already in pcache? diff = lfs2_min(diff, pcache->size - (off-pcache->off)); memcpy(data, &pcache->buffer[off-pcache->off], diff); data += diff; off += diff; size -= diff; continue; } // pcache takes priority diff = lfs2_min(diff, pcache->off-off); } if (block == rcache->block && off < rcache->off + rcache->size) { if (off >= rcache->off) { // is already in rcache? diff = lfs2_min(diff, rcache->size - (off-rcache->off)); memcpy(data, &rcache->buffer[off-rcache->off], diff); data += diff; off += diff; size -= diff; continue; } // rcache takes priority diff = lfs2_min(diff, rcache->off-off); } if (size >= hint && off % lfs2->cfg->read_size == 0 && size >= lfs2->cfg->read_size) { // bypass cache? diff = lfs2_aligndown(diff, lfs2->cfg->read_size); int err = lfs2->cfg->read(lfs2->cfg, block, off, data, diff); if (err) { return err; } data += diff; off += diff; size -= diff; continue; } // load to cache, first condition can no longer fail LFS2_ASSERT(block < lfs2->cfg->block_count); rcache->block = block; rcache->off = lfs2_aligndown(off, lfs2->cfg->read_size); rcache->size = lfs2_min( lfs2_min( lfs2_alignup(off+hint, lfs2->cfg->read_size), lfs2->cfg->block_size) - rcache->off, lfs2->cfg->cache_size); int err = lfs2->cfg->read(lfs2->cfg, rcache->block, rcache->off, rcache->buffer, rcache->size); LFS2_ASSERT(err <= 0); if (err) { return err; } } return 0; } enum { LFS2_CMP_EQ = 0, LFS2_CMP_LT = 1, LFS2_CMP_GT = 2, }; static int lfs2_bd_cmp(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_size_t hint, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { const uint8_t *data = buffer; lfs2_size_t diff = 0; for (lfs2_off_t i = 0; i < size; i += diff) { uint8_t dat[8]; diff = lfs2_min(size-i, sizeof(dat)); int res = lfs2_bd_read(lfs2, pcache, rcache, hint-i, block, off+i, &dat, diff); if (res) { return res; } res = memcmp(dat, data + i, diff); if (res) { return res < 0 ? LFS2_CMP_LT : LFS2_CMP_GT; } } return LFS2_CMP_EQ; } #ifndef LFS2_READONLY static int lfs2_bd_flush(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { if (pcache->block != LFS2_BLOCK_NULL && pcache->block != LFS2_BLOCK_INLINE) { LFS2_ASSERT(pcache->block < lfs2->cfg->block_count); lfs2_size_t diff = lfs2_alignup(pcache->size, lfs2->cfg->prog_size); int err = lfs2->cfg->prog(lfs2->cfg, pcache->block, pcache->off, pcache->buffer, diff); LFS2_ASSERT(err <= 0); if (err) { return err; } if (validate) { // check data on disk lfs2_cache_drop(lfs2, rcache); int res = lfs2_bd_cmp(lfs2, NULL, rcache, diff, pcache->block, pcache->off, pcache->buffer, diff); if (res < 0) { return res; } if (res != LFS2_CMP_EQ) { return LFS2_ERR_CORRUPT; } } lfs2_cache_zero(lfs2, pcache); } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_bd_sync(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { lfs2_cache_drop(lfs2, rcache); int err = lfs2_bd_flush(lfs2, pcache, rcache, validate); if (err) { return err; } err = lfs2->cfg->sync(lfs2->cfg); LFS2_ASSERT(err <= 0); return err; } #endif #ifndef LFS2_READONLY static int lfs2_bd_prog(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { const uint8_t *data = buffer; LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->cfg->block_count); LFS2_ASSERT(off + size <= lfs2->cfg->block_size); while (size > 0) { if (block == pcache->block && off >= pcache->off && off < pcache->off + lfs2->cfg->cache_size) { // already fits in pcache? lfs2_size_t diff = lfs2_min(size, lfs2->cfg->cache_size - (off-pcache->off)); memcpy(&pcache->buffer[off-pcache->off], data, diff); data += diff; off += diff; size -= diff; pcache->size = lfs2_max(pcache->size, off - pcache->off); if (pcache->size == lfs2->cfg->cache_size) { // eagerly flush out pcache if we fill up int err = lfs2_bd_flush(lfs2, pcache, rcache, validate); if (err) { return err; } } continue; } // pcache must have been flushed, either by programming and // entire block or manually flushing the pcache LFS2_ASSERT(pcache->block == LFS2_BLOCK_NULL); // prepare pcache, first condition can no longer fail pcache->block = block; pcache->off = lfs2_aligndown(off, lfs2->cfg->prog_size); pcache->size = 0; } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { LFS2_ASSERT(block < lfs2->cfg->block_count); int err = lfs2->cfg->erase(lfs2->cfg, block); LFS2_ASSERT(err <= 0); return err; } #endif /// Small type-level utilities /// // operations on block pairs static inline void lfs2_pair_swap(lfs2_block_t pair[2]) { lfs2_block_t t = pair[0]; pair[0] = pair[1]; pair[1] = t; } static inline bool lfs2_pair_isnull(const lfs2_block_t pair[2]) { return pair[0] == LFS2_BLOCK_NULL || pair[1] == LFS2_BLOCK_NULL; } static inline int lfs2_pair_cmp( const lfs2_block_t paira[2], const lfs2_block_t pairb[2]) { return !(paira[0] == pairb[0] || paira[1] == pairb[1] || paira[0] == pairb[1] || paira[1] == pairb[0]); } static inline bool lfs2_pair_sync( const lfs2_block_t paira[2], const lfs2_block_t pairb[2]) { return (paira[0] == pairb[0] && paira[1] == pairb[1]) || (paira[0] == pairb[1] && paira[1] == pairb[0]); } static inline void lfs2_pair_fromle32(lfs2_block_t pair[2]) { pair[0] = lfs2_fromle32(pair[0]); pair[1] = lfs2_fromle32(pair[1]); } static inline void lfs2_pair_tole32(lfs2_block_t pair[2]) { pair[0] = lfs2_tole32(pair[0]); pair[1] = lfs2_tole32(pair[1]); } // operations on 32-bit entry tags typedef uint32_t lfs2_tag_t; typedef int32_t lfs2_stag_t; #define LFS2_MKTAG(type, id, size) \ (((lfs2_tag_t)(type) << 20) | ((lfs2_tag_t)(id) << 10) | (lfs2_tag_t)(size)) #define LFS2_MKTAG_IF(cond, type, id, size) \ ((cond) ? LFS2_MKTAG(type, id, size) : LFS2_MKTAG(LFS2_FROM_NOOP, 0, 0)) #define LFS2_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ ((cond) ? LFS2_MKTAG(type1, id1, size1) : LFS2_MKTAG(type2, id2, size2)) static inline bool lfs2_tag_isvalid(lfs2_tag_t tag) { return !(tag & 0x80000000); } static inline bool lfs2_tag_isdelete(lfs2_tag_t tag) { return ((int32_t)(tag << 22) >> 22) == -1; } static inline uint16_t lfs2_tag_type1(lfs2_tag_t tag) { return (tag & 0x70000000) >> 20; } static inline uint16_t lfs2_tag_type3(lfs2_tag_t tag) { return (tag & 0x7ff00000) >> 20; } static inline uint8_t lfs2_tag_chunk(lfs2_tag_t tag) { return (tag & 0x0ff00000) >> 20; } static inline int8_t lfs2_tag_splice(lfs2_tag_t tag) { return (int8_t)lfs2_tag_chunk(tag); } static inline uint16_t lfs2_tag_id(lfs2_tag_t tag) { return (tag & 0x000ffc00) >> 10; } static inline lfs2_size_t lfs2_tag_size(lfs2_tag_t tag) { return tag & 0x000003ff; } static inline lfs2_size_t lfs2_tag_dsize(lfs2_tag_t tag) { return sizeof(tag) + lfs2_tag_size(tag + lfs2_tag_isdelete(tag)); } // operations on attributes in attribute lists struct lfs2_mattr { lfs2_tag_t tag; const void *buffer; }; struct lfs2_diskoff { lfs2_block_t block; lfs2_off_t off; }; #define LFS2_MKATTRS(...) \ (struct lfs2_mattr[]){__VA_ARGS__}, \ sizeof((struct lfs2_mattr[]){__VA_ARGS__}) / sizeof(struct lfs2_mattr) // operations on global state static inline void lfs2_gstate_xor(lfs2_gstate_t *a, const lfs2_gstate_t *b) { for (int i = 0; i < 3; i++) { ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; } } static inline bool lfs2_gstate_iszero(const lfs2_gstate_t *a) { for (int i = 0; i < 3; i++) { if (((uint32_t*)a)[i] != 0) { return false; } } return true; } static inline bool lfs2_gstate_hasorphans(const lfs2_gstate_t *a) { return lfs2_tag_size(a->tag); } static inline uint8_t lfs2_gstate_getorphans(const lfs2_gstate_t *a) { return lfs2_tag_size(a->tag); } static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) { return lfs2_tag_type1(a->tag); } static inline bool lfs2_gstate_hasmovehere(const lfs2_gstate_t *a, const lfs2_block_t *pair) { return lfs2_tag_type1(a->tag) && lfs2_pair_cmp(a->pair, pair) == 0; } static inline void lfs2_gstate_fromle32(lfs2_gstate_t *a) { a->tag = lfs2_fromle32(a->tag); a->pair[0] = lfs2_fromle32(a->pair[0]); a->pair[1] = lfs2_fromle32(a->pair[1]); } static inline void lfs2_gstate_tole32(lfs2_gstate_t *a) { a->tag = lfs2_tole32(a->tag); a->pair[0] = lfs2_tole32(a->pair[0]); a->pair[1] = lfs2_tole32(a->pair[1]); } // other endianness operations static void lfs2_ctz_fromle32(struct lfs2_ctz *ctz) { ctz->head = lfs2_fromle32(ctz->head); ctz->size = lfs2_fromle32(ctz->size); } #ifndef LFS2_READONLY static void lfs2_ctz_tole32(struct lfs2_ctz *ctz) { ctz->head = lfs2_tole32(ctz->head); ctz->size = lfs2_tole32(ctz->size); } #endif static inline void lfs2_superblock_fromle32(lfs2_superblock_t *superblock) { superblock->version = lfs2_fromle32(superblock->version); superblock->block_size = lfs2_fromle32(superblock->block_size); superblock->block_count = lfs2_fromle32(superblock->block_count); superblock->name_max = lfs2_fromle32(superblock->name_max); superblock->file_max = lfs2_fromle32(superblock->file_max); superblock->attr_max = lfs2_fromle32(superblock->attr_max); } static inline void lfs2_superblock_tole32(lfs2_superblock_t *superblock) { superblock->version = lfs2_tole32(superblock->version); superblock->block_size = lfs2_tole32(superblock->block_size); superblock->block_count = lfs2_tole32(superblock->block_count); superblock->name_max = lfs2_tole32(superblock->name_max); superblock->file_max = lfs2_tole32(superblock->file_max); superblock->attr_max = lfs2_tole32(superblock->attr_max); } #ifndef LFS2_NO_ASSERT static inline bool lfs2_mlist_isopen(struct lfs2_mlist *head, struct lfs2_mlist *node) { for (struct lfs2_mlist **p = &head; *p; p = &(*p)->next) { if (*p == (struct lfs2_mlist*)node) { return true; } } return false; } #endif static inline void lfs2_mlist_remove(lfs2_t *lfs2, struct lfs2_mlist *mlist) { for (struct lfs2_mlist **p = &lfs2->mlist; *p; p = &(*p)->next) { if (*p == mlist) { *p = (*p)->next; break; } } } static inline void lfs2_mlist_append(lfs2_t *lfs2, struct lfs2_mlist *mlist) { mlist->next = lfs2->mlist; lfs2->mlist = mlist; } /// Internal operations predeclared here /// #ifndef LFS2_READONLY static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount); static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t begin, uint16_t end); static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file); static void lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans); static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]); static int lfs2_fs_pred(lfs2_t *lfs2, const lfs2_block_t dir[2], lfs2_mdir_t *pdir); static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t dir[2], lfs2_mdir_t *parent); static int lfs2_fs_relocate(lfs2_t *lfs2, const lfs2_block_t oldpair[2], lfs2_block_t newpair[2]); static int lfs2_fs_forceconsistency(lfs2_t *lfs2); #endif #ifdef LFS2_MIGRATE static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); #endif static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir); static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file); static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file); static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2); static int lfs2_fs_rawtraverse(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans); static int lfs2_deinit(lfs2_t *lfs2); static int lfs2_rawunmount(lfs2_t *lfs2); /// Block allocator /// #ifndef LFS2_READONLY static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; lfs2_block_t off = ((block - lfs2->free.off) + lfs2->cfg->block_count) % lfs2->cfg->block_count; if (off < lfs2->free.size) { lfs2->free.buffer[off / 32] |= 1U << (off % 32); } return 0; } #endif // indicate allocated blocks have been committed into the filesystem, this // is to prevent blocks from being garbage collected in the middle of a // commit operation static void lfs2_alloc_ack(lfs2_t *lfs2) { lfs2->free.ack = lfs2->cfg->block_count; } // drop the lookahead buffer, this is done during mounting and failed // traversals in order to avoid invalid lookahead state static void lfs2_alloc_drop(lfs2_t *lfs2) { lfs2->free.size = 0; lfs2->free.i = 0; lfs2_alloc_ack(lfs2); } #ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { while (lfs2->free.i != lfs2->free.size) { lfs2_block_t off = lfs2->free.i; lfs2->free.i += 1; lfs2->free.ack -= 1; if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) { // found a free block *block = (lfs2->free.off + off) % lfs2->cfg->block_count; // eagerly find next off so an alloc ack can // discredit old lookahead blocks while (lfs2->free.i != lfs2->free.size && (lfs2->free.buffer[lfs2->free.i / 32] & (1U << (lfs2->free.i % 32)))) { lfs2->free.i += 1; lfs2->free.ack -= 1; } return 0; } } // check if we have looked at all blocks since last ack if (lfs2->free.ack == 0) { LFS2_ERROR("No more free space %"PRIu32, lfs2->free.i + lfs2->free.off); return LFS2_ERR_NOSPC; } lfs2->free.off = (lfs2->free.off + lfs2->free.size) % lfs2->cfg->block_count; lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); lfs2->free.i = 0; // find mask of free blocks from tree memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); if (err) { lfs2_alloc_drop(lfs2); return err; } } } #endif /// Metadata pair and directory operations /// static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t gmask, lfs2_tag_t gtag, lfs2_off_t goff, void *gbuffer, lfs2_size_t gsize) { lfs2_off_t off = dir->off; lfs2_tag_t ntag = dir->etag; lfs2_stag_t gdiff = 0; if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair) && lfs2_tag_id(gmask) != 0 && lfs2_tag_id(lfs2->gdisk.tag) <= lfs2_tag_id(gtag)) { // synthetic moves gdiff -= LFS2_MKTAG(0, 1, 0); } // iterate over dir block backwards (for faster lookups) while (off >= sizeof(lfs2_tag_t) + lfs2_tag_dsize(ntag)) { off -= lfs2_tag_dsize(ntag); lfs2_tag_t tag = ntag; int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(ntag), dir->pair[0], off, &ntag, sizeof(ntag)); if (err) { return err; } ntag = (lfs2_frombe32(ntag) ^ tag) & 0x7fffffff; if (lfs2_tag_id(gmask) != 0 && lfs2_tag_type1(tag) == LFS2_TYPE_SPLICE && lfs2_tag_id(tag) <= lfs2_tag_id(gtag - gdiff)) { if (tag == (LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0) | (LFS2_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { // found where we were created return LFS2_ERR_NOENT; } // move around splices gdiff += LFS2_MKTAG(0, lfs2_tag_splice(tag), 0); } if ((gmask & tag) == (gmask & (gtag - gdiff))) { if (lfs2_tag_isdelete(tag)) { return LFS2_ERR_NOENT; } lfs2_size_t diff = lfs2_min(lfs2_tag_size(tag), gsize); err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, diff, dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); if (err) { return err; } memset((uint8_t*)gbuffer + diff, 0, gsize - diff); return tag + gdiff; } } return LFS2_ERR_NOENT; } static lfs2_stag_t lfs2_dir_get(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t gmask, lfs2_tag_t gtag, void *buffer) { return lfs2_dir_getslice(lfs2, dir, gmask, gtag, 0, buffer, lfs2_tag_size(gtag)); } static int lfs2_dir_getread(lfs2_t *lfs2, const lfs2_mdir_t *dir, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_size_t hint, lfs2_tag_t gmask, lfs2_tag_t gtag, lfs2_off_t off, void *buffer, lfs2_size_t size) { uint8_t *data = buffer; if (off+size > lfs2->cfg->block_size) { return LFS2_ERR_CORRUPT; } while (size > 0) { lfs2_size_t diff = size; if (pcache && pcache->block == LFS2_BLOCK_INLINE && off < pcache->off + pcache->size) { if (off >= pcache->off) { // is already in pcache? diff = lfs2_min(diff, pcache->size - (off-pcache->off)); memcpy(data, &pcache->buffer[off-pcache->off], diff); data += diff; off += diff; size -= diff; continue; } // pcache takes priority diff = lfs2_min(diff, pcache->off-off); } if (rcache->block == LFS2_BLOCK_INLINE && off < rcache->off + rcache->size) { if (off >= rcache->off) { // is already in rcache? diff = lfs2_min(diff, rcache->size - (off-rcache->off)); memcpy(data, &rcache->buffer[off-rcache->off], diff); data += diff; off += diff; size -= diff; continue; } // rcache takes priority diff = lfs2_min(diff, rcache->off-off); } // load to cache, first condition can no longer fail rcache->block = LFS2_BLOCK_INLINE; rcache->off = lfs2_aligndown(off, lfs2->cfg->read_size); rcache->size = lfs2_min(lfs2_alignup(off+hint, lfs2->cfg->read_size), lfs2->cfg->cache_size); int err = lfs2_dir_getslice(lfs2, dir, gmask, gtag, rcache->off, rcache->buffer, rcache->size); if (err < 0) { return err; } } return 0; } #ifndef LFS2_READONLY static int lfs2_dir_traverse_filter(void *p, lfs2_tag_t tag, const void *buffer) { lfs2_tag_t *filtertag = p; (void)buffer; // which mask depends on unique bit in tag structure uint32_t mask = (tag & LFS2_MKTAG(0x100, 0, 0)) ? LFS2_MKTAG(0x7ff, 0x3ff, 0) : LFS2_MKTAG(0x700, 0x3ff, 0); // check for redundancy if ((mask & tag) == (mask & *filtertag) || lfs2_tag_isdelete(*filtertag) || (LFS2_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( LFS2_MKTAG(LFS2_TYPE_DELETE, 0, 0) | (LFS2_MKTAG(0, 0x3ff, 0) & *filtertag))) { return true; } // check if we need to adjust for created/deleted tags if (lfs2_tag_type1(tag) == LFS2_TYPE_SPLICE && lfs2_tag_id(tag) <= lfs2_tag_id(*filtertag)) { *filtertag += LFS2_MKTAG(0, lfs2_tag_splice(tag), 0); } return false; } #endif #ifndef LFS2_READONLY static int lfs2_dir_traverse(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_off_t off, lfs2_tag_t ptag, const struct lfs2_mattr *attrs, int attrcount, lfs2_tag_t tmask, lfs2_tag_t ttag, uint16_t begin, uint16_t end, int16_t diff, int (*cb)(void *data, lfs2_tag_t tag, const void *buffer), void *data) { // iterate over directory and attrs while (true) { lfs2_tag_t tag; const void *buffer; struct lfs2_diskoff disk; if (off+lfs2_tag_dsize(ptag) < dir->off) { off += lfs2_tag_dsize(ptag); int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(tag), dir->pair[0], off, &tag, sizeof(tag)); if (err) { return err; } tag = (lfs2_frombe32(tag) ^ ptag) | 0x80000000; disk.block = dir->pair[0]; disk.off = off+sizeof(lfs2_tag_t); buffer = &disk; ptag = tag; } else if (attrcount > 0) { tag = attrs[0].tag; buffer = attrs[0].buffer; attrs += 1; attrcount -= 1; } else { return 0; } lfs2_tag_t mask = LFS2_MKTAG(0x7ff, 0, 0); if ((mask & tmask & tag) != (mask & tmask & ttag)) { continue; } // do we need to filter? inlining the filtering logic here allows // for some minor optimizations if (lfs2_tag_id(tmask) != 0) { // scan for duplicates and update tag based on creates/deletes int filter = lfs2_dir_traverse(lfs2, dir, off, ptag, attrs, attrcount, 0, 0, 0, 0, 0, lfs2_dir_traverse_filter, &tag); if (filter < 0) { return filter; } if (filter) { continue; } // in filter range? if (!(lfs2_tag_id(tag) >= begin && lfs2_tag_id(tag) < end)) { continue; } } // handle special cases for mcu-side operations if (lfs2_tag_type3(tag) == LFS2_FROM_NOOP) { // do nothing } else if (lfs2_tag_type3(tag) == LFS2_FROM_MOVE) { uint16_t fromid = lfs2_tag_size(tag); uint16_t toid = lfs2_tag_id(tag); int err = lfs2_dir_traverse(lfs2, buffer, 0, 0xffffffff, NULL, 0, LFS2_MKTAG(0x600, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, 0, 0), fromid, fromid+1, toid-fromid+diff, cb, data); if (err) { return err; } } else if (lfs2_tag_type3(tag) == LFS2_FROM_USERATTRS) { for (unsigned i = 0; i < lfs2_tag_size(tag); i++) { const struct lfs2_attr *a = buffer; int err = cb(data, LFS2_MKTAG(LFS2_TYPE_USERATTR + a[i].type, lfs2_tag_id(tag) + diff, a[i].size), a[i].buffer); if (err) { return err; } } } else { int err = cb(data, tag + LFS2_MKTAG(0, diff, 0), buffer); if (err) { return err; } } } } #endif static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, lfs2_mdir_t *dir, const lfs2_block_t pair[2], lfs2_tag_t fmask, lfs2_tag_t ftag, uint16_t *id, int (*cb)(void *data, lfs2_tag_t tag, const void *buffer), void *data) { // we can find tag very efficiently during a fetch, since we're already // scanning the entire directory lfs2_stag_t besttag = -1; // if either block address is invalid we return LFS2_ERR_CORRUPT here, // otherwise later writes to the pair could fail if (pair[0] >= lfs2->cfg->block_count || pair[1] >= lfs2->cfg->block_count) { return LFS2_ERR_CORRUPT; } // find the block with the most recent revision uint32_t revs[2] = {0, 0}; int r = 0; for (int i = 0; i < 2; i++) { int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(revs[i]), pair[i], 0, &revs[i], sizeof(revs[i])); revs[i] = lfs2_fromle32(revs[i]); if (err && err != LFS2_ERR_CORRUPT) { return err; } if (err != LFS2_ERR_CORRUPT && lfs2_scmp(revs[i], revs[(i+1)%2]) > 0) { r = i; } } dir->pair[0] = pair[(r+0)%2]; dir->pair[1] = pair[(r+1)%2]; dir->rev = revs[(r+0)%2]; dir->off = 0; // nonzero = found some commits // now scan tags to fetch the actual dir and find possible match for (int i = 0; i < 2; i++) { lfs2_off_t off = 0; lfs2_tag_t ptag = 0xffffffff; uint16_t tempcount = 0; lfs2_block_t temptail[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; bool tempsplit = false; lfs2_stag_t tempbesttag = besttag; dir->rev = lfs2_tole32(dir->rev); uint32_t crc = lfs2_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); dir->rev = lfs2_fromle32(dir->rev); while (true) { // extract next tag lfs2_tag_t tag; off += lfs2_tag_dsize(ptag); int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, lfs2->cfg->block_size, dir->pair[0], off, &tag, sizeof(tag)); if (err) { if (err == LFS2_ERR_CORRUPT) { // can't continue? dir->erased = false; break; } return err; } crc = lfs2_crc(crc, &tag, sizeof(tag)); tag = lfs2_frombe32(tag) ^ ptag; // next commit not yet programmed or we're not in valid range if (!lfs2_tag_isvalid(tag)) { dir->erased = (lfs2_tag_type1(ptag) == LFS2_TYPE_CRC && dir->off % lfs2->cfg->prog_size == 0); break; } else if (off + lfs2_tag_dsize(tag) > lfs2->cfg->block_size) { dir->erased = false; break; } ptag = tag; if (lfs2_tag_type1(tag) == LFS2_TYPE_CRC) { // check the crc attr uint32_t dcrc; err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, lfs2->cfg->block_size, dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); if (err) { if (err == LFS2_ERR_CORRUPT) { dir->erased = false; break; } return err; } dcrc = lfs2_fromle32(dcrc); if (crc != dcrc) { dir->erased = false; break; } // reset the next bit if we need to ptag ^= (lfs2_tag_t)(lfs2_tag_chunk(tag) & 1U) << 31; // toss our crc into the filesystem seed for // pseudorandom numbers, note we use another crc here // as a collection function because it is sufficiently // random and convenient lfs2->seed = lfs2_crc(lfs2->seed, &crc, sizeof(crc)); // update with what's found so far besttag = tempbesttag; dir->off = off + lfs2_tag_dsize(tag); dir->etag = ptag; dir->count = tempcount; dir->tail[0] = temptail[0]; dir->tail[1] = temptail[1]; dir->split = tempsplit; // reset crc crc = 0xffffffff; continue; } // crc the entry first, hopefully leaving it in the cache for (lfs2_off_t j = sizeof(tag); j < lfs2_tag_dsize(tag); j++) { uint8_t dat; err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, lfs2->cfg->block_size, dir->pair[0], off+j, &dat, 1); if (err) { if (err == LFS2_ERR_CORRUPT) { dir->erased = false; break; } return err; } crc = lfs2_crc(crc, &dat, 1); } // directory modification tags? if (lfs2_tag_type1(tag) == LFS2_TYPE_NAME) { // increase count of files if necessary if (lfs2_tag_id(tag) >= tempcount) { tempcount = lfs2_tag_id(tag) + 1; } } else if (lfs2_tag_type1(tag) == LFS2_TYPE_SPLICE) { tempcount += lfs2_tag_splice(tag); if (tag == (LFS2_MKTAG(LFS2_TYPE_DELETE, 0, 0) | (LFS2_MKTAG(0, 0x3ff, 0) & tempbesttag))) { tempbesttag |= 0x80000000; } else if (tempbesttag != -1 && lfs2_tag_id(tag) <= lfs2_tag_id(tempbesttag)) { tempbesttag += LFS2_MKTAG(0, lfs2_tag_splice(tag), 0); } } else if (lfs2_tag_type1(tag) == LFS2_TYPE_TAIL) { tempsplit = (lfs2_tag_chunk(tag) & 1); err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, lfs2->cfg->block_size, dir->pair[0], off+sizeof(tag), &temptail, 8); if (err) { if (err == LFS2_ERR_CORRUPT) { dir->erased = false; break; } } lfs2_pair_fromle32(temptail); } // found a match for our fetcher? if ((fmask & tag) == (fmask & ftag)) { int res = cb(data, tag, &(struct lfs2_diskoff){ dir->pair[0], off+sizeof(tag)}); if (res < 0) { if (res == LFS2_ERR_CORRUPT) { dir->erased = false; break; } return res; } if (res == LFS2_CMP_EQ) { // found a match tempbesttag = tag; } else if ((LFS2_MKTAG(0x7ff, 0x3ff, 0) & tag) == (LFS2_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { // found an identical tag, but contents didn't match // this must mean that our besttag has been overwritten tempbesttag = -1; } else if (res == LFS2_CMP_GT && lfs2_tag_id(tag) <= lfs2_tag_id(tempbesttag)) { // found a greater match, keep track to keep things sorted tempbesttag = tag | 0x80000000; } } } // consider what we have good enough if (dir->off > 0) { // synthetic move if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair)) { if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(besttag)) { besttag |= 0x80000000; } else if (besttag != -1 && lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) { besttag -= LFS2_MKTAG(0, 1, 0); } } // found tag? or found best id? if (id) { *id = lfs2_min(lfs2_tag_id(besttag), dir->count); } if (lfs2_tag_isvalid(besttag)) { return besttag; } else if (lfs2_tag_id(besttag) < dir->count) { return LFS2_ERR_NOENT; } else { return 0; } } // failed, try the other block? lfs2_pair_swap(dir->pair); dir->rev = revs[(r+1)%2]; } LFS2_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", dir->pair[0], dir->pair[1]); return LFS2_ERR_CORRUPT; } static int lfs2_dir_fetch(lfs2_t *lfs2, lfs2_mdir_t *dir, const lfs2_block_t pair[2]) { // note, mask=-1, tag=-1 can never match a tag since this // pattern has the invalid bit set return (int)lfs2_dir_fetchmatch(lfs2, dir, pair, (lfs2_tag_t)-1, (lfs2_tag_t)-1, NULL, NULL, NULL); } static int lfs2_dir_getgstate(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_gstate_t *gstate) { lfs2_gstate_t temp; lfs2_stag_t res = lfs2_dir_get(lfs2, dir, LFS2_MKTAG(0x7ff, 0, 0), LFS2_MKTAG(LFS2_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); if (res < 0 && res != LFS2_ERR_NOENT) { return res; } if (res != LFS2_ERR_NOENT) { // xor together to find resulting gstate lfs2_gstate_fromle32(&temp); lfs2_gstate_xor(gstate, &temp); } return 0; } static int lfs2_dir_getinfo(lfs2_t *lfs2, lfs2_mdir_t *dir, uint16_t id, struct lfs2_info *info) { if (id == 0x3ff) { // special case for root strcpy(info->name, "/"); info->type = LFS2_TYPE_DIR; return 0; } lfs2_stag_t tag = lfs2_dir_get(lfs2, dir, LFS2_MKTAG(0x780, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_NAME, id, lfs2->name_max+1), info->name); if (tag < 0) { return (int)tag; } info->type = lfs2_tag_type3(tag); struct lfs2_ctz ctz; tag = lfs2_dir_get(lfs2, dir, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, id, sizeof(ctz)), &ctz); if (tag < 0) { return (int)tag; } lfs2_ctz_fromle32(&ctz); if (lfs2_tag_type3(tag) == LFS2_TYPE_CTZSTRUCT) { info->size = ctz.size; } else if (lfs2_tag_type3(tag) == LFS2_TYPE_INLINESTRUCT) { info->size = lfs2_tag_size(tag); } return 0; } struct lfs2_dir_find_match { lfs2_t *lfs2; const void *name; lfs2_size_t size; }; static int lfs2_dir_find_match(void *data, lfs2_tag_t tag, const void *buffer) { struct lfs2_dir_find_match *name = data; lfs2_t *lfs2 = name->lfs2; const struct lfs2_diskoff *disk = buffer; // compare with disk lfs2_size_t diff = lfs2_min(name->size, lfs2_tag_size(tag)); int res = lfs2_bd_cmp(lfs2, NULL, &lfs2->rcache, diff, disk->block, disk->off, name->name, diff); if (res != LFS2_CMP_EQ) { return res; } // only equal if our size is still the same if (name->size != lfs2_tag_size(tag)) { return (name->size < lfs2_tag_size(tag)) ? LFS2_CMP_LT : LFS2_CMP_GT; } // found a match! return LFS2_CMP_EQ; } static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, const char **path, uint16_t *id) { // we reduce path to a single name if we can find it const char *name = *path; if (id) { *id = 0x3ff; } // default to root dir lfs2_stag_t tag = LFS2_MKTAG(LFS2_TYPE_DIR, 0x3ff, 0); dir->tail[0] = lfs2->root[0]; dir->tail[1] = lfs2->root[1]; while (true) { nextname: // skip slashes name += strspn(name, "/"); lfs2_size_t namelen = strcspn(name, "/"); // skip '.' and root '..' if ((namelen == 1 && memcmp(name, ".", 1) == 0) || (namelen == 2 && memcmp(name, "..", 2) == 0)) { name += namelen; goto nextname; } // skip if matched by '..' in name const char *suffix = name + namelen; lfs2_size_t sufflen; int depth = 1; while (true) { suffix += strspn(suffix, "/"); sufflen = strcspn(suffix, "/"); if (sufflen == 0) { break; } if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { depth -= 1; if (depth == 0) { name = suffix + sufflen; goto nextname; } } else { depth += 1; } suffix += sufflen; } // found path if (name[0] == '\0') { return tag; } // update what we've found so far *path = name; // only continue if we hit a directory if (lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { return LFS2_ERR_NOTDIR; } // grab the entry data if (lfs2_tag_id(tag) != 0x3ff) { lfs2_stag_t res = lfs2_dir_get(lfs2, dir, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, lfs2_tag_id(tag), 8), dir->tail); if (res < 0) { return res; } lfs2_pair_fromle32(dir->tail); } // find entry matching name while (true) { tag = lfs2_dir_fetchmatch(lfs2, dir, dir->tail, LFS2_MKTAG(0x780, 0, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, namelen), // are we last name? (strchr(name, '/') == NULL) ? id : NULL, lfs2_dir_find_match, &(struct lfs2_dir_find_match){ lfs2, name, namelen}); if (tag < 0) { return tag; } if (tag) { break; } if (!dir->split) { return LFS2_ERR_NOENT; } } // to next name name += namelen; } } // commit logic struct lfs2_commit { lfs2_block_t block; lfs2_off_t off; lfs2_tag_t ptag; uint32_t crc; lfs2_off_t begin; lfs2_off_t end; }; #ifndef LFS2_READONLY static int lfs2_dir_commitprog(lfs2_t *lfs2, struct lfs2_commit *commit, const void *buffer, lfs2_size_t size) { int err = lfs2_bd_prog(lfs2, &lfs2->pcache, &lfs2->rcache, false, commit->block, commit->off , (const uint8_t*)buffer, size); if (err) { return err; } commit->crc = lfs2_crc(commit->crc, buffer, size); commit->off += size; return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit, lfs2_tag_t tag, const void *buffer) { // check if we fit lfs2_size_t dsize = lfs2_tag_dsize(tag); if (commit->off + dsize > commit->end) { return LFS2_ERR_NOSPC; } // write out tag lfs2_tag_t ntag = lfs2_tobe32((tag & 0x7fffffff) ^ commit->ptag); int err = lfs2_dir_commitprog(lfs2, commit, &ntag, sizeof(ntag)); if (err) { return err; } if (!(tag & 0x80000000)) { // from memory err = lfs2_dir_commitprog(lfs2, commit, buffer, dsize-sizeof(tag)); if (err) { return err; } } else { // from disk const struct lfs2_diskoff *disk = buffer; for (lfs2_off_t i = 0; i < dsize-sizeof(tag); i++) { // rely on caching to make this efficient uint8_t dat; err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, dsize-sizeof(tag)-i, disk->block, disk->off+i, &dat, 1); if (err) { return err; } err = lfs2_dir_commitprog(lfs2, commit, &dat, 1); if (err) { return err; } } } commit->ptag = tag & 0x7fffffff; return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { // align to program units const lfs2_off_t end = lfs2_alignup(commit->off + 2*sizeof(uint32_t), lfs2->cfg->prog_size); lfs2_off_t off1 = 0; uint32_t crc1 = 0; // create crc tags to fill up remainder of commit, note that // padding is not crced, which lets fetches skip padding but // makes committing a bit more complicated while (commit->off < end) { lfs2_off_t off = commit->off + sizeof(lfs2_tag_t); lfs2_off_t noff = lfs2_min(end - off, 0x3fe) + off; if (noff < end) { noff = lfs2_min(noff, end - 2*sizeof(uint32_t)); } // read erased state from next program unit lfs2_tag_t tag = 0xffffffff; int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(tag), commit->block, noff, &tag, sizeof(tag)); if (err && err != LFS2_ERR_CORRUPT) { return err; } // build crc tag bool reset = ~lfs2_frombe32(tag) >> 31; tag = LFS2_MKTAG(LFS2_TYPE_CRC + reset, 0x3ff, noff - off); // write out crc uint32_t footer[2]; footer[0] = lfs2_tobe32(tag ^ commit->ptag); commit->crc = lfs2_crc(commit->crc, &footer[0], sizeof(footer[0])); footer[1] = lfs2_tole32(commit->crc); err = lfs2_bd_prog(lfs2, &lfs2->pcache, &lfs2->rcache, false, commit->block, commit->off, &footer, sizeof(footer)); if (err) { return err; } // keep track of non-padding checksum to verify if (off1 == 0) { off1 = commit->off + sizeof(uint32_t); crc1 = commit->crc; } commit->off += sizeof(tag)+lfs2_tag_size(tag); commit->ptag = tag ^ ((lfs2_tag_t)reset << 31); commit->crc = 0xffffffff; // reset crc for next "commit" } // flush buffers int err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); if (err) { return err; } // successful commit, check checksums to make sure lfs2_off_t off = commit->begin; lfs2_off_t noff = off1; while (off < end) { uint32_t crc = 0xffffffff; for (lfs2_off_t i = off; i < noff+sizeof(uint32_t); i++) { // check against written crc, may catch blocks that // become readonly and match our commit size exactly if (i == off1 && crc != crc1) { return LFS2_ERR_CORRUPT; } // leave it up to caching to make this efficient uint8_t dat; err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, noff+sizeof(uint32_t)-i, commit->block, i, &dat, 1); if (err) { return err; } crc = lfs2_crc(crc, &dat, 1); } // detected write error? if (crc != 0) { return LFS2_ERR_CORRUPT; } // skip padding off = lfs2_min(end - noff, 0x3fe) + noff; if (off < end) { off = lfs2_min(off, end - 2*sizeof(uint32_t)); } noff = off + sizeof(uint32_t); } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { // allocate pair of dir blocks (backwards, so we write block 1 first) for (int i = 0; i < 2; i++) { int err = lfs2_alloc(lfs2, &dir->pair[(i+1)%2]); if (err) { return err; } } // zero for reproducability in case initial block is unreadable dir->rev = 0; // rather than clobbering one of the blocks we just pretend // the revision may be valid int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(dir->rev), dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); dir->rev = lfs2_fromle32(dir->rev); if (err && err != LFS2_ERR_CORRUPT) { return err; } // to make sure we don't immediately evict, align the new revision count // to our block_cycles modulus, see lfs2_dir_compact for why our modulus // is tweaked this way if (lfs2->cfg->block_cycles > 0) { dir->rev = lfs2_alignup(dir->rev, ((lfs2->cfg->block_cycles+1)|1)); } // set defaults dir->off = sizeof(dir->rev); dir->etag = 0xffffffff; dir->count = 0; dir->tail[0] = LFS2_BLOCK_NULL; dir->tail[1] = LFS2_BLOCK_NULL; dir->erased = false; dir->split = false; // don't write out yet, let caller take care of that return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_drop(lfs2_t *lfs2, lfs2_mdir_t *dir, lfs2_mdir_t *tail) { // steal state int err = lfs2_dir_getgstate(lfs2, tail, &lfs2->gdelta); if (err) { return err; } // steal tail lfs2_pair_tole32(tail->tail); err = lfs2_dir_commit(lfs2, dir, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); lfs2_pair_fromle32(tail->tail); if (err) { return err; } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_split(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t split, uint16_t end) { // create tail directory lfs2_alloc_ack(lfs2); lfs2_mdir_t tail; int err = lfs2_dir_alloc(lfs2, &tail); if (err) { return err; } tail.split = dir->split; tail.tail[0] = dir->tail[0]; tail.tail[1] = dir->tail[1]; err = lfs2_dir_compact(lfs2, &tail, attrs, attrcount, source, split, end); if (err) { return err; } dir->tail[0] = tail.pair[0]; dir->tail[1] = tail.pair[1]; dir->split = true; // update root if needed if (lfs2_pair_cmp(dir->pair, lfs2->root) == 0 && split == 0) { lfs2->root[0] = tail.pair[0]; lfs2->root[1] = tail.pair[1]; } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_commit_size(void *p, lfs2_tag_t tag, const void *buffer) { lfs2_size_t *size = p; (void)buffer; *size += lfs2_tag_dsize(tag); return 0; } #endif #ifndef LFS2_READONLY struct lfs2_dir_commit_commit { lfs2_t *lfs2; struct lfs2_commit *commit; }; #endif #ifndef LFS2_READONLY static int lfs2_dir_commit_commit(void *p, lfs2_tag_t tag, const void *buffer) { struct lfs2_dir_commit_commit *commit = p; return lfs2_dir_commitattr(commit->lfs2, commit->commit, tag, buffer); } #endif #ifndef LFS2_READONLY static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t begin, uint16_t end) { // save some state in case block is bad const lfs2_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; bool relocated = false; bool tired = false; // should we split? while (end - begin > 1) { // find size lfs2_size_t size = 0; int err = lfs2_dir_traverse(lfs2, source, 0, 0xffffffff, attrs, attrcount, LFS2_MKTAG(0x400, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, 0), begin, end, -begin, lfs2_dir_commit_size, &size); if (err) { return err; } // space is complicated, we need room for tail, crc, gstate, // cleanup delete, and we cap at half a block to give room // for metadata updates. if (end - begin < 0xff && size <= lfs2_min(lfs2->cfg->block_size - 36, lfs2_alignup(lfs2->cfg->block_size/2, lfs2->cfg->prog_size))) { break; } // can't fit, need to split, we should really be finding the // largest size that fits with a small binary search, but right now // it's not worth the code size uint16_t split = (end - begin) / 2; err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin+split, end); if (err) { // if we fail to split, we may be able to overcompact, unless // we're too big for even the full block, in which case our // only option is to error if (err == LFS2_ERR_NOSPC && size <= lfs2->cfg->block_size - 36) { break; } return err; } end = begin + split; } // increment revision count dir->rev += 1; // If our revision count == n * block_cycles, we should force a relocation, // this is how littlefs wear-levels at the metadata-pair level. Note that we // actually use (block_cycles+1)|1, this is to avoid two corner cases: // 1. block_cycles = 1, which would prevent relocations from terminating // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate // one metadata block in the pair, effectively making this useless if (lfs2->cfg->block_cycles > 0 && (dir->rev % ((lfs2->cfg->block_cycles+1)|1) == 0)) { if (lfs2_pair_cmp(dir->pair, (const lfs2_block_t[2]){0, 1}) == 0) { // oh no! we're writing too much to the superblock, // should we expand? lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); if (res < 0) { return res; } // do we have extra space? littlefs can't reclaim this space // by itself, so expand cautiously if ((lfs2_size_t)res < lfs2->cfg->block_count/2) { LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); int err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin, end); if (err && err != LFS2_ERR_NOSPC) { return err; } // welp, we tried, if we ran out of space there's not much // we can do, we'll error later if we've become frozen if (!err) { end = begin; } } #ifdef LFS2_MIGRATE } else if (lfs2->lfs21) { // do not proactively relocate blocks during migrations, this // can cause a number of failure states such: clobbering the // v1 superblock if we relocate root, and invalidating directory // pointers if we relocate the head of a directory. On top of // this, relocations increase the overall complexity of // lfs2_migration, which is already a delicate operation. #endif } else { // we're writing too much, time to relocate tired = true; goto relocate; } } // begin loop to commit compaction to blocks until a compact sticks while (true) { { // setup commit state struct lfs2_commit commit = { .block = dir->pair[1], .off = 0, .ptag = 0xffffffff, .crc = 0xffffffff, .begin = 0, .end = lfs2->cfg->block_size - 8, }; // erase block to write to int err = lfs2_bd_erase(lfs2, dir->pair[1]); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } // write out header dir->rev = lfs2_tole32(dir->rev); err = lfs2_dir_commitprog(lfs2, &commit, &dir->rev, sizeof(dir->rev)); dir->rev = lfs2_fromle32(dir->rev); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } // traverse the directory, this time writing out all unique tags err = lfs2_dir_traverse(lfs2, source, 0, 0xffffffff, attrs, attrcount, LFS2_MKTAG(0x400, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, 0), begin, end, -begin, lfs2_dir_commit_commit, &(struct lfs2_dir_commit_commit){ lfs2, &commit}); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } // commit tail, which may be new after last size check if (!lfs2_pair_isnull(dir->tail)) { lfs2_pair_tole32(dir->tail); err = lfs2_dir_commitattr(lfs2, &commit, LFS2_MKTAG(LFS2_TYPE_TAIL + dir->split, 0x3ff, 8), dir->tail); lfs2_pair_fromle32(dir->tail); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } } // bring over gstate? lfs2_gstate_t delta = {0}; if (!relocated) { lfs2_gstate_xor(&delta, &lfs2->gdisk); lfs2_gstate_xor(&delta, &lfs2->gstate); } lfs2_gstate_xor(&delta, &lfs2->gdelta); delta.tag &= ~LFS2_MKTAG(0, 0, 0x3ff); err = lfs2_dir_getgstate(lfs2, dir, &delta); if (err) { return err; } if (!lfs2_gstate_iszero(&delta)) { lfs2_gstate_tole32(&delta); err = lfs2_dir_commitattr(lfs2, &commit, LFS2_MKTAG(LFS2_TYPE_MOVESTATE, 0x3ff, sizeof(delta)), &delta); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } } // complete commit with crc err = lfs2_dir_commitcrc(lfs2, &commit); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } // successful compaction, swap dir pair to indicate most recent LFS2_ASSERT(commit.off % lfs2->cfg->prog_size == 0); lfs2_pair_swap(dir->pair); dir->count = end - begin; dir->off = commit.off; dir->etag = commit.ptag; // update gstate lfs2->gdelta = (lfs2_gstate_t){0}; if (!relocated) { lfs2->gdisk = lfs2->gstate; } } break; relocate: // commit was corrupted, drop caches and prepare to relocate block relocated = true; lfs2_cache_drop(lfs2, &lfs2->pcache); if (!tired) { LFS2_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); } // can't relocate superblock, filesystem is now frozen if (lfs2_pair_cmp(dir->pair, (const lfs2_block_t[2]){0, 1}) == 0) { LFS2_WARN("Superblock 0x%"PRIx32" has become unwritable", dir->pair[1]); return LFS2_ERR_NOSPC; } // relocate half of pair int err = lfs2_alloc(lfs2, &dir->pair[1]); if (err && (err != LFS2_ERR_NOSPC || !tired)) { return err; } tired = false; continue; } if (relocated) { // update references if we relocated LFS2_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " "-> {0x%"PRIx32", 0x%"PRIx32"}", oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); int err = lfs2_fs_relocate(lfs2, oldpair, dir->pair); if (err) { return err; } } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount) { // check for any inline files that aren't RAM backed and // forcefully evict them, needed for filesystem consistency for (lfs2_file_t *f = (lfs2_file_t*)lfs2->mlist; f; f = f->next) { if (dir != &f->m && lfs2_pair_cmp(f->m.pair, dir->pair) == 0 && f->type == LFS2_TYPE_REG && (f->flags & LFS2_F_INLINE) && f->ctz.size > lfs2->cfg->cache_size) { int err = lfs2_file_outline(lfs2, f); if (err) { return err; } err = lfs2_file_flush(lfs2, f); if (err) { return err; } } } // calculate changes to the directory lfs2_mdir_t olddir = *dir; bool hasdelete = false; for (int i = 0; i < attrcount; i++) { if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_CREATE) { dir->count += 1; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE) { LFS2_ASSERT(dir->count > 0); dir->count -= 1; hasdelete = true; } else if (lfs2_tag_type1(attrs[i].tag) == LFS2_TYPE_TAIL) { dir->tail[0] = ((lfs2_block_t*)attrs[i].buffer)[0]; dir->tail[1] = ((lfs2_block_t*)attrs[i].buffer)[1]; dir->split = (lfs2_tag_chunk(attrs[i].tag) & 1); lfs2_pair_fromle32(dir->tail); } } // should we actually drop the directory block? if (hasdelete && dir->count == 0) { lfs2_mdir_t pdir; int err = lfs2_fs_pred(lfs2, dir->pair, &pdir); if (err && err != LFS2_ERR_NOENT) { *dir = olddir; return err; } if (err != LFS2_ERR_NOENT && pdir.split) { err = lfs2_dir_drop(lfs2, &pdir, dir); if (err) { *dir = olddir; return err; } } } if (dir->erased || dir->count >= 0xff) { // try to commit struct lfs2_commit commit = { .block = dir->pair[0], .off = dir->off, .ptag = dir->etag, .crc = 0xffffffff, .begin = dir->off, .end = lfs2->cfg->block_size - 8, }; // traverse attrs that need to be written out lfs2_pair_tole32(dir->tail); int err = lfs2_dir_traverse(lfs2, dir, dir->off, dir->etag, attrs, attrcount, 0, 0, 0, 0, 0, lfs2_dir_commit_commit, &(struct lfs2_dir_commit_commit){ lfs2, &commit}); lfs2_pair_fromle32(dir->tail); if (err) { if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } *dir = olddir; return err; } // commit any global diffs if we have any lfs2_gstate_t delta = {0}; lfs2_gstate_xor(&delta, &lfs2->gstate); lfs2_gstate_xor(&delta, &lfs2->gdisk); lfs2_gstate_xor(&delta, &lfs2->gdelta); delta.tag &= ~LFS2_MKTAG(0, 0, 0x3ff); if (!lfs2_gstate_iszero(&delta)) { err = lfs2_dir_getgstate(lfs2, dir, &delta); if (err) { *dir = olddir; return err; } lfs2_gstate_tole32(&delta); err = lfs2_dir_commitattr(lfs2, &commit, LFS2_MKTAG(LFS2_TYPE_MOVESTATE, 0x3ff, sizeof(delta)), &delta); if (err) { if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } *dir = olddir; return err; } } // finalize commit with the crc err = lfs2_dir_commitcrc(lfs2, &commit); if (err) { if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } *dir = olddir; return err; } // successful commit, update dir LFS2_ASSERT(commit.off % lfs2->cfg->prog_size == 0); dir->off = commit.off; dir->etag = commit.ptag; // and update gstate lfs2->gdisk = lfs2->gstate; lfs2->gdelta = (lfs2_gstate_t){0}; } else { compact: // fall back to compaction lfs2_cache_drop(lfs2, &lfs2->pcache); int err = lfs2_dir_compact(lfs2, dir, attrs, attrcount, dir, 0, dir->count); if (err) { *dir = olddir; return err; } } // this complicated bit of logic is for fixing up any active // metadata-pairs that we may have affected // // note we have to make two passes since the mdir passed to // lfs2_dir_commit could also be in this list, and even then // we need to copy the pair so they don't get clobbered if we refetch // our mdir. for (struct lfs2_mlist *d = lfs2->mlist; d; d = d->next) { if (&d->m != dir && lfs2_pair_cmp(d->m.pair, olddir.pair) == 0) { d->m = *dir; for (int i = 0; i < attrcount; i++) { if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && d->id == lfs2_tag_id(attrs[i].tag)) { d->m.pair[0] = LFS2_BLOCK_NULL; d->m.pair[1] = LFS2_BLOCK_NULL; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && d->id > lfs2_tag_id(attrs[i].tag)) { d->id -= 1; if (d->type == LFS2_TYPE_DIR) { ((lfs2_dir_t*)d)->pos -= 1; } } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_CREATE && d->id >= lfs2_tag_id(attrs[i].tag)) { d->id += 1; if (d->type == LFS2_TYPE_DIR) { ((lfs2_dir_t*)d)->pos += 1; } } } } } for (struct lfs2_mlist *d = lfs2->mlist; d; d = d->next) { if (lfs2_pair_cmp(d->m.pair, olddir.pair) == 0) { while (d->id >= d->m.count && d->m.split) { // we split and id is on tail now d->id -= d->m.count; int err = lfs2_dir_fetch(lfs2, &d->m, d->m.tail); if (err) { return err; } } } } return 0; } #endif /// Top level directory operations /// #ifndef LFS2_READONLY static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { return err; } struct lfs2_mlist cwd; cwd.next = lfs2->mlist; uint16_t id; err = lfs2_dir_find(lfs2, &cwd.m, &path, &id); if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { return (err < 0) ? err : LFS2_ERR_EXIST; } // check that name fits lfs2_size_t nlen = strlen(path); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } // build up new directory lfs2_alloc_ack(lfs2); lfs2_mdir_t dir; err = lfs2_dir_alloc(lfs2, &dir); if (err) { return err; } // find end of list lfs2_mdir_t pred = cwd.m; while (pred.split) { err = lfs2_dir_fetch(lfs2, &pred, pred.tail); if (err) { return err; } } // setup dir lfs2_pair_tole32(pred.tail); err = lfs2_dir_commit(lfs2, &dir, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); lfs2_pair_fromle32(pred.tail); if (err) { return err; } // current block end of list? if (cwd.m.split) { // update tails, this creates a desync lfs2_fs_preporphans(lfs2, +1); // it's possible our predecessor has to be relocated, and if // our parent is our predecessor's predecessor, this could have // caused our parent to go out of date, fortunately we can hook // ourselves into littlefs to catch this cwd.type = 0; cwd.id = 0; lfs2->mlist = &cwd; lfs2_pair_tole32(dir.pair); err = lfs2_dir_commit(lfs2, &pred, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs2_pair_fromle32(dir.pair); if (err) { lfs2->mlist = cwd.next; return err; } lfs2->mlist = cwd.next; lfs2_fs_preporphans(lfs2, -1); } // now insert into our parent block lfs2_pair_tole32(dir.pair); err = lfs2_dir_commit(lfs2, &cwd.m, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_CREATE, id, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_DIR, id, nlen), path}, {LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, id, 8), dir.pair}, {LFS2_MKTAG_IF(!cwd.m.split, LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs2_pair_fromle32(dir.pair); if (err) { return err; } return 0; } #endif static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { lfs2_stag_t tag = lfs2_dir_find(lfs2, &dir->m, &path, NULL); if (tag < 0) { return tag; } if (lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { return LFS2_ERR_NOTDIR; } lfs2_block_t pair[2]; if (lfs2_tag_id(tag) == 0x3ff) { // handle root dir separately pair[0] = lfs2->root[0]; pair[1] = lfs2->root[1]; } else { // get dir pair from parent lfs2_stag_t res = lfs2_dir_get(lfs2, &dir->m, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, lfs2_tag_id(tag), 8), pair); if (res < 0) { return res; } lfs2_pair_fromle32(pair); } // fetch first pair int err = lfs2_dir_fetch(lfs2, &dir->m, pair); if (err) { return err; } // setup entry dir->head[0] = dir->m.pair[0]; dir->head[1] = dir->m.pair[1]; dir->id = 0; dir->pos = 0; // add to list of mdirs dir->type = LFS2_TYPE_DIR; lfs2_mlist_append(lfs2, (struct lfs2_mlist *)dir); return 0; } static int lfs2_dir_rawclose(lfs2_t *lfs2, lfs2_dir_t *dir) { // remove from list of mdirs lfs2_mlist_remove(lfs2, (struct lfs2_mlist *)dir); return 0; } static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { memset(info, 0, sizeof(*info)); // special offset for '.' and '..' if (dir->pos == 0) { info->type = LFS2_TYPE_DIR; strcpy(info->name, "."); dir->pos += 1; return true; } else if (dir->pos == 1) { info->type = LFS2_TYPE_DIR; strcpy(info->name, ".."); dir->pos += 1; return true; } while (true) { if (dir->id == dir->m.count) { if (!dir->m.split) { return false; } int err = lfs2_dir_fetch(lfs2, &dir->m, dir->m.tail); if (err) { return err; } dir->id = 0; } int err = lfs2_dir_getinfo(lfs2, &dir->m, dir->id, info); if (err && err != LFS2_ERR_NOENT) { return err; } dir->id += 1; if (err != LFS2_ERR_NOENT) { break; } } dir->pos += 1; return true; } static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { // simply walk from head dir int err = lfs2_dir_rawrewind(lfs2, dir); if (err) { return err; } // first two for ./.. dir->pos = lfs2_min(2, off); off -= dir->pos; // skip superblock entry dir->id = (off > 0 && lfs2_pair_cmp(dir->head, lfs2->root) == 0); while (off > 0) { int diff = lfs2_min(dir->m.count - dir->id, off); dir->id += diff; dir->pos += diff; off -= diff; if (dir->id == dir->m.count) { if (!dir->m.split) { return LFS2_ERR_INVAL; } err = lfs2_dir_fetch(lfs2, &dir->m, dir->m.tail); if (err) { return err; } dir->id = 0; } } return 0; } static lfs2_soff_t lfs2_dir_rawtell(lfs2_t *lfs2, lfs2_dir_t *dir) { (void)lfs2; return dir->pos; } static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir) { // reload the head dir int err = lfs2_dir_fetch(lfs2, &dir->m, dir->head); if (err) { return err; } dir->id = 0; dir->pos = 0; return 0; } /// File index list operations /// static int lfs2_ctz_index(lfs2_t *lfs2, lfs2_off_t *off) { lfs2_off_t size = *off; lfs2_off_t b = lfs2->cfg->block_size - 2*4; lfs2_off_t i = size / b; if (i == 0) { return 0; } i = (size - 4*(lfs2_popc(i-1)+2)) / b; *off = size - b*i - 4*lfs2_popc(i); return i; } static int lfs2_ctz_find(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_block_t head, lfs2_size_t size, lfs2_size_t pos, lfs2_block_t *block, lfs2_off_t *off) { if (size == 0) { *block = LFS2_BLOCK_NULL; *off = 0; return 0; } lfs2_off_t current = lfs2_ctz_index(lfs2, &(lfs2_off_t){size-1}); lfs2_off_t target = lfs2_ctz_index(lfs2, &pos); while (current > target) { lfs2_size_t skip = lfs2_min( lfs2_npw2(current-target+1) - 1, lfs2_ctz(current)); int err = lfs2_bd_read(lfs2, pcache, rcache, sizeof(head), head, 4*skip, &head, sizeof(head)); head = lfs2_fromle32(head); if (err) { return err; } current -= 1 << skip; } *block = head; *off = pos; return 0; } #ifndef LFS2_READONLY static int lfs2_ctz_extend(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_block_t head, lfs2_size_t size, lfs2_block_t *block, lfs2_off_t *off) { while (true) { // go ahead and grab a block lfs2_block_t nblock; int err = lfs2_alloc(lfs2, &nblock); if (err) { return err; } { err = lfs2_bd_erase(lfs2, nblock); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } if (size == 0) { *block = nblock; *off = 0; return 0; } lfs2_size_t noff = size - 1; lfs2_off_t index = lfs2_ctz_index(lfs2, &noff); noff = noff + 1; // just copy out the last block if it is incomplete if (noff != lfs2->cfg->block_size) { for (lfs2_off_t i = 0; i < noff; i++) { uint8_t data; err = lfs2_bd_read(lfs2, NULL, rcache, noff-i, head, i, &data, 1); if (err) { return err; } err = lfs2_bd_prog(lfs2, pcache, rcache, true, nblock, i, &data, 1); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } } *block = nblock; *off = noff; return 0; } // append block index += 1; lfs2_size_t skips = lfs2_ctz(index) + 1; lfs2_block_t nhead = head; for (lfs2_off_t i = 0; i < skips; i++) { nhead = lfs2_tole32(nhead); err = lfs2_bd_prog(lfs2, pcache, rcache, true, nblock, 4*i, &nhead, 4); nhead = lfs2_fromle32(nhead); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } if (i != skips-1) { err = lfs2_bd_read(lfs2, NULL, rcache, sizeof(nhead), nhead, 4*i, &nhead, sizeof(nhead)); nhead = lfs2_fromle32(nhead); if (err) { return err; } } } *block = nblock; *off = 4*skips; return 0; } relocate: LFS2_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs2_cache_drop(lfs2, pcache); } } #endif static int lfs2_ctz_traverse(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_block_t head, lfs2_size_t size, int (*cb)(void*, lfs2_block_t), void *data) { if (size == 0) { return 0; } lfs2_off_t index = lfs2_ctz_index(lfs2, &(lfs2_off_t){size-1}); while (true) { int err = cb(data, head); if (err) { return err; } if (index == 0) { return 0; } lfs2_block_t heads[2]; int count = 2 - (index & 1); err = lfs2_bd_read(lfs2, pcache, rcache, count*sizeof(head), head, 0, &heads, count*sizeof(head)); heads[0] = lfs2_fromle32(heads[0]); heads[1] = lfs2_fromle32(heads[1]); if (err) { return err; } for (int i = 0; i < count-1; i++) { err = cb(data, heads[i]); if (err) { return err; } } head = heads[count-1]; index -= count; } } /// Top level file operations /// static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags, const struct lfs2_file_config *cfg) { #ifndef LFS2_READONLY // deorphan if we haven't yet, needed at most once after poweron if ((flags & LFS2_O_WRONLY) == LFS2_O_WRONLY) { int err = lfs2_fs_forceconsistency(lfs2); if (err) { return err; } } #else LFS2_ASSERT((flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); #endif // setup simple file details int err; file->cfg = cfg; file->flags = flags; file->pos = 0; file->off = 0; file->cache.buffer = NULL; // allocate entry for file if it doesn't exist lfs2_stag_t tag = lfs2_dir_find(lfs2, &file->m, &path, &file->id); if (tag < 0 && !(tag == LFS2_ERR_NOENT && file->id != 0x3ff)) { err = tag; goto cleanup; } // get id, add to list of mdirs to catch update changes file->type = LFS2_TYPE_REG; lfs2_mlist_append(lfs2, (struct lfs2_mlist *)file); #ifdef LFS2_READONLY if (tag == LFS2_ERR_NOENT) { err = LFS2_ERR_NOENT; goto cleanup; #else if (tag == LFS2_ERR_NOENT) { if (!(flags & LFS2_O_CREAT)) { err = LFS2_ERR_NOENT; goto cleanup; } // check that name fits lfs2_size_t nlen = strlen(path); if (nlen > lfs2->name_max) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; } // get next slot and create entry to remember name err = lfs2_dir_commit(lfs2, &file->m, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_CREATE, file->id, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_REG, file->id, nlen), path}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), NULL})); if (err) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; } tag = LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, 0); } else if (flags & LFS2_O_EXCL) { err = LFS2_ERR_EXIST; goto cleanup; #endif } else if (lfs2_tag_type3(tag) != LFS2_TYPE_REG) { err = LFS2_ERR_ISDIR; goto cleanup; #ifndef LFS2_READONLY } else if (flags & LFS2_O_TRUNC) { // truncate if requested tag = LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0); file->flags |= LFS2_F_DIRTY; #endif } else { // try to load what's on disk, if it's inlined we'll fix it later tag = lfs2_dir_get(lfs2, &file->m, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, file->id, 8), &file->ctz); if (tag < 0) { err = tag; goto cleanup; } lfs2_ctz_fromle32(&file->ctz); } // fetch attrs for (unsigned i = 0; i < file->cfg->attr_count; i++) { // if opened for read / read-write operations if ((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY) { lfs2_stag_t res = lfs2_dir_get(lfs2, &file->m, LFS2_MKTAG(0x7ff, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_USERATTR + file->cfg->attrs[i].type, file->id, file->cfg->attrs[i].size), file->cfg->attrs[i].buffer); if (res < 0 && res != LFS2_ERR_NOENT) { err = res; goto cleanup; } } #ifndef LFS2_READONLY // if opened for write / read-write operations if ((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY) { if (file->cfg->attrs[i].size > lfs2->attr_max) { err = LFS2_ERR_NOSPC; goto cleanup; } file->flags |= LFS2_F_DIRTY; } #endif } // allocate buffer if needed if (file->cfg->buffer) { file->cache.buffer = file->cfg->buffer; } else { file->cache.buffer = lfs2_malloc(lfs2->cfg->cache_size); if (!file->cache.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } } // zero to avoid information leak lfs2_cache_zero(lfs2, &file->cache); if (lfs2_tag_type3(tag) == LFS2_TYPE_INLINESTRUCT) { // load inline files file->ctz.head = LFS2_BLOCK_INLINE; file->ctz.size = lfs2_tag_size(tag); file->flags |= LFS2_F_INLINE; file->cache.block = file->ctz.head; file->cache.off = 0; file->cache.size = lfs2->cfg->cache_size; // don't always read (may be new/trunc file) if (file->ctz.size > 0) { lfs2_stag_t res = lfs2_dir_get(lfs2, &file->m, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, file->id, lfs2_min(file->cache.size, 0x3fe)), file->cache.buffer); if (res < 0) { err = res; goto cleanup; } } } return 0; cleanup: // clean up lingering resources #ifndef LFS2_READONLY file->flags |= LFS2_F_ERRED; #endif lfs2_file_rawclose(lfs2, file); return err; } static int lfs2_file_rawopen(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { static const struct lfs2_file_config defaults = {0}; int err = lfs2_file_rawopencfg(lfs2, file, path, flags, &defaults); return err; } static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY int err = lfs2_file_rawsync(lfs2, file); #else int err = 0; #endif // remove from list of mdirs lfs2_mlist_remove(lfs2, (struct lfs2_mlist*)file); // clean up memory if (!file->cfg->buffer) { lfs2_free(file->cache.buffer); } return err; } #ifndef LFS2_READONLY static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { while (true) { // just relocate what exists into new block lfs2_block_t nblock; int err = lfs2_alloc(lfs2, &nblock); if (err) { return err; } err = lfs2_bd_erase(lfs2, nblock); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } // either read from dirty cache or disk for (lfs2_off_t i = 0; i < file->off; i++) { uint8_t data; if (file->flags & LFS2_F_INLINE) { err = lfs2_dir_getread(lfs2, &file->m, // note we evict inline files before they can be dirty NULL, &file->cache, file->off-i, LFS2_MKTAG(0xfff, 0x1ff, 0), LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), i, &data, 1); if (err) { return err; } } else { err = lfs2_bd_read(lfs2, &file->cache, &lfs2->rcache, file->off-i, file->block, i, &data, 1); if (err) { return err; } } err = lfs2_bd_prog(lfs2, &lfs2->pcache, &lfs2->rcache, true, nblock, i, &data, 1); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } } // copy over new state of file memcpy(file->cache.buffer, lfs2->pcache.buffer, lfs2->cfg->cache_size); file->cache.block = lfs2->pcache.block; file->cache.off = lfs2->pcache.off; file->cache.size = lfs2->pcache.size; lfs2_cache_zero(lfs2, &lfs2->pcache); file->block = nblock; file->flags |= LFS2_F_WRITING; return 0; relocate: LFS2_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs2_cache_drop(lfs2, &lfs2->pcache); } } #endif #ifndef LFS2_READONLY static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->off = file->pos; lfs2_alloc_ack(lfs2); int err = lfs2_file_relocate(lfs2, file); if (err) { return err; } file->flags &= ~LFS2_F_INLINE; return 0; } #endif #ifndef LFS2_READONLY static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_READING) { if (!(file->flags & LFS2_F_INLINE)) { lfs2_cache_drop(lfs2, &file->cache); } file->flags &= ~LFS2_F_READING; } if (file->flags & LFS2_F_WRITING) { lfs2_off_t pos = file->pos; if (!(file->flags & LFS2_F_INLINE)) { // copy over anything after current branch lfs2_file_t orig = { .ctz.head = file->ctz.head, .ctz.size = file->ctz.size, .flags = LFS2_O_RDONLY, .pos = file->pos, .cache = lfs2->rcache, }; lfs2_cache_drop(lfs2, &lfs2->rcache); while (file->pos < file->ctz.size) { // copy over a byte at a time, leave it up to caching // to make this efficient uint8_t data; lfs2_ssize_t res = lfs2_file_rawread(lfs2, &orig, &data, 1); if (res < 0) { return res; } res = lfs2_file_rawwrite(lfs2, file, &data, 1); if (res < 0) { return res; } // keep our reference to the rcache in sync if (lfs2->rcache.block != LFS2_BLOCK_NULL) { lfs2_cache_drop(lfs2, &orig.cache); lfs2_cache_drop(lfs2, &lfs2->rcache); } } // write out what we have while (true) { int err = lfs2_bd_flush(lfs2, &file->cache, &lfs2->rcache, true); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } return err; } break; relocate: LFS2_DEBUG("Bad block at 0x%"PRIx32, file->block); err = lfs2_file_relocate(lfs2, file); if (err) { return err; } } } else { file->pos = lfs2_max(file->pos, file->ctz.size); } // actual file updates file->ctz.head = file->block; file->ctz.size = file->pos; file->flags &= ~LFS2_F_WRITING; file->flags |= LFS2_F_DIRTY; file->pos = pos; } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_ERRED) { // it's not safe to do anything if our file errored return 0; } int err = lfs2_file_flush(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; return err; } if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { // update dir entry uint16_t type; const void *buffer; lfs2_size_t size; struct lfs2_ctz ctz; if (file->flags & LFS2_F_INLINE) { // inline the whole file type = LFS2_TYPE_INLINESTRUCT; buffer = file->cache.buffer; size = file->ctz.size; } else { // update the ctz reference type = LFS2_TYPE_CTZSTRUCT; // copy ctz so alloc will work during a relocate ctz = file->ctz; lfs2_ctz_tole32(&ctz); buffer = &ctz; size = sizeof(ctz); } // commit file data and attributes err = lfs2_dir_commit(lfs2, &file->m, LFS2_MKATTRS( {LFS2_MKTAG(type, file->id, size), buffer}, {LFS2_MKTAG(LFS2_FROM_USERATTRS, file->id, file->cfg->attr_count), file->cfg->attrs})); if (err) { file->flags |= LFS2_F_ERRED; return err; } file->flags &= ~LFS2_F_DIRTY; } return 0; } #endif static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); uint8_t *data = buffer; lfs2_size_t nsize = size; #ifndef LFS2_READONLY if (file->flags & LFS2_F_WRITING) { // flush out any writes int err = lfs2_file_flush(lfs2, file); if (err) { return err; } } #endif if (file->pos >= file->ctz.size) { // eof if past end return 0; } size = lfs2_min(size, file->ctz.size - file->pos); nsize = size; while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS2_F_READING) || file->off == lfs2->cfg->block_size) { if (!(file->flags & LFS2_F_INLINE)) { int err = lfs2_ctz_find(lfs2, NULL, &file->cache, file->ctz.head, file->ctz.size, file->pos, &file->block, &file->off); if (err) { return err; } } else { file->block = LFS2_BLOCK_INLINE; file->off = file->pos; } file->flags |= LFS2_F_READING; } // read as much as we can in current block lfs2_size_t diff = lfs2_min(nsize, lfs2->cfg->block_size - file->off); if (file->flags & LFS2_F_INLINE) { int err = lfs2_dir_getread(lfs2, &file->m, NULL, &file->cache, lfs2->cfg->block_size, LFS2_MKTAG(0xfff, 0x1ff, 0), LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), file->off, data, diff); if (err) { return err; } } else { int err = lfs2_bd_read(lfs2, NULL, &file->cache, lfs2->cfg->block_size, file->block, file->off, data, diff); if (err) { return err; } } file->pos += diff; file->off += diff; data += diff; nsize -= diff; } return size; } #ifndef LFS2_READONLY static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); const uint8_t *data = buffer; lfs2_size_t nsize = size; if (file->flags & LFS2_F_READING) { // drop any reads int err = lfs2_file_flush(lfs2, file); if (err) { return err; } } if ((file->flags & LFS2_O_APPEND) && file->pos < file->ctz.size) { file->pos = file->ctz.size; } if (file->pos + size > lfs2->file_max) { // Larger than file limit? return LFS2_ERR_FBIG; } if (!(file->flags & LFS2_F_WRITING) && file->pos > file->ctz.size) { // fill with zeros lfs2_off_t pos = file->pos; file->pos = file->ctz.size; while (file->pos < pos) { lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { return res; } } } if ((file->flags & LFS2_F_INLINE) && lfs2_max(file->pos+nsize, file->ctz.size) > lfs2_min(0x3fe, lfs2_min( lfs2->cfg->cache_size, lfs2->cfg->block_size/8))) { // inline file doesn't fit anymore int err = lfs2_file_outline(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; return err; } } while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS2_F_WRITING) || file->off == lfs2->cfg->block_size) { if (!(file->flags & LFS2_F_INLINE)) { if (!(file->flags & LFS2_F_WRITING) && file->pos > 0) { // find out which block we're extending from int err = lfs2_ctz_find(lfs2, NULL, &file->cache, file->ctz.head, file->ctz.size, file->pos-1, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; return err; } // mark cache as dirty since we may have read data into it lfs2_cache_zero(lfs2, &file->cache); } // extend file with new blocks lfs2_alloc_ack(lfs2); int err = lfs2_ctz_extend(lfs2, &file->cache, &lfs2->rcache, file->block, file->pos, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; return err; } } else { file->block = LFS2_BLOCK_INLINE; file->off = file->pos; } file->flags |= LFS2_F_WRITING; } // program as much as we can in current block lfs2_size_t diff = lfs2_min(nsize, lfs2->cfg->block_size - file->off); while (true) { int err = lfs2_bd_prog(lfs2, &file->cache, &lfs2->rcache, true, file->block, file->off, data, diff); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; } file->flags |= LFS2_F_ERRED; return err; } break; relocate: err = lfs2_file_relocate(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; return err; } } file->pos += diff; file->off += diff; data += diff; nsize -= diff; lfs2_alloc_ack(lfs2); } file->flags &= ~LFS2_F_ERRED; return size; } #endif static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { #ifndef LFS2_READONLY // write out everything beforehand, may be noop if rdonly int err = lfs2_file_flush(lfs2, file); if (err) { return err; } #endif // find new pos lfs2_off_t npos = file->pos; if (whence == LFS2_SEEK_SET) { npos = off; } else if (whence == LFS2_SEEK_CUR) { npos = file->pos + off; } else if (whence == LFS2_SEEK_END) { npos = file->ctz.size + off; } if (npos > lfs2->file_max) { // file position out of range return LFS2_ERR_INVAL; } // update pos file->pos = npos; return npos; } #ifndef LFS2_READONLY static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); if (size > LFS2_FILE_MAX) { return LFS2_ERR_INVAL; } lfs2_off_t pos = file->pos; lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file); if (size < oldsize) { // need to flush since directly changing metadata int err = lfs2_file_flush(lfs2, file); if (err) { return err; } // lookup new head in ctz skip list err = lfs2_ctz_find(lfs2, NULL, &file->cache, file->ctz.head, file->ctz.size, size, &file->block, &file->off); if (err) { return err; } file->ctz.head = file->block; file->ctz.size = size; file->flags |= LFS2_F_DIRTY | LFS2_F_READING; } else if (size > oldsize) { // flush+seek if not already at end if (file->pos != oldsize) { lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END); if (res < 0) { return (int)res; } } // fill with zeros while (file->pos < size) { lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { return (int)res; } } } // restore pos lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, pos, LFS2_SEEK_SET); if (res < 0) { return (int)res; } return 0; } #endif static lfs2_soff_t lfs2_file_rawtell(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; return file->pos; } static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { return (int)res; } return 0; } static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; #ifndef LFS2_READONLY if (file->flags & LFS2_F_WRITING) { return lfs2_max(file->pos, file->ctz.size); } #endif return file->ctz.size; } /// General fs operations /// static int lfs2_rawstat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { return (int)tag; } return lfs2_dir_getinfo(lfs2, &cwd, lfs2_tag_id(tag), info); } #ifndef LFS2_READONLY static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { return err; } lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0 || lfs2_tag_id(tag) == 0x3ff) { return (tag < 0) ? (int)tag : LFS2_ERR_INVAL; } struct lfs2_mlist dir; dir.next = lfs2->mlist; if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { // must be empty before removal lfs2_block_t pair[2]; lfs2_stag_t res = lfs2_dir_get(lfs2, &cwd, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, lfs2_tag_id(tag), 8), pair); if (res < 0) { return (int)res; } lfs2_pair_fromle32(pair); err = lfs2_dir_fetch(lfs2, &dir.m, pair); if (err) { return err; } if (dir.m.count > 0 || dir.m.split) { return LFS2_ERR_NOTEMPTY; } // mark fs as orphaned lfs2_fs_preporphans(lfs2, +1); // I know it's crazy but yes, dir can be changed by our parent's // commit (if predecessor is child) dir.type = 0; dir.id = 0; lfs2->mlist = &dir; } // delete the entry err = lfs2_dir_commit(lfs2, &cwd, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(tag), 0), NULL})); if (err) { lfs2->mlist = dir.next; return err; } lfs2->mlist = dir.next; if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { // fix orphan lfs2_fs_preporphans(lfs2, -1); err = lfs2_fs_pred(lfs2, dir.m.pair, &cwd); if (err) { return err; } err = lfs2_dir_drop(lfs2, &cwd, &dir.m); if (err) { return err; } } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { return err; } // find old entry lfs2_mdir_t oldcwd; lfs2_stag_t oldtag = lfs2_dir_find(lfs2, &oldcwd, &oldpath, NULL); if (oldtag < 0 || lfs2_tag_id(oldtag) == 0x3ff) { return (oldtag < 0) ? (int)oldtag : LFS2_ERR_INVAL; } // find new entry lfs2_mdir_t newcwd; uint16_t newid; lfs2_stag_t prevtag = lfs2_dir_find(lfs2, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs2_tag_id(prevtag) == 0x3ff) && !(prevtag == LFS2_ERR_NOENT && newid != 0x3ff)) { return (prevtag < 0) ? (int)prevtag : LFS2_ERR_INVAL; } // if we're in the same pair there's a few special cases... bool samepair = (lfs2_pair_cmp(oldcwd.pair, newcwd.pair) == 0); uint16_t newoldid = lfs2_tag_id(oldtag); struct lfs2_mlist prevdir; prevdir.next = lfs2->mlist; if (prevtag == LFS2_ERR_NOENT) { // check that name fits lfs2_size_t nlen = strlen(newpath); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } // there is a small chance we are being renamed in the same // directory/ to an id less than our old id, the global update // to handle this is a bit messy if (samepair && newid <= newoldid) { newoldid += 1; } } else if (lfs2_tag_type3(prevtag) != lfs2_tag_type3(oldtag)) { return LFS2_ERR_ISDIR; } else if (samepair && newid == newoldid) { // we're renaming to ourselves?? return 0; } else if (lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { // must be empty before removal lfs2_block_t prevpair[2]; lfs2_stag_t res = lfs2_dir_get(lfs2, &newcwd, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, newid, 8), prevpair); if (res < 0) { return (int)res; } lfs2_pair_fromle32(prevpair); // must be empty before removal err = lfs2_dir_fetch(lfs2, &prevdir.m, prevpair); if (err) { return err; } if (prevdir.m.count > 0 || prevdir.m.split) { return LFS2_ERR_NOTEMPTY; } // mark fs as orphaned lfs2_fs_preporphans(lfs2, +1); // I know it's crazy but yes, dir can be changed by our parent's // commit (if predecessor is child) prevdir.type = 0; prevdir.id = 0; lfs2->mlist = &prevdir; } if (!samepair) { lfs2_fs_prepmove(lfs2, newoldid, oldcwd.pair); } // move over all attributes err = lfs2_dir_commit(lfs2, &newcwd, LFS2_MKATTRS( {LFS2_MKTAG_IF(prevtag != LFS2_ERR_NOENT, LFS2_TYPE_DELETE, newid, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0), NULL}, {LFS2_MKTAG(lfs2_tag_type3(oldtag), newid, strlen(newpath)), newpath}, {LFS2_MKTAG(LFS2_FROM_MOVE, newid, lfs2_tag_id(oldtag)), &oldcwd}, {LFS2_MKTAG_IF(samepair, LFS2_TYPE_DELETE, newoldid, 0), NULL})); if (err) { lfs2->mlist = prevdir.next; return err; } // let commit clean up after move (if we're different! otherwise move // logic already fixed it for us) if (!samepair && lfs2_gstate_hasmove(&lfs2->gstate)) { // prep gstate and delete move id lfs2_fs_prepmove(lfs2, 0x3ff, NULL); err = lfs2_dir_commit(lfs2, &oldcwd, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(oldtag), 0), NULL})); if (err) { lfs2->mlist = prevdir.next; return err; } } lfs2->mlist = prevdir.next; if (prevtag != LFS2_ERR_NOENT && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { // fix orphan lfs2_fs_preporphans(lfs2, -1); err = lfs2_fs_pred(lfs2, prevdir.m.pair, &newcwd); if (err) { return err; } err = lfs2_dir_drop(lfs2, &newcwd, &prevdir.m); if (err) { return err; } } return 0; } #endif static lfs2_ssize_t lfs2_rawgetattr(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { return tag; } uint16_t id = lfs2_tag_id(tag); if (id == 0x3ff) { // special case for root id = 0; int err = lfs2_dir_fetch(lfs2, &cwd, lfs2->root); if (err) { return err; } } tag = lfs2_dir_get(lfs2, &cwd, LFS2_MKTAG(0x7ff, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_USERATTR + type, id, lfs2_min(size, lfs2->attr_max)), buffer); if (tag < 0) { if (tag == LFS2_ERR_NOENT) { return LFS2_ERR_NOATTR; } return tag; } return lfs2_tag_size(tag); } #ifndef LFS2_READONLY static int lfs2_commitattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { return tag; } uint16_t id = lfs2_tag_id(tag); if (id == 0x3ff) { // special case for root id = 0; int err = lfs2_dir_fetch(lfs2, &cwd, lfs2->root); if (err) { return err; } } return lfs2_dir_commit(lfs2, &cwd, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_USERATTR + type, id, size), buffer})); } #endif #ifndef LFS2_READONLY static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { if (size > lfs2->attr_max) { return LFS2_ERR_NOSPC; } return lfs2_commitattr(lfs2, path, type, buffer, size); } #endif #ifndef LFS2_READONLY static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { return lfs2_commitattr(lfs2, path, type, NULL, 0x3ff); } #endif /// Filesystem operations /// static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; int err = 0; // validate that the lfs2-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS2_ASSERT(lfs2->cfg->read_size != 0); LFS2_ASSERT(lfs2->cfg->prog_size != 0); LFS2_ASSERT(lfs2->cfg->cache_size != 0); // check that block size is a multiple of cache size is a multiple // of prog and read sizes LFS2_ASSERT(lfs2->cfg->cache_size % lfs2->cfg->read_size == 0); LFS2_ASSERT(lfs2->cfg->cache_size % lfs2->cfg->prog_size == 0); LFS2_ASSERT(lfs2->cfg->block_size % lfs2->cfg->cache_size == 0); // check that the block size is large enough to fit ctz pointers LFS2_ASSERT(4*lfs2_npw2(0xffffffff / (lfs2->cfg->block_size-2*4)) <= lfs2->cfg->block_size); // block_cycles = 0 is no longer supported. // // block_cycles is the number of erase cycles before littlefs evicts // metadata logs as a part of wear leveling. Suggested values are in the // range of 100-1000, or set block_cycles to -1 to disable block-level // wear-leveling. LFS2_ASSERT(lfs2->cfg->block_cycles != 0); // setup read cache if (lfs2->cfg->read_buffer) { lfs2->rcache.buffer = lfs2->cfg->read_buffer; } else { lfs2->rcache.buffer = lfs2_malloc(lfs2->cfg->cache_size); if (!lfs2->rcache.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } } // setup program cache if (lfs2->cfg->prog_buffer) { lfs2->pcache.buffer = lfs2->cfg->prog_buffer; } else { lfs2->pcache.buffer = lfs2_malloc(lfs2->cfg->cache_size); if (!lfs2->pcache.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } } // zero to avoid information leaks lfs2_cache_zero(lfs2, &lfs2->rcache); lfs2_cache_zero(lfs2, &lfs2->pcache); // setup lookahead, must be multiple of 64-bits, 32-bit aligned LFS2_ASSERT(lfs2->cfg->lookahead_size > 0); LFS2_ASSERT(lfs2->cfg->lookahead_size % 8 == 0 && (uintptr_t)lfs2->cfg->lookahead_buffer % 4 == 0); if (lfs2->cfg->lookahead_buffer) { lfs2->free.buffer = lfs2->cfg->lookahead_buffer; } else { lfs2->free.buffer = lfs2_malloc(lfs2->cfg->lookahead_size); if (!lfs2->free.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } } // check that the size limits are sane LFS2_ASSERT(lfs2->cfg->name_max <= LFS2_NAME_MAX); lfs2->name_max = lfs2->cfg->name_max; if (!lfs2->name_max) { lfs2->name_max = LFS2_NAME_MAX; } LFS2_ASSERT(lfs2->cfg->file_max <= LFS2_FILE_MAX); lfs2->file_max = lfs2->cfg->file_max; if (!lfs2->file_max) { lfs2->file_max = LFS2_FILE_MAX; } LFS2_ASSERT(lfs2->cfg->attr_max <= LFS2_ATTR_MAX); lfs2->attr_max = lfs2->cfg->attr_max; if (!lfs2->attr_max) { lfs2->attr_max = LFS2_ATTR_MAX; } // setup default state lfs2->root[0] = LFS2_BLOCK_NULL; lfs2->root[1] = LFS2_BLOCK_NULL; lfs2->mlist = NULL; lfs2->seed = 0; lfs2->gdisk = (lfs2_gstate_t){0}; lfs2->gstate = (lfs2_gstate_t){0}; lfs2->gdelta = (lfs2_gstate_t){0}; #ifdef LFS2_MIGRATE lfs2->lfs21 = NULL; #endif return 0; cleanup: lfs2_deinit(lfs2); return err; } static int lfs2_deinit(lfs2_t *lfs2) { // free allocated memory if (!lfs2->cfg->read_buffer) { lfs2_free(lfs2->rcache.buffer); } if (!lfs2->cfg->prog_buffer) { lfs2_free(lfs2->pcache.buffer); } if (!lfs2->cfg->lookahead_buffer) { lfs2_free(lfs2->free.buffer); } return 0; } #ifndef LFS2_READONLY static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; { err = lfs2_init(lfs2, cfg); if (err) { return err; } // create free lookahead memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); lfs2->free.off = 0; lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->cfg->block_count); lfs2->free.i = 0; lfs2_alloc_ack(lfs2); // create root dir lfs2_mdir_t root; err = lfs2_dir_alloc(lfs2, &root); if (err) { goto cleanup; } // write one superblock lfs2_superblock_t superblock = { .version = LFS2_DISK_VERSION, .block_size = lfs2->cfg->block_size, .block_count = lfs2->cfg->block_count, .name_max = lfs2->name_max, .file_max = lfs2->file_max, .attr_max = lfs2->attr_max, }; lfs2_superblock_tole32(&superblock); err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); if (err) { goto cleanup; } // sanity check that fetch works err = lfs2_dir_fetch(lfs2, &root, (const lfs2_block_t[2]){0, 1}); if (err) { goto cleanup; } // force compaction to prevent accidentally mounting any // older version of littlefs that may live on disk root.erased = false; err = lfs2_dir_commit(lfs2, &root, NULL, 0); if (err) { goto cleanup; } } cleanup: lfs2_deinit(lfs2); return err; } #endif static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = lfs2_init(lfs2, cfg); if (err) { return err; } // scan directory blocks for superblock and any global updates lfs2_mdir_t dir = {.tail = {0, 1}}; lfs2_block_t cycle = 0; while (!lfs2_pair_isnull(dir.tail)) { if (cycle >= lfs2->cfg->block_count/2) { // loop detected err = LFS2_ERR_CORRUPT; goto cleanup; } cycle += 1; // fetch next block in tail list lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, &dir, dir.tail, LFS2_MKTAG(0x7ff, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), NULL, lfs2_dir_find_match, &(struct lfs2_dir_find_match){ lfs2, "littlefs", 8}); if (tag < 0) { err = tag; goto cleanup; } // has superblock? if (tag && !lfs2_tag_isdelete(tag)) { // update root lfs2->root[0] = dir.pair[0]; lfs2->root[1] = dir.pair[1]; // grab superblock lfs2_superblock_t superblock; tag = lfs2_dir_get(lfs2, &dir, LFS2_MKTAG(0x7ff, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock); if (tag < 0) { err = tag; goto cleanup; } lfs2_superblock_fromle32(&superblock); // check version uint16_t major_version = (0xffff & (superblock.version >> 16)); uint16_t minor_version = (0xffff & (superblock.version >> 0)); if ((major_version != LFS2_DISK_VERSION_MAJOR || minor_version > LFS2_DISK_VERSION_MINOR)) { LFS2_ERROR("Invalid version v%"PRIu16".%"PRIu16, major_version, minor_version); err = LFS2_ERR_INVAL; goto cleanup; } // check superblock configuration if (superblock.name_max) { if (superblock.name_max > lfs2->name_max) { LFS2_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", superblock.name_max, lfs2->name_max); err = LFS2_ERR_INVAL; goto cleanup; } lfs2->name_max = superblock.name_max; } if (superblock.file_max) { if (superblock.file_max > lfs2->file_max) { LFS2_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", superblock.file_max, lfs2->file_max); err = LFS2_ERR_INVAL; goto cleanup; } lfs2->file_max = superblock.file_max; } if (superblock.attr_max) { if (superblock.attr_max > lfs2->attr_max) { LFS2_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", superblock.attr_max, lfs2->attr_max); err = LFS2_ERR_INVAL; goto cleanup; } lfs2->attr_max = superblock.attr_max; } } // has gstate? err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gstate); if (err) { goto cleanup; } } // found superblock? if (lfs2_pair_isnull(lfs2->root)) { err = LFS2_ERR_INVAL; goto cleanup; } // update littlefs with gstate if (!lfs2_gstate_iszero(&lfs2->gstate)) { LFS2_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, lfs2->gstate.tag, lfs2->gstate.pair[0], lfs2->gstate.pair[1]); } lfs2->gstate.tag += !lfs2_tag_isvalid(lfs2->gstate.tag); lfs2->gdisk = lfs2->gstate; // setup free lookahead, to distribute allocations uniformly across // boots, we start the allocator at a random location lfs2->free.off = lfs2->seed % lfs2->cfg->block_count; lfs2_alloc_drop(lfs2); return 0; cleanup: lfs2_rawunmount(lfs2); return err; } static int lfs2_rawunmount(lfs2_t *lfs2) { return lfs2_deinit(lfs2); } /// Filesystem filesystem operations /// int lfs2_fs_rawtraverse(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { // iterate over metadata pairs lfs2_mdir_t dir = {.tail = {0, 1}}; #ifdef LFS2_MIGRATE // also consider v1 blocks during migration if (lfs2->lfs21) { int err = lfs21_traverse(lfs2, cb, data); if (err) { return err; } dir.tail[0] = lfs2->root[0]; dir.tail[1] = lfs2->root[1]; } #endif lfs2_block_t cycle = 0; while (!lfs2_pair_isnull(dir.tail)) { if (cycle >= lfs2->cfg->block_count/2) { // loop detected return LFS2_ERR_CORRUPT; } cycle += 1; for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); if (err) { return err; } } // iterate through ids in directory int err = lfs2_dir_fetch(lfs2, &dir, dir.tail); if (err) { return err; } for (uint16_t id = 0; id < dir.count; id++) { struct lfs2_ctz ctz; lfs2_stag_t tag = lfs2_dir_get(lfs2, &dir, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, id, sizeof(ctz)), &ctz); if (tag < 0) { if (tag == LFS2_ERR_NOENT) { continue; } return tag; } lfs2_ctz_fromle32(&ctz); if (lfs2_tag_type3(tag) == LFS2_TYPE_CTZSTRUCT) { err = lfs2_ctz_traverse(lfs2, NULL, &lfs2->rcache, ctz.head, ctz.size, cb, data); if (err) { return err; } } else if (includeorphans && lfs2_tag_type3(tag) == LFS2_TYPE_DIRSTRUCT) { for (int i = 0; i < 2; i++) { err = cb(data, (&ctz.head)[i]); if (err) { return err; } } } } } #ifndef LFS2_READONLY // iterate over any open files for (lfs2_file_t *f = (lfs2_file_t*)lfs2->mlist; f; f = f->next) { if (f->type != LFS2_TYPE_REG) { continue; } if ((f->flags & LFS2_F_DIRTY) && !(f->flags & LFS2_F_INLINE)) { int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { return err; } } if ((f->flags & LFS2_F_WRITING) && !(f->flags & LFS2_F_INLINE)) { int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->block, f->pos, cb, data); if (err) { return err; } } } #endif return 0; } #ifndef LFS2_READONLY static int lfs2_fs_pred(lfs2_t *lfs2, const lfs2_block_t pair[2], lfs2_mdir_t *pdir) { // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; lfs2_block_t cycle = 0; while (!lfs2_pair_isnull(pdir->tail)) { if (cycle >= lfs2->cfg->block_count/2) { // loop detected return LFS2_ERR_CORRUPT; } cycle += 1; if (lfs2_pair_cmp(pdir->tail, pair) == 0) { return 0; } int err = lfs2_dir_fetch(lfs2, pdir, pdir->tail); if (err) { return err; } } return LFS2_ERR_NOENT; } #endif #ifndef LFS2_READONLY struct lfs2_fs_parent_match { lfs2_t *lfs2; const lfs2_block_t pair[2]; }; #endif #ifndef LFS2_READONLY static int lfs2_fs_parent_match(void *data, lfs2_tag_t tag, const void *buffer) { struct lfs2_fs_parent_match *find = data; lfs2_t *lfs2 = find->lfs2; const struct lfs2_diskoff *disk = buffer; (void)tag; lfs2_block_t child[2]; int err = lfs2_bd_read(lfs2, &lfs2->pcache, &lfs2->rcache, lfs2->cfg->block_size, disk->block, disk->off, &child, sizeof(child)); if (err) { return err; } lfs2_pair_fromle32(child); return (lfs2_pair_cmp(child, find->pair) == 0) ? LFS2_CMP_EQ : LFS2_CMP_LT; } #endif #ifndef LFS2_READONLY static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], lfs2_mdir_t *parent) { // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; lfs2_block_t cycle = 0; while (!lfs2_pair_isnull(parent->tail)) { if (cycle >= lfs2->cfg->block_count/2) { // loop detected return LFS2_ERR_CORRUPT; } cycle += 1; lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, parent, parent->tail, LFS2_MKTAG(0x7ff, 0, 0x3ff), LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 0, 8), NULL, lfs2_fs_parent_match, &(struct lfs2_fs_parent_match){ lfs2, {pair[0], pair[1]}}); if (tag && tag != LFS2_ERR_NOENT) { return tag; } } return LFS2_ERR_NOENT; } #endif #ifndef LFS2_READONLY static int lfs2_fs_relocate(lfs2_t *lfs2, const lfs2_block_t oldpair[2], lfs2_block_t newpair[2]) { // update internal root if (lfs2_pair_cmp(oldpair, lfs2->root) == 0) { lfs2->root[0] = newpair[0]; lfs2->root[1] = newpair[1]; } // update internally tracked dirs for (struct lfs2_mlist *d = lfs2->mlist; d; d = d->next) { if (lfs2_pair_cmp(oldpair, d->m.pair) == 0) { d->m.pair[0] = newpair[0]; d->m.pair[1] = newpair[1]; } if (d->type == LFS2_TYPE_DIR && lfs2_pair_cmp(oldpair, ((lfs2_dir_t*)d)->head) == 0) { ((lfs2_dir_t*)d)->head[0] = newpair[0]; ((lfs2_dir_t*)d)->head[1] = newpair[1]; } } // find parent lfs2_mdir_t parent; lfs2_stag_t tag = lfs2_fs_parent(lfs2, oldpair, &parent); if (tag < 0 && tag != LFS2_ERR_NOENT) { return tag; } if (tag != LFS2_ERR_NOENT) { // update disk, this creates a desync lfs2_fs_preporphans(lfs2, +1); // fix pending move in this pair? this looks like an optimization but // is in fact _required_ since relocating may outdate the move. uint16_t moveid = 0x3ff; if (lfs2_gstate_hasmovehere(&lfs2->gstate, parent.pair)) { moveid = lfs2_tag_id(lfs2->gstate.tag); LFS2_DEBUG("Fixing move while relocating " "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", parent.pair[0], parent.pair[1], moveid); lfs2_fs_prepmove(lfs2, 0x3ff, NULL); if (moveid < lfs2_tag_id(tag)) { tag -= LFS2_MKTAG(0, 1, 0); } } lfs2_pair_tole32(newpair); int err = lfs2_dir_commit(lfs2, &parent, LFS2_MKATTRS( {LFS2_MKTAG_IF(moveid != 0x3ff, LFS2_TYPE_DELETE, moveid, 0), NULL}, {tag, newpair})); lfs2_pair_fromle32(newpair); if (err) { return err; } // next step, clean up orphans lfs2_fs_preporphans(lfs2, -1); } // find pred int err = lfs2_fs_pred(lfs2, oldpair, &parent); if (err && err != LFS2_ERR_NOENT) { return err; } // if we can't find dir, it must be new if (err != LFS2_ERR_NOENT) { // fix pending move in this pair? this looks like an optimization but // is in fact _required_ since relocating may outdate the move. uint16_t moveid = 0x3ff; if (lfs2_gstate_hasmovehere(&lfs2->gstate, parent.pair)) { moveid = lfs2_tag_id(lfs2->gstate.tag); LFS2_DEBUG("Fixing move while relocating " "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", parent.pair[0], parent.pair[1], moveid); lfs2_fs_prepmove(lfs2, 0x3ff, NULL); } // replace bad pair, either we clean up desync, or no desync occured lfs2_pair_tole32(newpair); err = lfs2_dir_commit(lfs2, &parent, LFS2_MKATTRS( {LFS2_MKTAG_IF(moveid != 0x3ff, LFS2_TYPE_DELETE, moveid, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); lfs2_pair_fromle32(newpair); if (err) { return err; } } return 0; } #endif #ifndef LFS2_READONLY static void lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) { LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0 || orphans >= 0); lfs2->gstate.tag += orphans; lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x800, 0, 0)) | ((uint32_t)lfs2_gstate_hasorphans(&lfs2->gstate) << 31)); } #endif #ifndef LFS2_READONLY static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]) { lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x7ff, 0x3ff, 0)) | ((id != 0x3ff) ? LFS2_MKTAG(LFS2_TYPE_DELETE, id, 0) : 0)); lfs2->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; lfs2->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; } #endif #ifndef LFS2_READONLY static int lfs2_fs_demove(lfs2_t *lfs2) { if (!lfs2_gstate_hasmove(&lfs2->gdisk)) { return 0; } // Fix bad moves LFS2_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, lfs2->gdisk.pair[0], lfs2->gdisk.pair[1], lfs2_tag_id(lfs2->gdisk.tag)); // fetch and delete the moved entry lfs2_mdir_t movedir; int err = lfs2_dir_fetch(lfs2, &movedir, lfs2->gdisk.pair); if (err) { return err; } // prep gstate and delete move id uint16_t moveid = lfs2_tag_id(lfs2->gdisk.tag); lfs2_fs_prepmove(lfs2, 0x3ff, NULL); err = lfs2_dir_commit(lfs2, &movedir, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_DELETE, moveid, 0), NULL})); if (err) { return err; } return 0; } #endif #ifndef LFS2_READONLY static int lfs2_fs_deorphan(lfs2_t *lfs2) { if (!lfs2_gstate_hasorphans(&lfs2->gstate)) { return 0; } // Fix any orphans lfs2_mdir_t pdir = {.split = true, .tail = {0, 1}}; lfs2_mdir_t dir; // iterate over all directory directory entries while (!lfs2_pair_isnull(pdir.tail)) { int err = lfs2_dir_fetch(lfs2, &dir, pdir.tail); if (err) { return err; } // check head blocks for orphans if (!pdir.split) { // check if we have a parent lfs2_mdir_t parent; lfs2_stag_t tag = lfs2_fs_parent(lfs2, pdir.tail, &parent); if (tag < 0 && tag != LFS2_ERR_NOENT) { return tag; } if (tag == LFS2_ERR_NOENT) { // we are an orphan LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", pdir.tail[0], pdir.tail[1]); err = lfs2_dir_drop(lfs2, &pdir, &dir); if (err) { return err; } // refetch tail continue; } lfs2_block_t pair[2]; lfs2_stag_t res = lfs2_dir_get(lfs2, &parent, LFS2_MKTAG(0x7ff, 0x3ff, 0), tag, pair); if (res < 0) { return res; } lfs2_pair_fromle32(pair); if (!lfs2_pair_sync(pair, pdir.tail)) { // we have desynced LFS2_DEBUG("Fixing half-orphan {0x%"PRIx32", 0x%"PRIx32"} " "-> {0x%"PRIx32", 0x%"PRIx32"}", pdir.tail[0], pdir.tail[1], pair[0], pair[1]); lfs2_pair_tole32(pair); err = lfs2_dir_commit(lfs2, &pdir, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), pair})); lfs2_pair_fromle32(pair); if (err) { return err; } // refetch tail continue; } } pdir = dir; } // mark orphans as fixed lfs2_fs_preporphans(lfs2, -lfs2_gstate_getorphans(&lfs2->gstate)); return 0; } #endif #ifndef LFS2_READONLY static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { int err = lfs2_fs_demove(lfs2); if (err) { return err; } err = lfs2_fs_deorphan(lfs2); if (err) { return err; } return 0; } #endif static int lfs2_fs_size_count(void *p, lfs2_block_t block) { (void)block; lfs2_size_t *size = p; *size += 1; return 0; } static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { lfs2_size_t size = 0; int err = lfs2_fs_rawtraverse(lfs2, lfs2_fs_size_count, &size, false); if (err) { return err; } return size; } #ifdef LFS2_MIGRATE ////// Migration from littelfs v1 below this ////// /// Version info /// // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions #define LFS21_VERSION 0x00010007 #define LFS21_VERSION_MAJOR (0xffff & (LFS21_VERSION >> 16)) #define LFS21_VERSION_MINOR (0xffff & (LFS21_VERSION >> 0)) // Version of On-disk data structures // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions #define LFS21_DISK_VERSION 0x00010001 #define LFS21_DISK_VERSION_MAJOR (0xffff & (LFS21_DISK_VERSION >> 16)) #define LFS21_DISK_VERSION_MINOR (0xffff & (LFS21_DISK_VERSION >> 0)) /// v1 Definitions /// // File types enum lfs21_type { LFS21_TYPE_REG = 0x11, LFS21_TYPE_DIR = 0x22, LFS21_TYPE_SUPERBLOCK = 0x2e, }; typedef struct lfs21 { lfs2_block_t root[2]; } lfs21_t; typedef struct lfs21_entry { lfs2_off_t off; struct lfs21_disk_entry { uint8_t type; uint8_t elen; uint8_t alen; uint8_t nlen; union { struct { lfs2_block_t head; lfs2_size_t size; } file; lfs2_block_t dir[2]; } u; } d; } lfs21_entry_t; typedef struct lfs21_dir { struct lfs21_dir *next; lfs2_block_t pair[2]; lfs2_off_t off; lfs2_block_t head[2]; lfs2_off_t pos; struct lfs21_disk_dir { uint32_t rev; lfs2_size_t size; lfs2_block_t tail[2]; } d; } lfs21_dir_t; typedef struct lfs21_superblock { lfs2_off_t off; struct lfs21_disk_superblock { uint8_t type; uint8_t elen; uint8_t alen; uint8_t nlen; lfs2_block_t root[2]; uint32_t block_size; uint32_t block_count; uint32_t version; char magic[8]; } d; } lfs21_superblock_t; /// Low-level wrappers v1->v2 /// static void lfs21_crc(uint32_t *crc, const void *buffer, size_t size) { *crc = lfs2_crc(*crc, buffer, size); } static int lfs21_bd_read(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { // if we ever do more than writes to alternating pairs, // this may need to consider pcache return lfs2_bd_read(lfs2, &lfs2->pcache, &lfs2->rcache, size, block, off, buffer, size); } static int lfs21_bd_crc(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, lfs2_size_t size, uint32_t *crc) { for (lfs2_off_t i = 0; i < size; i++) { uint8_t c; int err = lfs21_bd_read(lfs2, block, off+i, &c, 1); if (err) { return err; } lfs21_crc(crc, &c, 1); } return 0; } /// Endian swapping functions /// static void lfs21_dir_fromle32(struct lfs21_disk_dir *d) { d->rev = lfs2_fromle32(d->rev); d->size = lfs2_fromle32(d->size); d->tail[0] = lfs2_fromle32(d->tail[0]); d->tail[1] = lfs2_fromle32(d->tail[1]); } static void lfs21_dir_tole32(struct lfs21_disk_dir *d) { d->rev = lfs2_tole32(d->rev); d->size = lfs2_tole32(d->size); d->tail[0] = lfs2_tole32(d->tail[0]); d->tail[1] = lfs2_tole32(d->tail[1]); } static void lfs21_entry_fromle32(struct lfs21_disk_entry *d) { d->u.dir[0] = lfs2_fromle32(d->u.dir[0]); d->u.dir[1] = lfs2_fromle32(d->u.dir[1]); } static void lfs21_entry_tole32(struct lfs21_disk_entry *d) { d->u.dir[0] = lfs2_tole32(d->u.dir[0]); d->u.dir[1] = lfs2_tole32(d->u.dir[1]); } static void lfs21_superblock_fromle32(struct lfs21_disk_superblock *d) { d->root[0] = lfs2_fromle32(d->root[0]); d->root[1] = lfs2_fromle32(d->root[1]); d->block_size = lfs2_fromle32(d->block_size); d->block_count = lfs2_fromle32(d->block_count); d->version = lfs2_fromle32(d->version); } ///// Metadata pair and directory operations /// static inline lfs2_size_t lfs21_entry_size(const lfs21_entry_t *entry) { return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; } static int lfs21_dir_fetch(lfs2_t *lfs2, lfs21_dir_t *dir, const lfs2_block_t pair[2]) { // copy out pair, otherwise may be aliasing dir const lfs2_block_t tpair[2] = {pair[0], pair[1]}; bool valid = false; // check both blocks for the most recent revision for (int i = 0; i < 2; i++) { struct lfs21_disk_dir test; int err = lfs21_bd_read(lfs2, tpair[i], 0, &test, sizeof(test)); lfs21_dir_fromle32(&test); if (err) { if (err == LFS2_ERR_CORRUPT) { continue; } return err; } if (valid && lfs2_scmp(test.rev, dir->d.rev) < 0) { continue; } if ((0x7fffffff & test.size) < sizeof(test)+4 || (0x7fffffff & test.size) > lfs2->cfg->block_size) { continue; } uint32_t crc = 0xffffffff; lfs21_dir_tole32(&test); lfs21_crc(&crc, &test, sizeof(test)); lfs21_dir_fromle32(&test); err = lfs21_bd_crc(lfs2, tpair[i], sizeof(test), (0x7fffffff & test.size) - sizeof(test), &crc); if (err) { if (err == LFS2_ERR_CORRUPT) { continue; } return err; } if (crc != 0) { continue; } valid = true; // setup dir in case it's valid dir->pair[0] = tpair[(i+0) % 2]; dir->pair[1] = tpair[(i+1) % 2]; dir->off = sizeof(dir->d); dir->d = test; } if (!valid) { LFS2_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", tpair[0], tpair[1]); return LFS2_ERR_CORRUPT; } return 0; } static int lfs21_dir_next(lfs2_t *lfs2, lfs21_dir_t *dir, lfs21_entry_t *entry) { while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { if (!(0x80000000 & dir->d.size)) { entry->off = dir->off; return LFS2_ERR_NOENT; } int err = lfs21_dir_fetch(lfs2, dir, dir->d.tail); if (err) { return err; } dir->off = sizeof(dir->d); dir->pos += sizeof(dir->d) + 4; } int err = lfs21_bd_read(lfs2, dir->pair[0], dir->off, &entry->d, sizeof(entry->d)); lfs21_entry_fromle32(&entry->d); if (err) { return err; } entry->off = dir->off; dir->off += lfs21_entry_size(entry); dir->pos += lfs21_entry_size(entry); return 0; } /// littlefs v1 specific operations /// int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data) { if (lfs2_pair_isnull(lfs2->lfs21->root)) { return 0; } // iterate over metadata pairs lfs21_dir_t dir; lfs21_entry_t entry; lfs2_block_t cwd[2] = {0, 1}; while (true) { for (int i = 0; i < 2; i++) { int err = cb(data, cwd[i]); if (err) { return err; } } int err = lfs21_dir_fetch(lfs2, &dir, cwd); if (err) { return err; } // iterate over contents while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { err = lfs21_bd_read(lfs2, dir.pair[0], dir.off, &entry.d, sizeof(entry.d)); lfs21_entry_fromle32(&entry.d); if (err) { return err; } dir.off += lfs21_entry_size(&entry); if ((0x70 & entry.d.type) == (0x70 & LFS21_TYPE_REG)) { err = lfs2_ctz_traverse(lfs2, NULL, &lfs2->rcache, entry.d.u.file.head, entry.d.u.file.size, cb, data); if (err) { return err; } } } // we also need to check if we contain a threaded v2 directory lfs2_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; while (dir2.split) { err = lfs2_dir_fetch(lfs2, &dir2, dir2.tail); if (err) { break; } for (int i = 0; i < 2; i++) { err = cb(data, dir2.pair[i]); if (err) { return err; } } } cwd[0] = dir.d.tail[0]; cwd[1] = dir.d.tail[1]; if (lfs2_pair_isnull(cwd)) { break; } } return 0; } static int lfs21_moved(lfs2_t *lfs2, const void *e) { if (lfs2_pair_isnull(lfs2->lfs21->root)) { return 0; } // skip superblock lfs21_dir_t cwd; int err = lfs21_dir_fetch(lfs2, &cwd, (const lfs2_block_t[2]){0, 1}); if (err) { return err; } // iterate over all directory directory entries lfs21_entry_t entry; while (!lfs2_pair_isnull(cwd.d.tail)) { err = lfs21_dir_fetch(lfs2, &cwd, cwd.d.tail); if (err) { return err; } while (true) { err = lfs21_dir_next(lfs2, &cwd, &entry); if (err && err != LFS2_ERR_NOENT) { return err; } if (err == LFS2_ERR_NOENT) { break; } if (!(0x80 & entry.d.type) && memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { return true; } } } return false; } /// Filesystem operations /// static int lfs21_mount(lfs2_t *lfs2, struct lfs21 *lfs21, const struct lfs2_config *cfg) { int err = 0; { err = lfs2_init(lfs2, cfg); if (err) { return err; } lfs2->lfs21 = lfs21; lfs2->lfs21->root[0] = LFS2_BLOCK_NULL; lfs2->lfs21->root[1] = LFS2_BLOCK_NULL; // setup free lookahead lfs2->free.off = 0; lfs2->free.size = 0; lfs2->free.i = 0; lfs2_alloc_ack(lfs2); // load superblock lfs21_dir_t dir; lfs21_superblock_t superblock; err = lfs21_dir_fetch(lfs2, &dir, (const lfs2_block_t[2]){0, 1}); if (err && err != LFS2_ERR_CORRUPT) { goto cleanup; } if (!err) { err = lfs21_bd_read(lfs2, dir.pair[0], sizeof(dir.d), &superblock.d, sizeof(superblock.d)); lfs21_superblock_fromle32(&superblock.d); if (err) { goto cleanup; } lfs2->lfs21->root[0] = superblock.d.root[0]; lfs2->lfs21->root[1] = superblock.d.root[1]; } if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { LFS2_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", 0, 1); err = LFS2_ERR_CORRUPT; goto cleanup; } uint16_t major_version = (0xffff & (superblock.d.version >> 16)); uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); if ((major_version != LFS21_DISK_VERSION_MAJOR || minor_version > LFS21_DISK_VERSION_MINOR)) { LFS2_ERROR("Invalid version v%d.%d", major_version, minor_version); err = LFS2_ERR_INVAL; goto cleanup; } return 0; } cleanup: lfs2_deinit(lfs2); return err; } static int lfs21_unmount(lfs2_t *lfs2) { return lfs2_deinit(lfs2); } /// v1 migration /// static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; int err = lfs21_mount(lfs2, &lfs21, cfg); if (err) { return err; } { // iterate through each directory, copying over entries // into new directory lfs21_dir_t dir1; lfs2_mdir_t dir2; dir1.d.tail[0] = lfs2->lfs21->root[0]; dir1.d.tail[1] = lfs2->lfs21->root[1]; while (!lfs2_pair_isnull(dir1.d.tail)) { // iterate old dir err = lfs21_dir_fetch(lfs2, &dir1, dir1.d.tail); if (err) { goto cleanup; } // create new dir and bind as temporary pretend root err = lfs2_dir_alloc(lfs2, &dir2); if (err) { goto cleanup; } dir2.rev = dir1.d.rev; dir1.head[0] = dir1.pair[0]; dir1.head[1] = dir1.pair[1]; lfs2->root[0] = dir2.pair[0]; lfs2->root[1] = dir2.pair[1]; err = lfs2_dir_commit(lfs2, &dir2, NULL, 0); if (err) { goto cleanup; } while (true) { lfs21_entry_t entry1; err = lfs21_dir_next(lfs2, &dir1, &entry1); if (err && err != LFS2_ERR_NOENT) { goto cleanup; } if (err == LFS2_ERR_NOENT) { break; } // check that entry has not been moved if (entry1.d.type & 0x80) { int moved = lfs21_moved(lfs2, &entry1.d.u); if (moved < 0) { err = moved; goto cleanup; } if (moved) { continue; } entry1.d.type &= ~0x80; } // also fetch name char name[LFS2_NAME_MAX+1]; memset(name, 0, sizeof(name)); err = lfs21_bd_read(lfs2, dir1.pair[0], entry1.off + 4+entry1.d.elen+entry1.d.alen, name, entry1.d.nlen); if (err) { goto cleanup; } bool isdir = (entry1.d.type == LFS21_TYPE_DIR); // create entry in new dir err = lfs2_dir_fetch(lfs2, &dir2, lfs2->root); if (err) { goto cleanup; } uint16_t id; err = lfs2_dir_find(lfs2, &dir2, &(const char*){name}, &id); if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { err = (err < 0) ? err : LFS2_ERR_EXIST; goto cleanup; } lfs21_entry_tole32(&entry1.d); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_CREATE, id, 0)}, {LFS2_MKTAG_IF_ELSE(isdir, LFS2_TYPE_DIR, id, entry1.d.nlen, LFS2_TYPE_REG, id, entry1.d.nlen), name}, {LFS2_MKTAG_IF_ELSE(isdir, LFS2_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), LFS2_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), &entry1.d.u})); lfs21_entry_fromle32(&entry1.d); if (err) { goto cleanup; } } if (!lfs2_pair_isnull(dir1.d.tail)) { // find last block and update tail to thread into fs err = lfs2_dir_fetch(lfs2, &dir2, lfs2->root); if (err) { goto cleanup; } while (dir2.split) { err = lfs2_dir_fetch(lfs2, &dir2, dir2.tail); if (err) { goto cleanup; } } lfs2_pair_tole32(dir2.pair); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); lfs2_pair_fromle32(dir2.pair); if (err) { goto cleanup; } } // Copy over first block to thread into fs. Unfortunately // if this fails there is not much we can do. LFS2_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " "-> {0x%"PRIx32", 0x%"PRIx32"}", lfs2->root[0], lfs2->root[1], dir1.head[0], dir1.head[1]); err = lfs2_bd_erase(lfs2, dir1.head[1]); if (err) { goto cleanup; } err = lfs2_dir_fetch(lfs2, &dir2, lfs2->root); if (err) { goto cleanup; } for (lfs2_off_t i = 0; i < dir2.off; i++) { uint8_t dat; err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, dir2.off, dir2.pair[0], i, &dat, 1); if (err) { goto cleanup; } err = lfs2_bd_prog(lfs2, &lfs2->pcache, &lfs2->rcache, true, dir1.head[1], i, &dat, 1); if (err) { goto cleanup; } } err = lfs2_bd_flush(lfs2, &lfs2->pcache, &lfs2->rcache, true); if (err) { goto cleanup; } } // Create new superblock. This marks a successful migration! err = lfs21_dir_fetch(lfs2, &dir1, (const lfs2_block_t[2]){0, 1}); if (err) { goto cleanup; } dir2.pair[0] = dir1.pair[0]; dir2.pair[1] = dir1.pair[1]; dir2.rev = dir1.d.rev; dir2.off = sizeof(dir2.rev); dir2.etag = 0xffffffff; dir2.count = 0; dir2.tail[0] = lfs2->lfs21->root[0]; dir2.tail[1] = lfs2->lfs21->root[1]; dir2.erased = false; dir2.split = true; lfs2_superblock_t superblock = { .version = LFS2_DISK_VERSION, .block_size = lfs2->cfg->block_size, .block_count = lfs2->cfg->block_count, .name_max = lfs2->name_max, .file_max = lfs2->file_max, .attr_max = lfs2->attr_max, }; lfs2_superblock_tole32(&superblock); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0)}, {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); if (err) { goto cleanup; } // sanity check that fetch works err = lfs2_dir_fetch(lfs2, &dir2, (const lfs2_block_t[2]){0, 1}); if (err) { goto cleanup; } // force compaction to prevent accidentally mounting v1 dir2.erased = false; err = lfs2_dir_commit(lfs2, &dir2, NULL, 0); if (err) { goto cleanup; } } cleanup: lfs21_unmount(lfs2); return err; } #endif /// Public API wrappers /// // Here we can add tracing/thread safety easily // Thread-safe wrappers if enabled #ifdef LFS2_THREADSAFE #define LFS2_LOCK(cfg) cfg->lock(cfg) #define LFS2_UNLOCK(cfg) cfg->unlock(cfg) #else #define LFS2_LOCK(cfg) ((void)cfg, 0) #define LFS2_UNLOCK(cfg) ((void)cfg) #endif // Public API #ifndef LFS2_READONLY int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = LFS2_LOCK(cfg); if (err) { return err; } LFS2_TRACE("lfs2_format(%p, %p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " ".attr_max=%"PRIu32"})", (void*)lfs2, (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); err = lfs2_rawformat(lfs2, cfg); LFS2_TRACE("lfs2_format -> %d", err); LFS2_UNLOCK(cfg); return err; } #endif int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = LFS2_LOCK(cfg); if (err) { return err; } LFS2_TRACE("lfs2_mount(%p, %p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " ".attr_max=%"PRIu32"})", (void*)lfs2, (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); err = lfs2_rawmount(lfs2, cfg); LFS2_TRACE("lfs2_mount -> %d", err); LFS2_UNLOCK(cfg); return err; } int lfs2_unmount(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_unmount(%p)", (void*)lfs2); err = lfs2_rawunmount(lfs2); LFS2_TRACE("lfs2_unmount -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #ifndef LFS2_READONLY int lfs2_remove(lfs2_t *lfs2, const char *path) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_remove(%p, \"%s\")", (void*)lfs2, path); err = lfs2_rawremove(lfs2, path); LFS2_TRACE("lfs2_remove -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif #ifndef LFS2_READONLY int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_rename(%p, \"%s\", \"%s\")", (void*)lfs2, oldpath, newpath); err = lfs2_rawrename(lfs2, oldpath, newpath); LFS2_TRACE("lfs2_rename -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_stat(%p, \"%s\", %p)", (void*)lfs2, path, (void*)info); err = lfs2_rawstat(lfs2, path, info); LFS2_TRACE("lfs2_stat -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); lfs2_ssize_t res = lfs2_rawgetattr(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_getattr -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } #ifndef LFS2_READONLY int lfs2_setattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); err = lfs2_rawsetattr(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_setattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif #ifndef LFS2_READONLY int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs2, path, type); err = lfs2_rawremoveattr(lfs2, path, type); LFS2_TRACE("lfs2_removeattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_open(%p, %p, \"%s\", %x)", (void*)lfs2, (void*)file, path, flags); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); err = lfs2_file_rawopen(lfs2, file, path, flags); LFS2_TRACE("lfs2_file_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags, const struct lfs2_file_config *cfg) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_opencfg(%p, %p, \"%s\", %x, %p {" ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", (void*)lfs2, (void*)file, path, flags, (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); err = lfs2_file_rawopencfg(lfs2, file, path, flags, cfg); LFS2_TRACE("lfs2_file_opencfg -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_close(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); err = lfs2_file_rawclose(lfs2, file); LFS2_TRACE("lfs2_file_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #ifndef LFS2_READONLY int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); err = lfs2_file_rawsync(lfs2, file); LFS2_TRACE("lfs2_file_sync -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_read(%p, %p, %p, %"PRIu32")", (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); lfs2_ssize_t res = lfs2_file_rawread(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_read -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } #ifndef LFS2_READONLY lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_write(%p, %p, %p, %"PRIu32")", (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_write -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } #endif lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_seek(%p, %p, %"PRId32", %d)", (void*)lfs2, (void*)file, off, whence); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, off, whence); LFS2_TRACE("lfs2_file_seek -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } #ifndef LFS2_READONLY int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_truncate(%p, %p, %"PRIu32")", (void*)lfs2, (void*)file, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); err = lfs2_file_rawtruncate(lfs2, file, size); LFS2_TRACE("lfs2_file_truncate -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_tell(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); lfs2_soff_t res = lfs2_file_rawtell(lfs2, file); LFS2_TRACE("lfs2_file_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); err = lfs2_file_rawrewind(lfs2, file); LFS2_TRACE("lfs2_file_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_file_size(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); lfs2_soff_t res = lfs2_file_rawsize(lfs2, file); LFS2_TRACE("lfs2_file_size -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } #ifndef LFS2_READONLY int lfs2_mkdir(lfs2_t *lfs2, const char *path) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); err = lfs2_rawmkdir(lfs2, path); LFS2_TRACE("lfs2_mkdir -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)dir)); err = lfs2_dir_rawopen(lfs2, dir, path); LFS2_TRACE("lfs2_dir_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_close(%p, %p)", (void*)lfs2, (void*)dir); err = lfs2_dir_rawclose(lfs2, dir); LFS2_TRACE("lfs2_dir_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_read(%p, %p, %p)", (void*)lfs2, (void*)dir, (void*)info); err = lfs2_dir_rawread(lfs2, dir, info); LFS2_TRACE("lfs2_dir_read -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_seek(%p, %p, %"PRIu32")", (void*)lfs2, (void*)dir, off); err = lfs2_dir_rawseek(lfs2, dir, off); LFS2_TRACE("lfs2_dir_seek -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_tell(%p, %p)", (void*)lfs2, (void*)dir); lfs2_soff_t res = lfs2_dir_rawtell(lfs2, dir); LFS2_TRACE("lfs2_dir_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_dir_rewind(%p, %p)", (void*)lfs2, (void*)dir); err = lfs2_dir_rawrewind(lfs2, dir); LFS2_TRACE("lfs2_dir_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); LFS2_TRACE("lfs2_fs_size -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); return res; } int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", (void*)lfs2, (void*)(uintptr_t)cb, data); err = lfs2_fs_rawtraverse(lfs2, cb, data, true); LFS2_TRACE("lfs2_fs_traverse -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #ifdef LFS2_MIGRATE int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = LFS2_LOCK(cfg); if (err) { return err; } LFS2_TRACE("lfs2_migrate(%p, %p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " ".attr_max=%"PRIu32"})", (void*)lfs2, (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); err = lfs2_rawmigrate(lfs2, cfg); LFS2_TRACE("lfs2_migrate -> %d", err); LFS2_UNLOCK(cfg); return err; } #endif // --------------------- lfs2_util.c ----------------- // #include "lib/littlefs/lfs2.h" // Only compile if user does not provide custom config #ifndef LFS2_CONFIG // Software CRC implementation with small lookup table uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { static const uint32_t rtable[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, }; const uint8_t *data = buffer; for (size_t i = 0; i < size; i++) { crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; } return crc; } #endif // ---------------------- shared/runtime/pyexec.c --------------- // #include #include #include #include "py/compile.h" #include "py/runtime.h" #include "py/repl.h" #include "py/gc.h" #include "py/frozenmod.h" #include "py/mphal.h" #if MICROPY_HW_ENABLE_USB #include "irq.h" #include "usb.h" #endif #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "genhdr/mpversion.h" pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; int pyexec_system_exit = 0; #if MICROPY_REPL_INFO STATIC bool repl_display_debugging_info = 0; #endif #define EXEC_FLAG_PRINT_EOF (1 << 0) #define EXEC_FLAG_ALLOW_DEBUGGING (1 << 1) #define EXEC_FLAG_IS_REPL (1 << 2) #define EXEC_FLAG_SOURCE_IS_RAW_CODE (1 << 3) #define EXEC_FLAG_SOURCE_IS_VSTR (1 << 4) #define EXEC_FLAG_SOURCE_IS_FILENAME (1 << 5) #define EXEC_FLAG_SOURCE_IS_READER (1 << 6) // parses, compiles and executes the code in the lexer // frees the lexer before returning // EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output // EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code // EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile) STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, mp_uint_t exec_flags) { int ret = 0; #if MICROPY_REPL_INFO uint32_t start = 0; #endif #ifdef MICROPY_BOARD_BEFORE_PYTHON_EXEC MICROPY_BOARD_BEFORE_PYTHON_EXEC(input_kind, exec_flags); #endif // by default a SystemExit exception returns 0 pyexec_system_exit = 0; nlr_buf_t nlr; nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { mp_obj_t module_fun; #if MICROPY_MODULE_FROZEN_MPY if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { // source is a raw_code object, create the function const mp_frozen_module_t *frozen = source; mp_module_context_t *ctx = m_new_obj(mp_module_context_t); ctx->module.globals = mp_globals_get(); ctx->constants = frozen->constants; module_fun = mp_make_function_from_raw_code(frozen->rc, ctx, NULL); } else #endif { #if MICROPY_ENABLE_COMPILER mp_lexer_t *lex; if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) { const vstr_t *vstr = source; lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0); } else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source); } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { lex = mp_lexer_new_from_file(source); } else { lex = (mp_lexer_t *)source; } // source is a lexer, parse and compile the script qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); #else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); #endif } // execute code mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us #if MICROPY_REPL_INFO start = mp_hal_ticks_ms(); #endif mp_call_function_0(module_fun); mp_hal_set_interrupt_char(-1); // disable interrupt mp_handle_pending(true); // handle any pending exceptions (and any callbacks) nlr_pop(); ret = 1; if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); } } else { // uncaught exception mp_hal_set_interrupt_char(-1); // disable interrupt mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { const mp_reader_t *reader = source; reader->close(reader->data); } // print EOF after normal output if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); } // check for SystemExit if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // at the moment, the value of SystemExit is unused ret = pyexec_system_exit; } else { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); ret = 0; } } #if MICROPY_REPL_INFO // display debugging info if wanted if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) { mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly printf("took " UINT_FMT " ms\n", ticks); // qstr info { size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); printf("qstr:\n n_pool=%u\n n_qstr=%u\n " "n_str_data_bytes=%u\n n_total_bytes=%u\n", (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); } #if MICROPY_ENABLE_GC // run collection and print GC info gc_collect(); gc_dump_info(); #endif } #endif if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); } #ifdef MICROPY_BOARD_AFTER_PYTHON_EXEC MICROPY_BOARD_AFTER_PYTHON_EXEC(input_kind, exec_flags, nlr.ret_val, &ret); #endif return ret; } #if MICROPY_ENABLE_COMPILER // This can be configured by a port (and even configured to a function to be // computed dynamically) to indicate the maximum number of bytes that can be // held in the stdin buffer. #ifndef MICROPY_REPL_STDIN_BUFFER_MAX #define MICROPY_REPL_STDIN_BUFFER_MAX (256) #endif typedef struct _mp_reader_stdin_t { bool eof; uint16_t window_max; uint16_t window_remain; } mp_reader_stdin_t; STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) { mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; if (reader->eof) { return MP_READER_EOF; } int c = mp_hal_stdin_rx_chr(); if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { reader->eof = true; mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host if (c == CHAR_CTRL_C) { #if MICROPY_KBD_EXCEPTION MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); #else mp_raise_type(&mp_type_KeyboardInterrupt); #endif } else { return MP_READER_EOF; } } if (--reader->window_remain == 0) { mp_hal_stdout_tx_strn("\x01", 1); // indicate window available to host reader->window_remain = reader->window_max; } return c; } STATIC void mp_reader_stdin_close(void *data) { mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; if (!reader->eof) { reader->eof = true; mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host for (;;) { int c = mp_hal_stdin_rx_chr(); if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { break; } } } } STATIC void mp_reader_new_stdin(mp_reader_t *reader, mp_reader_stdin_t *reader_stdin, uint16_t buf_max) { // Make flow-control window half the buffer size, and indicate to the host that 2x windows are // free (sending the window size implicitly indicates that a window is free, and then the 0x01 // indicates that another window is free). size_t window = buf_max / 2; char reply[3] = { window & 0xff, window >> 8, 0x01 }; mp_hal_stdout_tx_strn(reply, sizeof(reply)); reader_stdin->eof = false; reader_stdin->window_max = window; reader_stdin->window_remain = window; reader->data = reader_stdin; reader->readbyte = mp_reader_stdin_readbyte; reader->close = mp_reader_stdin_close; } STATIC int do_reader_stdin(int c) { if (c != 'A') { // Unsupported command. mp_hal_stdout_tx_strn("R\x00", 2); return 0; } // Indicate reception of command. mp_hal_stdout_tx_strn("R\x01", 2); mp_reader_t reader; mp_reader_stdin_t reader_stdin; mp_reader_new_stdin(&reader, &reader_stdin, MICROPY_REPL_STDIN_BUFFER_MAX); int exec_flags = EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_READER; return parse_compile_execute(&reader, MP_PARSE_FILE_INPUT, exec_flags); } #if MICROPY_REPL_EVENT_DRIVEN typedef struct _repl_t { // This structure originally also held current REPL line, // but it was moved to MP_STATE_VM(repl_line) as containing // root pointer. Still keep structure in case more state // will be added later. // vstr_t line; bool cont_line; bool paste_mode; } repl_t; repl_t repl; STATIC int pyexec_raw_repl_process_char(int c); STATIC int pyexec_friendly_repl_process_char(int c); void pyexec_event_repl_init(void) { MP_STATE_VM(repl_line) = vstr_new(32); repl.cont_line = false; repl.paste_mode = false; // no prompt before printing friendly REPL banner or entering raw REPL readline_init(MP_STATE_VM(repl_line), ""); if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { pyexec_raw_repl_process_char(CHAR_CTRL_A); } else { pyexec_friendly_repl_process_char(CHAR_CTRL_B); } } STATIC int pyexec_raw_repl_process_char(int c) { if (c == CHAR_CTRL_A) { // reset raw REPL if (vstr_len(MP_STATE_VM(repl_line)) == 2 && vstr_str(MP_STATE_VM(repl_line))[0] == CHAR_CTRL_E) { int ret = do_reader_stdin(vstr_str(MP_STATE_VM(repl_line))[1]); if (ret & PYEXEC_FORCED_EXIT) { return ret; } goto reset; } mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); goto reset; } else if (c == CHAR_CTRL_B) { // change to friendly REPL pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; vstr_reset(MP_STATE_VM(repl_line)); repl.cont_line = false; repl.paste_mode = false; pyexec_friendly_repl_process_char(CHAR_CTRL_B); return 0; } else if (c == CHAR_CTRL_C) { // clear line vstr_reset(MP_STATE_VM(repl_line)); return 0; } else if (c == CHAR_CTRL_D) { // input finished } else { // let through any other raw 8-bit value vstr_add_byte(MP_STATE_VM(repl_line), c); return 0; } // indicate reception of command mp_hal_stdout_tx_str("OK"); if (MP_STATE_VM(repl_line)->len == 0) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(MP_STATE_VM(repl_line)); return PYEXEC_FORCED_EXIT; } int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } reset: vstr_reset(MP_STATE_VM(repl_line)); mp_hal_stdout_tx_str(">"); return 0; } STATIC int pyexec_friendly_repl_process_char(int c) { if (repl.paste_mode) { if (c == CHAR_CTRL_C) { // cancel everything mp_hal_stdout_tx_str("\r\n"); goto input_restart; } else if (c == CHAR_CTRL_D) { // end of input mp_hal_stdout_tx_str("\r\n"); int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } goto input_restart; } else { // add char to buffer and echo vstr_add_byte(MP_STATE_VM(repl_line), c); if (c == '\r') { mp_hal_stdout_tx_str("\r\n=== "); } else { char buf[1] = {c}; mp_hal_stdout_tx_strn(buf, 1); } return 0; } } int ret = readline_process_char(c); if (!repl.cont_line) { if (ret == CHAR_CTRL_A) { // change to raw REPL pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; mp_hal_stdout_tx_str("\r\n"); pyexec_raw_repl_process_char(CHAR_CTRL_A); return 0; } else if (ret == CHAR_CTRL_B) { // reset friendly REPL mp_hal_stdout_tx_str("\r\n"); mp_hal_stdout_tx_str("\e[01;31mIPUpy\e[0m REPL on " MICROPY_HW_BOARD_NAME " with \e[01;36m" MICROPY_HW_MCU_NAME "\e[0m\r\n"); mp_hal_stdout_tx_str(MICROPY_GIT_TAG " built " MICROPY_BUILD_DATE "\r\n"); #if MICROPY_PY_BUILTINS_HELP mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); #endif goto input_restart; } else if (ret == CHAR_CTRL_C) { // break mp_hal_stdout_tx_str("\r\n"); goto input_restart; } else if (ret == CHAR_CTRL_D) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(MP_STATE_VM(repl_line)); return PYEXEC_FORCED_EXIT; } else if (ret == CHAR_CTRL_E) { // paste mode mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); vstr_reset(MP_STATE_VM(repl_line)); repl.paste_mode = true; return 0; } if (ret < 0) { return 0; } if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { goto exec; } vstr_add_byte(MP_STATE_VM(repl_line), '\n'); repl.cont_line = true; readline_note_newline(mp_repl_get_ps2()); return 0; } else { if (ret == CHAR_CTRL_C) { // cancel everything mp_hal_stdout_tx_str("\r\n"); repl.cont_line = false; goto input_restart; } else if (ret == CHAR_CTRL_D) { // stop entering compound statement goto exec; } if (ret < 0) { return 0; } if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { vstr_add_byte(MP_STATE_VM(repl_line), '\n'); readline_note_newline(mp_repl_get_ps2()); return 0; } exec:; int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } input_restart: vstr_reset(MP_STATE_VM(repl_line)); repl.cont_line = false; repl.paste_mode = false; readline_init(MP_STATE_VM(repl_line), mp_repl_get_ps1()); return 0; } } uint8_t pyexec_repl_active; int pyexec_event_repl_process_char(int c) { pyexec_repl_active = 1; int res; if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { res = pyexec_raw_repl_process_char(c); } else { res = pyexec_friendly_repl_process_char(c); } pyexec_repl_active = 0; return res; } #else // MICROPY_REPL_EVENT_DRIVEN int pyexec_raw_repl(void) { vstr_t line; vstr_init(&line, 32); raw_repl_reset: mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); for (;;) { vstr_reset(&line); mp_hal_stdout_tx_str(">"); for (;;) { int c = mp_hal_stdin_rx_chr(); if (c == CHAR_CTRL_A) { // reset raw REPL if (vstr_len(&line) == 2 && vstr_str(&line)[0] == CHAR_CTRL_E) { int ret = do_reader_stdin(vstr_str(&line)[1]); if (ret & PYEXEC_FORCED_EXIT) { return ret; } vstr_reset(&line); mp_hal_stdout_tx_str(">"); continue; } goto raw_repl_reset; } else if (c == CHAR_CTRL_B) { // change to friendly REPL mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; return 0; } else if (c == CHAR_CTRL_C) { // clear line vstr_reset(&line); } else if (c == CHAR_CTRL_D) { // input finished break; } else { // let through any other raw 8-bit value vstr_add_byte(&line, c); } } // indicate reception of command mp_hal_stdout_tx_str("OK"); if (line.len == 0) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); return PYEXEC_FORCED_EXIT; } int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } } } int pyexec_friendly_repl(void) { vstr_t line; vstr_init(&line, 32); friendly_repl_reset: mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); #if MICROPY_PY_BUILTINS_HELP mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); #endif // to test ctrl-C /* { uint32_t x[4] = {0x424242, 0xdeaddead, 0x242424, 0xdeadbeef}; for (;;) { nlr_buf_t nlr; printf("pyexec_repl: %p\n", x); mp_hal_set_interrupt_char(CHAR_CTRL_C); if (nlr_push(&nlr) == 0) { for (;;) { } } else { printf("break\n"); } } } */ for (;;) { input_restart: #if MICROPY_HW_ENABLE_USB if (usb_vcp_is_enabled()) { // If the user gets to here and interrupts are disabled then // they'll never see the prompt, traceback etc. The USB REPL needs // interrupts to be enabled or no transfers occur. So we try to // do the user a favor and reenable interrupts. if (query_irq() == IRQ_STATE_DISABLED) { enable_irq(IRQ_STATE_ENABLED); mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n"); } } #endif // If the GC is locked at this point there is no way out except a reset, // so force the GC to be unlocked to help the user debug what went wrong. if (MP_STATE_THREAD(gc_lock_depth) != 0) { MP_STATE_THREAD(gc_lock_depth) = 0; } vstr_reset(&line); int ret = readline(&line, mp_repl_get_ps1()); mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; if (ret == CHAR_CTRL_A) { // change to raw REPL mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; return 0; } else if (ret == CHAR_CTRL_B) { // reset friendly REPL mp_hal_stdout_tx_str("\r\n"); goto friendly_repl_reset; } else if (ret == CHAR_CTRL_C) { // break mp_hal_stdout_tx_str("\r\n"); continue; } else if (ret == CHAR_CTRL_D) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); return PYEXEC_FORCED_EXIT; } else if (ret == CHAR_CTRL_E) { // paste mode mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); vstr_reset(&line); for (;;) { char c = mp_hal_stdin_rx_chr(); if (c == CHAR_CTRL_C) { // cancel everything mp_hal_stdout_tx_str("\r\n"); goto input_restart; } else if (c == CHAR_CTRL_D) { // end of input mp_hal_stdout_tx_str("\r\n"); break; } else { // add char to buffer and echo vstr_add_byte(&line, c); if (c == '\r') { mp_hal_stdout_tx_str("\r\n=== "); } else { mp_hal_stdout_tx_strn(&c, 1); } } } parse_input_kind = MP_PARSE_FILE_INPUT; } else if (vstr_len(&line) == 0) { continue; } else { // got a line with non-zero length, see if it needs continuing while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { vstr_add_byte(&line, '\n'); ret = readline(&line, mp_repl_get_ps2()); if (ret == CHAR_CTRL_C) { // cancel everything mp_hal_stdout_tx_str("\r\n"); goto input_restart; } else if (ret == CHAR_CTRL_D) { // stop entering compound statement break; } } } ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } } } #endif // MICROPY_REPL_EVENT_DRIVEN #endif // MICROPY_ENABLE_COMPILER int pyexec_file(const char *filename) { return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME); } int pyexec_file_if_exists(const char *filename) { #if MICROPY_MODULE_FROZEN if (mp_find_frozen_module(filename, NULL, NULL) == MP_IMPORT_STAT_FILE) { return pyexec_frozen_module(filename); } #endif if (mp_import_stat(filename) != MP_IMPORT_STAT_FILE) { return 1; // success (no file is the same as an empty file executing without fail) } return pyexec_file(filename); } #if MICROPY_MODULE_FROZEN int pyexec_frozen_module(const char *name) { void *frozen_data; int frozen_type; mp_find_frozen_module(name, &frozen_type, &frozen_data); switch (frozen_type) { #if MICROPY_MODULE_FROZEN_STR case MP_FROZEN_STR: return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, 0); #endif #if MICROPY_MODULE_FROZEN_MPY case MP_FROZEN_MPY: return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_RAW_CODE); #endif default: printf("could not find module '%s'\n", name); return false; } } #endif #if MICROPY_REPL_INFO mp_obj_t pyb_set_repl_info(mp_obj_t o_value) { repl_display_debugging_info = mp_obj_get_int(o_value); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj, pyb_set_repl_info); #endif // -------------- shared/readline/readline.c --------------------- // #include #include #include "py/mpstate.h" #include "py/repl.h" #include "py/mphal.h" #include "shared/readline/readline.h" #if 0 // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf printf #else // don't print debugging info #define DEBUG_printf(...) (void)0 #endif #define READLINE_HIST_SIZE (MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist))) enum { ESEQ_NONE, ESEQ_ESC, ESEQ_ESC_BRACKET, ESEQ_ESC_BRACKET_DIGIT, ESEQ_ESC_O }; void readline_init0(void) { memset(MP_STATE_PORT(readline_hist), 0, READLINE_HIST_SIZE * sizeof(const char*)); } STATIC char *str_dup_maybe(const char *str) { uint32_t len = strlen(str); char *s2 = m_new_maybe(char, len + 1); if (s2 == NULL) { return NULL; } memcpy(s2, str, len + 1); return s2; } // By default assume terminal which implements VT100 commands... #ifndef MICROPY_HAL_HAS_VT100 #define MICROPY_HAL_HAS_VT100 (1) #endif // ...and provide the implementation using them #if MICROPY_HAL_HAS_VT100 STATIC void mp_hal_move_cursor_back(uint pos) { if (pos <= 4) { // fast path for most common case of 1 step back mp_hal_stdout_tx_strn("\b\b\b\b", pos); } else { assert(pos < 1000u); // snprintf needs space for the terminating null character // int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos); // char vt100_command[46] = "\nmp_hal_move_cursor_back not implemented :)\n"; char vt100_command[6] = "\x1b[DDDD"; int n; if (pos > 99) { vt100_command[2] = 0x30 + (pos / 100); vt100_command[3] = 0x30 + ((pos % 100) / 10); vt100_command[4] = 0x30 + (pos % 10); n = 6; } else if (pos > 9) { vt100_command[2] = 0x30 + ((pos % 100) / 10); vt100_command[3] = 0x30 + (pos % 10); n = 5; } else { vt100_command[2] = 0x30 + (pos % 10); n = 4; } mp_hal_stdout_tx_strn(vt100_command, n); // if (n > 0) { // assert((unsigned)n < sizeof(vt100_command)); // vt100_command[n] = 'D'; // replace null char // mp_hal_stdout_tx_strn(vt100_command, n + 1); // } } } STATIC void mp_hal_erase_line_from_cursor(uint n_chars_to_erase) { (void)n_chars_to_erase; mp_hal_stdout_tx_strn("\x1b[K", 3); } #endif typedef struct _readline_t { vstr_t *line; size_t orig_line_len; int escape_seq; int hist_cur; size_t cursor_pos; char escape_seq_buf[1]; const char *prompt; } readline_t; STATIC readline_t rl; #if MICROPY_REPL_EMACS_WORDS_MOVE STATIC size_t cursor_count_word(int forward) { const char *line_buf = vstr_str(rl.line); size_t pos = rl.cursor_pos; bool in_word = false; for (;;) { // if moving backwards and we've reached 0... break if (!forward && pos == 0) { break; } // or if moving forwards and we've reached to the end of line... break else if (forward && pos == vstr_len(rl.line)) { break; } if (unichar_isalnum(line_buf[pos + (forward - 1)])) { in_word = true; } else if (in_word) { break; } pos += forward ? forward : -1; } return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos; } #endif int readline_process_char(int c) { size_t last_line_len = rl.line->len; int redraw_step_back = 0; bool redraw_from_cursor = false; int redraw_step_forward = 0; if (rl.escape_seq == ESEQ_NONE) { if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_E && vstr_len(rl.line) == rl.orig_line_len) { // control character with empty line return c; } else if (c == CHAR_CTRL_A) { // CTRL-A with non-empty line is go-to-start-of-line goto home_key; #if MICROPY_REPL_EMACS_KEYS } else if (c == CHAR_CTRL_B) { // CTRL-B with non-empty line is go-back-one-char goto left_arrow_key; #endif } else if (c == CHAR_CTRL_C) { // CTRL-C with non-empty line is cancel return c; #if MICROPY_REPL_EMACS_KEYS } else if (c == CHAR_CTRL_D) { // CTRL-D with non-empty line is delete-at-cursor goto delete_key; #endif } else if (c == CHAR_CTRL_E) { // CTRL-E is go-to-end-of-line goto end_key; #if MICROPY_REPL_EMACS_KEYS } else if (c == CHAR_CTRL_F) { // CTRL-F with non-empty line is go-forward-one-char goto right_arrow_key; } else if (c == CHAR_CTRL_K) { // CTRL-K is kill from cursor to end-of-line, inclusive vstr_cut_tail_bytes(rl.line, last_line_len - rl.cursor_pos); // set redraw parameters redraw_from_cursor = true; } else if (c == CHAR_CTRL_N) { // CTRL-N is go to next line in history goto down_arrow_key; } else if (c == CHAR_CTRL_P) { // CTRL-P is go to previous line in history goto up_arrow_key; } else if (c == CHAR_CTRL_U) { // CTRL-U is kill from beginning-of-line up to cursor vstr_cut_out_bytes(rl.line, rl.orig_line_len, rl.cursor_pos - rl.orig_line_len); // set redraw parameters redraw_step_back = rl.cursor_pos - rl.orig_line_len; redraw_from_cursor = true; #endif #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE } else if (c == CHAR_CTRL_W) { goto backward_kill_word; #endif } else if (c == '\r') { // newline mp_hal_stdout_tx_str("\r\n"); readline_push_history(vstr_null_terminated_str(rl.line) + rl.orig_line_len); return 0; } else if (c == 27) { // escape sequence rl.escape_seq = ESEQ_ESC; } else if (c == 8 || c == 127) { // backspace/delete if (rl.cursor_pos > rl.orig_line_len) { // work out how many chars to backspace #if MICROPY_REPL_AUTO_INDENT int nspace = 0; for (size_t i = rl.orig_line_len; i < rl.cursor_pos; i++) { if (rl.line->buf[i] != ' ') { nspace = 0; break; } nspace += 1; } if (nspace < 4) { nspace = 1; } else { nspace = 4; } #else int nspace = 1; #endif // do the backspace vstr_cut_out_bytes(rl.line, rl.cursor_pos - nspace, nspace); // set redraw parameters redraw_step_back = nspace; redraw_from_cursor = true; } #if MICROPY_HELPER_REPL } else if (c == 9) { // tab magic const char *compl_str; size_t compl_len = mp_repl_autocomplete(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len, &mp_plat_print, &compl_str); if (compl_len == 0) { // no match } else if (compl_len == (size_t)(-1)) { // many matches mp_hal_stdout_tx_str(rl.prompt); mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len); redraw_from_cursor = true; } else { // one match for (size_t i = 0; i < compl_len; ++i) { vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++); } // set redraw parameters redraw_from_cursor = true; redraw_step_forward = compl_len; } #endif } else if (32 <= c && c <= 126) { // printable character vstr_ins_char(rl.line, rl.cursor_pos, c); // set redraw parameters redraw_from_cursor = true; redraw_step_forward = 1; } } else if (rl.escape_seq == ESEQ_ESC) { switch (c) { case '[': rl.escape_seq = ESEQ_ESC_BRACKET; break; case 'O': rl.escape_seq = ESEQ_ESC_O; break; #if MICROPY_REPL_EMACS_WORDS_MOVE case 'b': #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE backward_word: #endif redraw_step_back = cursor_count_word(0); rl.escape_seq = ESEQ_NONE; break; case 'f': #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE forward_word: #endif redraw_step_forward = cursor_count_word(1); rl.escape_seq = ESEQ_NONE; break; case 'd': vstr_cut_out_bytes(rl.line, rl.cursor_pos, cursor_count_word(1)); redraw_from_cursor = true; rl.escape_seq = ESEQ_NONE; break; case 127: #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE backward_kill_word: #endif redraw_step_back = cursor_count_word(0); vstr_cut_out_bytes(rl.line, rl.cursor_pos - redraw_step_back, redraw_step_back); redraw_from_cursor = true; rl.escape_seq = ESEQ_NONE; break; #endif default: DEBUG_printf("(ESC %d)", c); rl.escape_seq = ESEQ_NONE; break; } } else if (rl.escape_seq == ESEQ_ESC_BRACKET) { if ('0' <= c && c <= '9') { rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT; rl.escape_seq_buf[0] = c; } else { rl.escape_seq = ESEQ_NONE; if (c == 'A') { #if MICROPY_REPL_EMACS_KEYS up_arrow_key: #endif // up arrow if (rl.hist_cur + 1 < (int)READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) { // increase hist num rl.hist_cur += 1; // set line to history rl.line->len = rl.orig_line_len; vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]); // set redraw parameters redraw_step_back = rl.cursor_pos - rl.orig_line_len; redraw_from_cursor = true; redraw_step_forward = rl.line->len - rl.orig_line_len; } } else if (c == 'B') { #if MICROPY_REPL_EMACS_KEYS down_arrow_key: #endif // down arrow if (rl.hist_cur >= 0) { // decrease hist num rl.hist_cur -= 1; // set line to history vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len); if (rl.hist_cur >= 0) { vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]); } // set redraw parameters redraw_step_back = rl.cursor_pos - rl.orig_line_len; redraw_from_cursor = true; redraw_step_forward = rl.line->len - rl.orig_line_len; } } else if (c == 'C') { #if MICROPY_REPL_EMACS_KEYS right_arrow_key: #endif // right arrow if (rl.cursor_pos < rl.line->len) { redraw_step_forward = 1; } } else if (c == 'D') { #if MICROPY_REPL_EMACS_KEYS left_arrow_key: #endif // left arrow if (rl.cursor_pos > rl.orig_line_len) { redraw_step_back = 1; } } else if (c == 'H') { // home goto home_key; } else if (c == 'F') { // end goto end_key; } else { DEBUG_printf("(ESC [ %d)", c); } } } else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) { if (c == '~') { if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') { home_key: redraw_step_back = rl.cursor_pos - rl.orig_line_len; } else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') { end_key: redraw_step_forward = rl.line->len - rl.cursor_pos; } else if (rl.escape_seq_buf[0] == '3') { // delete #if MICROPY_REPL_EMACS_KEYS delete_key: #endif if (rl.cursor_pos < rl.line->len) { vstr_cut_out_bytes(rl.line, rl.cursor_pos, 1); redraw_from_cursor = true; } } else { DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE } else if (c == ';' && rl.escape_seq_buf[0] == '1') { // ';' is used to separate parameters. so first parameter was '1', // that's used for sequences like ctrl+left, which we will try to parse. // escape_seq state is reset back to ESEQ_ESC_BRACKET, as if we've just received // the opening bracket, because more parameters are to come. // we don't track the parameters themselves to keep low on logic and code size. that // might be required in the future if more complex sequences are added. rl.escape_seq = ESEQ_ESC_BRACKET; // goto away from the state-machine, as rl.escape_seq will be overridden. goto redraw; } else if (rl.escape_seq_buf[0] == '5' && c == 'C') { // ctrl+right goto forward_word; } else if (rl.escape_seq_buf[0] == '5' && c == 'D') { // ctrl+left goto backward_word; #endif } else { DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } rl.escape_seq = ESEQ_NONE; } else if (rl.escape_seq == ESEQ_ESC_O) { switch (c) { case 'H': goto home_key; case 'F': goto end_key; default: DEBUG_printf("(ESC O %d)", c); rl.escape_seq = ESEQ_NONE; } } else { rl.escape_seq = ESEQ_NONE; } #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE redraw: #endif // redraw command prompt, efficiently if (redraw_step_back > 0) { mp_hal_move_cursor_back(redraw_step_back); rl.cursor_pos -= redraw_step_back; } if (redraw_from_cursor) { if (rl.line->len < last_line_len) { // erase old chars mp_hal_erase_line_from_cursor(last_line_len - rl.cursor_pos); } // draw new chars mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos); // move cursor forward if needed (already moved forward by length of line, so move it back) mp_hal_move_cursor_back(rl.line->len - (rl.cursor_pos + redraw_step_forward)); rl.cursor_pos += redraw_step_forward; } else if (redraw_step_forward > 0) { // draw over old chars to move cursor forwards mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward); rl.cursor_pos += redraw_step_forward; } return -1; } #if MICROPY_REPL_AUTO_INDENT STATIC void readline_auto_indent(void) { vstr_t *line = rl.line; if (line->len > 1 && line->buf[line->len - 1] == '\n') { int i; for (i = line->len - 1; i > 0; i--) { if (line->buf[i - 1] == '\n') { break; } } size_t j; for (j = i; j < line->len; j++) { if (line->buf[j] != ' ') { break; } } // i=start of line; j=first non-space if (i > 0 && j + 1 == line->len) { // previous line is not first line and is all spaces for (size_t k = i - 1; k > 0; --k) { if (line->buf[k - 1] == '\n') { // don't auto-indent if last 2 lines are all spaces return; } else if (line->buf[k - 1] != ' ') { // 2nd previous line is not all spaces break; } } } int n = (j - i) / 4; if (line->buf[line->len - 2] == ':') { n += 1; } while (n-- > 0) { vstr_add_strn(line, " ", 4); mp_hal_stdout_tx_strn(" ", 4); rl.cursor_pos += 4; } } } #endif void readline_note_newline(const char *prompt) { rl.orig_line_len = rl.line->len; rl.cursor_pos = rl.orig_line_len; rl.prompt = prompt; mp_hal_stdout_tx_str(prompt); #if MICROPY_REPL_AUTO_INDENT readline_auto_indent(); #endif } void readline_init(vstr_t *line, const char *prompt) { rl.line = line; rl.orig_line_len = line->len; rl.escape_seq = ESEQ_NONE; rl.escape_seq_buf[0] = 0; rl.hist_cur = -1; rl.cursor_pos = rl.orig_line_len; rl.prompt = prompt; mp_hal_stdout_tx_str(prompt); #if MICROPY_REPL_AUTO_INDENT readline_auto_indent(); #endif } int readline(vstr_t *line, const char *prompt) { readline_init(line, prompt); for (;;) { int c = mp_hal_stdin_rx_chr(); int r = readline_process_char(c); if (r >= 0) { return r; } } } void readline_push_history(const char *line) { if (line[0] != '\0' && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(readline_hist)[0], line) != 0)) { // a line which is not empty and different from the last one // so update the history char *most_recent_hist = str_dup_maybe(line); if (most_recent_hist != NULL) { for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) { MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1]; } MP_STATE_PORT(readline_hist)[0] = most_recent_hist; } } } // ---------------- extmod/vfs_reader.c ---------------- // #if MICROPY_READER_VFS typedef struct _mp_reader_vfs_t { mp_obj_t file; uint16_t len; uint16_t pos; byte buf[24]; } mp_reader_vfs_t; STATIC mp_uint_t mp_reader_vfs_readbyte(void *data) { mp_reader_vfs_t *reader = (mp_reader_vfs_t *)data; if (reader->pos >= reader->len) { if (reader->len < sizeof(reader->buf)) { return MP_READER_EOF; } else { int errcode; reader->len = mp_stream_rw(reader->file, reader->buf, sizeof(reader->buf), &errcode, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); if (errcode != 0) { // TODO handle errors properly return MP_READER_EOF; } if (reader->len == 0) { return MP_READER_EOF; } reader->pos = 0; } } return reader->buf[reader->pos++]; } STATIC void mp_reader_vfs_close(void *data) { mp_reader_vfs_t *reader = (mp_reader_vfs_t *)data; mp_stream_close(reader->file); m_del_obj(mp_reader_vfs_t, reader); } void mp_reader_new_file(mp_reader_t *reader, const char *filename) { mp_reader_vfs_t *rf = m_new_obj(mp_reader_vfs_t); mp_obj_t args[2] = { mp_obj_new_str(filename, strlen(filename)), MP_OBJ_NEW_QSTR(MP_QSTR_rb), }; rf->file = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map); int errcode; rf->len = mp_stream_rw(rf->file, rf->buf, sizeof(rf->buf), &errcode, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); if (errcode != 0) { mp_raise_OSError(errcode); } rf->pos = 0; reader->data = rf; reader->readbyte = mp_reader_vfs_readbyte; reader->close = mp_reader_vfs_close; } #endif // MICROPY_READER_VFS // -------- GC goes last because nasty #define free... -------- // #ifndef __IPU__ #include #endif #include #include #include "py/mpconfig.h" #include "py/misc.h" #include "py/mpstate.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_printf DEBUG_printf #else // don't print debugging info #define DEBUG_printf(...) (void)0 #endif #if MICROPY_MEM_STATS #if !MICROPY_MALLOC_USES_ALLOCATED_SIZE #error MICROPY_MEM_STATS requires MICROPY_MALLOC_USES_ALLOCATED_SIZE #endif #define UPDATE_PEAK() { if (MP_STATE_MEM(current_bytes_allocated) > MP_STATE_MEM(peak_bytes_allocated)) MP_STATE_MEM(peak_bytes_allocated) = MP_STATE_MEM(current_bytes_allocated); } #endif #if MICROPY_ENABLE_GC #include "py/gc.h" // We redirect standard alloc functions to GC heap - just for the rest of // this module. In the rest of MicroPython source, system malloc can be // freely accessed - for interfacing with system and 3rd-party libs for // example. On the other hand, some (e.g. bare-metal) ports may use GC // heap as system heap, so, to avoid warnings, we do undef's first. #undef malloc #undef free #undef realloc #define malloc(b) gc_alloc((b), false) #define malloc_with_finaliser(b) gc_alloc((b), true) #define free gc_free #define realloc(ptr, n) gc_realloc(ptr, n, true) #define realloc_ext(ptr, n, mv) gc_realloc(ptr, n, mv) #else // GC is disabled. Use system malloc/realloc/free. #if MICROPY_ENABLE_FINALISER #error MICROPY_ENABLE_FINALISER requires MICROPY_ENABLE_GC #endif STATIC void *realloc_ext(void *ptr, size_t n_bytes, bool allow_move) { if (allow_move) { return realloc(ptr, n_bytes); } else { // We are asked to resize, but without moving the memory region pointed to // by ptr. Unless the underlying memory manager has special provision for // this behaviour there is nothing we can do except fail to resize. return NULL; } } #endif // MICROPY_ENABLE_GC void *m_malloc(size_t num_bytes) { void *ptr = malloc(num_bytes); if (ptr == NULL && num_bytes != 0) { m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS MP_STATE_MEM(total_bytes_allocated) += num_bytes; MP_STATE_MEM(current_bytes_allocated) += num_bytes; UPDATE_PEAK(); #endif DEBUG_printf("malloc %d : %p\n", num_bytes, ptr); return ptr; } void *m_malloc_maybe(size_t num_bytes) { void *ptr = malloc(num_bytes); #if MICROPY_MEM_STATS MP_STATE_MEM(total_bytes_allocated) += num_bytes; MP_STATE_MEM(current_bytes_allocated) += num_bytes; UPDATE_PEAK(); #endif DEBUG_printf("malloc %d : %p\n", num_bytes, ptr); return ptr; } #if MICROPY_ENABLE_FINALISER void *m_malloc_with_finaliser(size_t num_bytes) { void *ptr = malloc_with_finaliser(num_bytes); if (ptr == NULL && num_bytes != 0) { m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS MP_STATE_MEM(total_bytes_allocated) += num_bytes; MP_STATE_MEM(current_bytes_allocated) += num_bytes; UPDATE_PEAK(); #endif DEBUG_printf("malloc %d : %p\n", num_bytes, ptr); return ptr; } #endif void *m_malloc0(size_t num_bytes) { void *ptr = m_malloc(num_bytes); // If this config is set then the GC clears all memory, so we don't need to. #if !MICROPY_GC_CONSERVATIVE_CLEAR memset(ptr, 0, num_bytes); #endif return ptr; } #if MICROPY_MALLOC_USES_ALLOCATED_SIZE void *m_realloc(void *ptr, size_t old_num_bytes, size_t new_num_bytes) #else void *m_realloc(void *ptr, size_t new_num_bytes) #endif { void *new_ptr = realloc(ptr, new_num_bytes); if (new_ptr == NULL && new_num_bytes != 0) { m_malloc_fail(new_num_bytes); } #if MICROPY_MEM_STATS // At first thought, "Total bytes allocated" should only grow, // after all, it's *total*. But consider for example 2K block // shrunk to 1K and then grown to 2K again. It's still 2K // allocated total. If we process only positive increments, // we'll count 3K. size_t diff = new_num_bytes - old_num_bytes; MP_STATE_MEM(total_bytes_allocated) += diff; MP_STATE_MEM(current_bytes_allocated) += diff; UPDATE_PEAK(); #endif #if MICROPY_MALLOC_USES_ALLOCATED_SIZE DEBUG_printf("realloc %p, %d, %d : %p\n", ptr, old_num_bytes, new_num_bytes, new_ptr); #else DEBUG_printf("realloc %p, %d : %p\n", ptr, new_num_bytes, new_ptr); #endif return new_ptr; } #if MICROPY_MALLOC_USES_ALLOCATED_SIZE void *m_realloc_maybe(void *ptr, size_t old_num_bytes, size_t new_num_bytes, bool allow_move) #else void *m_realloc_maybe(void *ptr, size_t new_num_bytes, bool allow_move) #endif { void *new_ptr = realloc_ext(ptr, new_num_bytes, allow_move); #if MICROPY_MEM_STATS // At first thought, "Total bytes allocated" should only grow, // after all, it's *total*. But consider for example 2K block // shrunk to 1K and then grown to 2K again. It's still 2K // allocated total. If we process only positive increments, // we'll count 3K. // Also, don't count failed reallocs. if (!(new_ptr == NULL && new_num_bytes != 0)) { size_t diff = new_num_bytes - old_num_bytes; MP_STATE_MEM(total_bytes_allocated) += diff; MP_STATE_MEM(current_bytes_allocated) += diff; UPDATE_PEAK(); } #endif #if MICROPY_MALLOC_USES_ALLOCATED_SIZE DEBUG_printf("realloc %p, %d, %d : %p\n", ptr, old_num_bytes, new_num_bytes, new_ptr); #else DEBUG_printf("realloc %p, %d, %d : %p\n", ptr, new_num_bytes, new_ptr); #endif return new_ptr; } #if MICROPY_MALLOC_USES_ALLOCATED_SIZE void m_free(void *ptr, size_t num_bytes) #else void m_free(void *ptr) #endif { free(ptr); #if MICROPY_MEM_STATS MP_STATE_MEM(current_bytes_allocated) -= num_bytes; #endif #if MICROPY_MALLOC_USES_ALLOCATED_SIZE DEBUG_printf("free %p, %d\n", ptr, num_bytes); #else DEBUG_printf("free %p\n", ptr); #endif } #if MICROPY_MEM_STATS size_t m_get_total_bytes_allocated(void) { return MP_STATE_MEM(total_bytes_allocated); } size_t m_get_current_bytes_allocated(void) { return MP_STATE_MEM(current_bytes_allocated); } size_t m_get_peak_bytes_allocated(void) { return MP_STATE_MEM(peak_bytes_allocated); } #endif #if MICROPY_ENABLE_GC #undef malloc #undef malloc_with_finaliser #undef free #undef realloc #undef realloc_ext #endif // ----------------------------- "IPUpy" IPU port (here because I'm bad at linking) ------------------- // char *IPUpy_stack_top; #if MICROPY_ENABLE_GC void gc_collect(void) { // This gc_collect implementation doesn't try to get root pointers from registers. // Other micropython ports will try to do this in case GC is triggered by an interupt // but I don't think this is necessary on IPU (no interrupts) void** stack_bottom; asm volatile( "mov %[stack_bottom], $m11" : [stack_bottom] "+r" (stack_bottom) :: ); gc_collect_start(); gc_collect_root(stack_bottom, ((mp_uint_t)stack_bottom - (mp_uint_t)IPUpy_stack_top) / sizeof(mp_uint_t)); gc_collect_end(); // gc_dump_info(); } #endif void nlr_jump_fail(void *val) { asm("trap 0"); __builtin_unreachable(); } #if MICROPY_PY_ARRAY void IPUpy_add_memory_as_array(const char* name, void* data, size_t num_elts, char dtype) { mp_obj_array_t* var = array_new(dtype, 0); var->items = data; var->len = num_elts; var->free = 0; mp_obj_t key = mp_obj_new_str(name, strlen(name)); mp_obj_dict_store(&MP_STATE_VM(dict_main), key, var); } void IPUpy_add_memory_as_relocatable_array(const char* name, void*** data_ptr, size_t num_elts) { mp_obj_array_t* var = array_new('b', 0); *data_ptr = &var->items; **data_ptr = (void*) 0x80000; // A nice safe starting address, nothing to see here :D var->len = num_elts; var->free = 0; mp_obj_t key = mp_obj_new_str(name, strlen(name)); mp_obj_dict_store(&MP_STATE_VM(dict_main), key, var); } #endif void IPUpy_add_memory_as_string(const char* name, const char* data, size_t size) { mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); o_str->base.type = &mp_type_str; o_str->len = size; o_str->data = (const byte *)data; o_str->hash = qstr_compute_hash(o_str->data, o_str->len); mp_obj_t key = mp_obj_new_str(name, strlen(name)); mp_obj_dict_store(&MP_STATE_VM(dict_main), key, MP_OBJ_FROM_PTR(o_str)); } void IPUpy_add_int(const char* name, unsigned value) { mp_obj_t var = MP_OBJ_NEW_SMALL_INT(value); mp_obj_t key = mp_obj_new_str(name, strlen(name)); mp_obj_dict_store(&MP_STATE_VM(dict_main), key, var); } #if MICROPY_ENABLE_COMPILER void IPUpy_do_str(const char *src, int is_single_line) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); qstr source_name = lex->source_name; mp_parse_input_kind_t input_kind = is_single_line? MP_PARSE_SINGLE_INPUT : MP_PARSE_FILE_INPUT; mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true); mp_call_function_0(module_fun); nlr_pop(); } else { // uncaught exception mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); } } #endif char * IPUpy_stdout_head = NULL; char * IPUpy_stdout_end = NULL; void IPUpy_set_stdout(char* _stdout, int len) { IPUpy_stdout_head = _stdout; if (IPUpy_stdout_head != NULL) { *IPUpy_stdout_head = '\0'; IPUpy_stdout_end = IPUpy_stdout_head + (len - 1); } } void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { if (IPUpy_stdout_head == NULL) return; for (int i = 0; i < len && IPUpy_stdout_head < IPUpy_stdout_end; ++i, ++IPUpy_stdout_head) { *IPUpy_stdout_head = str[i]; } *IPUpy_stdout_head = '\0'; } // Send zero-terminated string void mp_hal_stdout_tx_str(const char *str) { mp_hal_stdout_tx_strn(str, strlen(str)); } uint64_t mp_hal_time_ns(void) { unsigned int U; // get $COUNT_U asm volatile("get %[U], 0x61": [U] "+r"(U) ::); return U; } void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) { const char *last = str; while (len--) { if (*str == '\n') { if (str > last) { mp_hal_stdout_tx_strn(last, str - last); } mp_hal_stdout_tx_strn("\r\n", 2); ++str; last = str; } else { ++str; } } if (str > last) { mp_hal_stdout_tx_strn(last, str - last); } } #if MICROPY_ENABLE_GC static char IPUpy_heap[250*1024]; #endif #if MICROPY_ENABLE_PYSTACK static mp_obj_t IPUpy_pystack[5 * 1024]; #endif void IPUpy_init(char *poplar_stack_bottom) { IPUpy_stack_top = poplar_stack_bottom; #if MICROPY_ENABLE_GC gc_init(IPUpy_heap, IPUpy_heap + sizeof(IPUpy_heap)); #endif #if MICROPY_ENABLE_PYSTACK mp_pystack_init(IPUpy_pystack, &IPUpy_pystack[MP_ARRAY_SIZE(IPUpy_pystack)]); #endif mp_init(); } void IPUpy_deinit() { mp_deinit(); } char * IPUpy_stdin_head = NULL; void IPUpy_set_stdin(char* _stdin) { IPUpy_stdin_head = _stdin; } int mp_hal_stdin_rx_chr(void) { if (NULL == IPUpy_stdin_head) return '\n'; char c = *IPUpy_stdin_head; IPUpy_stdin_head += 1; return c; }