diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95b5379d59..5a3883e809 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,27 +71,18 @@ jobs: run: make -C mpy-cross -j2 - name: Build unix port run: | - make -C ports/unix deplibs -j2 - make -C ports/unix -j2 - make -C ports/unix coverage -j2 + make -C ports/unix VARIANT=coverage -j2 - name: Test all - run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython_coverage ./run-tests -j1 + run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-tests -j1 working-directory: tests - - name: Print failure info - run: | - shopt -s nullglob; - for exp in *.exp; - do testbase=$(basename $exp .exp); - echo -e "\nFAILURE $testbase"; - diff -u $testbase.exp $testbase.out; - done - working-directory: tests - if: failure() - name: Native Tests - run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython_coverage ./run-tests -j1 --emit native + run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-tests -j1 --emit native working-directory: tests - name: mpy Tests - run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython_coverage ./run-tests -j1 --via-mpy -d basics float + run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-tests -j1 --mpy-cross-flags='-mcache-lookup-bc' --via-mpy -d basics float micropython + working-directory: tests + - name: Native mpy Tests + run: MICROPY_CPYTHON3=python3.8 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-tests -j1 --mpy-cross-flags='-mcache-lookup-bc' --via-mpy --emit native -d basics float micropython working-directory: tests - name: Build mpy-cross.static-raspbian run: make -C mpy-cross -j2 -f Makefile.static-raspbian diff --git a/.gitignore b/.gitignore index 2e2a336328..de649ad3a0 100644 --- a/.gitignore +++ b/.gitignore @@ -37,8 +37,7 @@ build-*/ # Test failure outputs ###################### -tests/*.exp -tests/*.out +tests/results/* # Python cache files ###################### diff --git a/LICENSE b/LICENSE index e6a54cf269..8c5e4fe4c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2019 Damien P. George +Copyright (c) 2013-2020 Damien P. George Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 5857299c51..f1a4f84331 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ # You can set these variables from the command line. PYTHON = python3 -SPHINXOPTS = +SPHINXOPTS = -W --keep-going SPHINXBUILD = sphinx-build PAPER = # path to build the generated docs @@ -223,7 +223,7 @@ pseudoxml: all-source: locale/circuitpython.pot: all-source - find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -f- -L C -s --add-location=file --keyword=translate -o - | sed -e '/"POT-Creation-Date: /d' > $@ + find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -f- -L C -s --add-location=file --keyword=translate --keyword=MP_ERROR_TEXT -o - | sed -e '/"POT-Creation-Date: /d' > $@ # Historically, `make translate` updated the .pot file and ran msgmerge. # However, this was a frequent source of merge conflicts. Weblate can perform @@ -248,7 +248,7 @@ merge-translate: .PHONY: check-translate check-translate: - find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -f- -L C -s --add-location=file --keyword=translate -o circuitpython.pot.tmp -p locale + find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -f- -L C -s --add-location=file --keyword=translate --keyword=MP_ERROR_TEXT -o circuitpython.pot.tmp -p locale $(PYTHON) tools/check_translations.py locale/circuitpython.pot.tmp locale/circuitpython.pot; status=$$?; rm -f locale/circuitpython.pot.tmp; exit $$status stubs: diff --git a/docs/design_guide.rst b/docs/design_guide.rst index 04a2cc874f..9c4137b859 100644 --- a/docs/design_guide.rst +++ b/docs/design_guide.rst @@ -541,10 +541,10 @@ object instead of the pins themselves. This allows the calling code to provide any object with the appropriate methods such as an I2C expansion board. Another example is to expect a :py:class:`~digitalio.DigitalInOut` for a pin to -toggle instead of a :py:class:`~microcontroller.Pin` from `board`. Taking in the -:py:class:`~microcontroller.Pin` object alone would limit the driver to pins on -the actual microcontroller instead of pins provided by another driver such as an -IO expander. +toggle instead of a :py:class:`~microcontroller.Pin` from :py:mod:`board`. +Taking in the :py:class:`~microcontroller.Pin` object alone would limit the +driver to pins on the actual microcontroller instead of pins provided by another +driver such as an IO expander. Lots of small modules -------------------------------------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 3ee76fdd8e..6e9789852e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,6 +36,7 @@ Full Table of Contents :caption: MicroPython specific library/index.rst + reference/glossary.rst .. toctree:: :maxdepth: 1 diff --git a/docs/library/btree.rst b/docs/library/btree.rst index 4c7b30d5ca..0dafe3f866 100644 --- a/docs/library/btree.rst +++ b/docs/library/btree.rst @@ -78,7 +78,7 @@ Example:: Functions --------- -.. function:: open(stream, \*, flags=0, pagesize=0, cachesize=0, minkeypage=0) +.. function:: open(stream, *, flags=0, pagesize=0, cachesize=0, minkeypage=0) Open a database from a random-access ``stream`` (like an open file). All other parameters are optional and keyword-only, and allow to tweak advanced @@ -118,7 +118,7 @@ Methods Flush any data in cache to the underlying stream. .. method:: btree.__getitem__(key) - btree.get(key, default=None) + btree.get(key, default=None, /) btree.__setitem__(key, val) btree.__detitem__(key) btree.__contains__(key) diff --git a/docs/library/errno.rst b/docs/library/errno.rst index 96777b2052..def01362f1 100644 --- a/docs/library/errno.rst +++ b/docs/library/errno.rst @@ -1,12 +1,13 @@ -:mod:`errno` -- system error codes +:mod:`uerrno` -- system error codes =================================== -.. module:: errno +.. module:: uerrno :synopsis: system error codes -|see_cpython_module| :mod:`cpython:errno`. +|see_cpython_module| :mod:`python:errno`. This module provides access to symbolic error codes for `OSError` exception. +A particular inventory of codes depends on :term:`MicroPython port`. Constants --------- @@ -14,13 +15,14 @@ Constants .. data:: EEXIST, EAGAIN, etc. Error codes, based on ANSI C/POSIX standard. All error codes start with - "E". Errors are usually accessible as ``exc.args[0]`` + "E". As mentioned above, inventory of the codes depends on + :term:`MicroPython port`. Errors are usually accessible as ``exc.args[0]`` where ``exc`` is an instance of `OSError`. Usage example:: try: - os.mkdir("my_dir") + uos.mkdir("my_dir") except OSError as exc: - if exc.args[0] == errno.EEXIST: + if exc.args[0] == uerrno.EEXIST: print("Directory already exists") .. data:: errorcode @@ -28,5 +30,5 @@ Constants Dictionary mapping numeric error codes to strings with symbolic error code (see above):: - >>> print(errno.errorcode[uerrno.EEXIST]) + >>> print(uerrno.errorcode[uerrno.EEXIST]) EEXIST diff --git a/docs/library/framebuf.rst b/docs/library/framebuf.rst index 7db155593b..a9cbc2fc47 100644 --- a/docs/library/framebuf.rst +++ b/docs/library/framebuf.rst @@ -1,4 +1,4 @@ -:mod:`framebuf` --- Frame buffer manipulation +:mod:`framebuf` --- frame buffer manipulation ============================================= .. include:: ../templates/unsupported_in_circuitpython.inc @@ -30,7 +30,7 @@ For example:: Constructors ------------ -.. class:: FrameBuffer(buffer, width, height, format, stride=width) +.. class:: FrameBuffer(buffer, width, height, format, stride=width, /) Construct a FrameBuffer object. The parameters are: @@ -132,7 +132,7 @@ Constants Monochrome (1-bit) color format This defines a mapping where the bits in a byte are horizontally mapped. - Each byte occupies 8 horizontal pixels with bit 0 being the leftmost. + Each byte occupies 8 horizontal pixels with bit 7 being the leftmost. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached. Further bytes are rendered on the next row, one pixel lower. @@ -141,7 +141,7 @@ Constants Monochrome (1-bit) color format This defines a mapping where the bits in a byte are horizontally mapped. - Each byte occupies 8 horizontal pixels with bit 7 being the leftmost. + Each byte occupies 8 horizontal pixels with bit 0 being the leftmost. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached. Further bytes are rendered on the next row, one pixel lower. diff --git a/docs/library/index.rst b/docs/library/index.rst index d490ae2277..9a7f9afd84 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -37,6 +37,7 @@ with the ``u`` prefix dropped: json.rst re.rst sys.rst + uasyncio.rst uctypes.rst uselect.rst uzlib.rst diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index fb6a8e82f1..2623aab582 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -73,17 +73,26 @@ Functions .. function:: heap_lock() .. function:: heap_unlock() +.. function:: heap_locked() Lock or unlock the heap. When locked no memory allocation can occur and a `MemoryError` will be raised if any heap allocation is attempted. + `heap_locked()` returns a true value if the heap is currently locked. These functions can be nested, ie `heap_lock()` can be called multiple times in a row and the lock-depth will increase, and then `heap_unlock()` must be called the same number of times to make the heap available again. + Both `heap_unlock()` and `heap_locked()` return the current lock depth + (after unlocking for the former) as a non-negative integer, with 0 meaning + the heap is not locked. + If the REPL becomes active with the heap locked then it will be forcefully unlocked. + Note: `heap_locked()` is not enabled on most ports by default, + requires ``MICROPY_PY_MICROPYTHON_HEAP_LOCKED``. + .. function:: kbd_intr(chr) Set the character that will raise a `KeyboardInterrupt` exception. By diff --git a/docs/library/re.rst b/docs/library/re.rst index 8cbd43e83b..12e640654e 100644 --- a/docs/library/re.rst +++ b/docs/library/re.rst @@ -124,7 +124,7 @@ Functions string for first position which matches regex (which still may be 0 if regex is anchored). -.. function:: sub(regex_str, replace, string, count=0, flags=0) +.. function:: sub(regex_str, replace, string, count=0, flags=0, /) Compile *regex_str* and search for it in *string*, replacing all matches with *replace*, and returning the new string. @@ -138,11 +138,12 @@ Functions If *count* is specified and non-zero then substitution will stop after this many substitutions are made. The *flags* argument is ignored. - Note: availability of this function depends on MicroPython port. + Note: availability of this function depends on :term:`MicroPython port`. .. data:: DEBUG Flag value, display debug information about compiled expression. + (Availability depends on :term:`MicroPython port`.) .. _regex: @@ -155,14 +156,14 @@ Compiled regular expression. Instances of this class are created using .. method:: regex.match(string) regex.search(string) - regex.sub(replace, string, count=0, flags=0) + regex.sub(replace, string, count=0, flags=0, /) Similar to the module-level functions :meth:`match`, :meth:`search` and :meth:`sub`. Using methods is (much) more efficient if the same regex is applied to multiple strings. -.. method:: regex.split(string, max_split=-1) +.. method:: regex.split(string, max_split=-1, /) Split a *string* using regex. If *max_split* is given, it specifies maximum number of splits to perform. Returns list of strings (there @@ -183,7 +184,7 @@ to the replacement function in `sub()`. Return a tuple containing all the substrings of the groups of the match. - Note: availability of this method depends on MicroPython port. + Note: availability of this method depends on :term:`MicroPython port`. .. method:: match.start([index]) match.end([index]) @@ -192,10 +193,10 @@ to the replacement function in `sub()`. substring group that was matched. *index* defaults to the entire group, otherwise it will select a group. - Note: availability of these methods depends on MicroPython port. + Note: availability of these methods depends on :term:`MicroPython port`. .. method:: match.span([index]) Returns the 2-tuple ``(match.start(index), match.end(index))``. - Note: availability of this method depends on MicroPython port. + Note: availability of this method depends on :term:`MicroPython port`. diff --git a/docs/library/sys.rst b/docs/library/sys.rst index a369fe3d85..e37f722450 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -11,7 +11,7 @@ Functions --------- -.. function:: exit(retval=0) +.. function:: exit(retval=0, /) Terminate current program with a given exit code. Underlyingly, this function raise as `SystemExit` exception. If an argument is given, its diff --git a/docs/library/uasyncio.rst b/docs/library/uasyncio.rst new file mode 100644 index 0000000000..15f3cc2015 --- /dev/null +++ b/docs/library/uasyncio.rst @@ -0,0 +1,303 @@ +:mod:`uasyncio` --- asynchronous I/O scheduler +============================================== + +.. module:: uasyncio + :synopsis: asynchronous I/O scheduler for writing concurrent code + +|see_cpython_module| +`asyncio `_ + +Example:: + + import uasyncio + + async def blink(led, period_ms): + while True: + led.on() + await uasyncio.sleep_ms(5) + led.off() + await uasyncio.sleep_ms(period_ms) + + async def main(led1, led2): + uasyncio.create_task(blink(led1, 700)) + uasyncio.create_task(blink(led2, 400)) + await uasyncio.sleep_ms(10_000) + + # Running on a pyboard + from pyb import LED + uasyncio.run(main(LED(1), LED(2))) + + # Running on a generic board + from machine import Pin + uasyncio.run(main(Pin(1), Pin(2))) + +Core functions +-------------- + +.. function:: create_task(coro) + + Create a new task from the given coroutine and schedule it to run. + + Returns the corresponding `Task` object. + +.. function:: run(coro) + + Create a new task from the given coroutine and run it until it completes. + + Returns the value returned by *coro*. + +.. function:: sleep(t) + + Sleep for *t* seconds (can be a float). + + This is a coroutine. + +.. function:: sleep_ms(t) + + Sleep for *t* milliseconds. + + This is a coroutine, and a MicroPython extension. + +Additional functions +-------------------- + +.. function:: wait_for(awaitable, timeout) + + Wait for the *awaitable* to complete, but cancel it if it takes longer + that *timeout* seconds. If *awaitable* is not a task then a task will be + created from it. + + If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: + this should be trapped by the caller. + + Returns the return value of *awaitable*. + + This is a coroutine. + +.. function:: wait_for_ms(awaitable, timeout) + + Similar to `wait_for` but *timeout* is an integer in milliseconds. + + This is a coroutine, and a MicroPython extension. + +.. function:: gather(*awaitables, return_exceptions=False) + + Run all *awaitables* concurrently. Any *awaitables* that are not tasks are + promoted to tasks. + + Returns a list of return values of all *awaitables*. + + This is a coroutine. + +class Task +---------- + +.. class:: Task() + + This object wraps a coroutine into a running task. Tasks can be waited on + using ``await task``, which will wait for the task to complete and return + the return value of the task. + + Tasks should not be created directly, rather use `create_task` to create them. + +.. method:: Task.cancel() + + Cancel the task by injecting a ``CancelledError`` into it. The task may + or may not ignore this exception. + +class Event +----------- + +.. class:: Event() + + Create a new event which can be used to synchronise tasks. Events start + in the cleared state. + +.. method:: Event.is_set() + + Returns ``True`` if the event is set, ``False`` otherwise. + +.. method:: Event.set() + + Set the event. Any tasks waiting on the event will be scheduled to run. + +.. method:: Event.clear() + + Clear the event. + +.. method:: Event.wait() + + Wait for the event to be set. If the event is already set then it returns + immediately. + + This is a coroutine. + +class Lock +---------- + +.. class:: Lock() + + Create a new lock which can be used to coordinate tasks. Locks start in + the unlocked state. + + In addition to the methods below, locks can be used in an ``async with`` statement. + +.. method:: Lock.locked() + + Returns ``True`` if the lock is locked, otherwise ``False``. + +.. method:: Lock.acquire() + + Wait for the lock to be in the unlocked state and then lock it in an atomic + way. Only one task can acquire the lock at any one time. + + This is a coroutine. + +.. method:: Lock.release() + + Release the lock. If any tasks are waiting on the lock then the next one in the + queue is scheduled to run and the lock remains locked. Otherwise, no tasks are + waiting an the lock becomes unlocked. + +TCP stream connections +---------------------- + +.. function:: open_connection(host, port) + + Open a TCP connection to the given *host* and *port*. The *host* address will be + resolved using `socket.getaddrinfo`, which is currently a blocking call. + + Returns a pair of streams: a reader and a writer stream. + Will raise a socket-specific ``OSError`` if the host could not be resolved or if + the connection could not be made. + + This is a coroutine. + +.. function:: start_server(callback, host, port, backlog=5) + + Start a TCP server on the given *host* and *port*. The *callback* will be + called with incoming, accepted connections, and be passed 2 arguments: reader + and writer streams for the connection. + + Returns a `Server` object. + + This is a coroutine. + +.. class:: Stream() + + This represents a TCP stream connection. To minimise code this class implements + both a reader and a writer, and both ``StreamReader`` and ``StreamWriter`` alias to + this class. + +.. method:: Stream.get_extra_info(v) + + Get extra information about the stream, given by *v*. The valid values for *v* are: + ``peername``. + +.. method:: Stream.close() + + Close the stream. + +.. method:: Stream.wait_closed() + + Wait for the stream to close. + + This is a coroutine. + +.. method:: Stream.read(n) + + Read up to *n* bytes and return them. + + This is a coroutine. + +.. method:: Stream.readline() + + Read a line and return it. + + This is a coroutine. + +.. method:: Stream.write(buf) + + Accumulated *buf* to the output buffer. The data is only flushed when + `Stream.drain` is called. It is recommended to call `Stream.drain` immediately + after calling this function. + +.. method:: Stream.drain() + + Drain (write) all buffered output data out to the stream. + + This is a coroutine. + +.. class:: Server() + + This represents the server class returned from `start_server`. It can be used + in an ``async with`` statement to close the server upon exit. + +.. method:: Server.close() + + Close the server. + +.. method:: Server.wait_closed() + + Wait for the server to close. + + This is a coroutine. + +Event Loop +---------- + +.. function:: get_event_loop() + + Return the event loop used to schedule and run tasks. See `Loop`. + +.. function:: new_event_loop() + + Reset the event loop and return it. + + Note: since MicroPython only has a single event loop this function just + resets the loop's state, it does not create a new one. + +.. class:: Loop() + + This represents the object which schedules and runs tasks. It cannot be + created, use `get_event_loop` instead. + +.. method:: Loop.create_task(coro) + + Create a task from the given *coro* and return the new `Task` object. + +.. method:: Loop.run_forever() + + Run the event loop until `stop()` is called. + +.. method:: Loop.run_until_complete(awaitable) + + Run the given *awaitable* until it completes. If *awaitable* is not a task + then it will be promoted to one. + +.. method:: Loop.stop() + + Stop the event loop. + +.. method:: Loop.close() + + Close the event loop. + +.. method:: Loop.set_exception_handler(handler) + + Set the exception handler to call when a Task raises an exception that is not + caught. The *handler* should accept two arguments: ``(loop, context)``. + +.. method:: Loop.get_exception_handler() + + Get the current exception handler. Returns the handler, or ``None`` if no + custom handler is set. + +.. method:: Loop.default_exception_handler(context) + + The default exception handler that is called. + +.. method:: Loop.call_exception_handler(context) + + Call the current exception handler. The argument *context* is passed through and + is a dictionary containing keys: ``'message'``, ``'exception'``, ``'future'``. diff --git a/docs/library/uctypes.rst b/docs/library/uctypes.rst index fb81e3377e..80f88a39d8 100644 --- a/docs/library/uctypes.rst +++ b/docs/library/uctypes.rst @@ -182,7 +182,7 @@ Following are encoding examples for various field types: Module contents --------------- -.. class:: struct(addr, descriptor, layout_type=NATIVE) +.. class:: struct(addr, descriptor, layout_type=NATIVE, /) Instantiate a "foreign data structure" object based on structure address in memory, descriptor (encoded as a dictionary), and layout type (see below). @@ -202,7 +202,7 @@ Module contents Layout type for a native structure - with data endianness and alignment conforming to the ABI of the system on which MicroPython runs. -.. function:: sizeof(struct, layout_type=NATIVE) +.. function:: sizeof(struct, layout_type=NATIVE, /) Return size of data structure in bytes. The *struct* argument can be either a structure class or a specific instantiated structure object diff --git a/docs/library/uselect.rst b/docs/library/uselect.rst index cdf12fac26..a01c5b62dd 100644 --- a/docs/library/uselect.rst +++ b/docs/library/uselect.rst @@ -60,7 +60,7 @@ Methods Modify the *eventmask* for *obj*. If *obj* is not registered, `OSError` is raised with error of ENOENT. -.. method:: poll.poll(timeout=-1) +.. method:: poll.poll(timeout=-1, /) Wait for at least one of the registered objects to become ready or have an exceptional condition, with optional timeout in milliseconds (if *timeout* @@ -83,7 +83,7 @@ Methods Tuples returned may contain more than 2 elements as described above. -.. method:: poll.ipoll(timeout=-1, flags=0) +.. method:: poll.ipoll(timeout=-1, flags=0, /) Like :meth:`poll.poll`, but instead returns an iterator which yields a ``callee-owned tuples``. This function provides efficient, allocation-free diff --git a/docs/library/uzlib.rst b/docs/library/uzlib.rst index ba08b535cf..9a6c471f76 100644 --- a/docs/library/uzlib.rst +++ b/docs/library/uzlib.rst @@ -16,7 +16,7 @@ is not yet implemented. Functions --------- -.. function:: decompress(data, wbits=0, bufsize=0) +.. function:: decompress(data, wbits=0, bufsize=0, /) Return decompressed *data* as bytes. *wbits* is DEFLATE dictionary window size used during compression (8-15, the dictionary size is power of 2 of @@ -25,7 +25,7 @@ Functions to be raw DEFLATE stream. *bufsize* parameter is for compatibility with CPython and is ignored. -.. class:: DecompIO(stream, wbits=0) +.. class:: DecompIO(stream, wbits=0, /) Create a ``stream`` wrapper which allows transparent decompression of compressed data in another *stream*. This allows to process compressed diff --git a/docs/reference/glossary.rst b/docs/reference/glossary.rst new file mode 100644 index 0000000000..5aae70af0a --- /dev/null +++ b/docs/reference/glossary.rst @@ -0,0 +1,175 @@ +Glossary +======== + +.. glossary:: + + baremetal + A system without a (full-fledged) operating system, for example an + :term:`MCU`-based system. When running on a baremetal system, + MicroPython effectively functions like a small operating system, + running user programs and providing a command interpreter + (:term:`REPL`). + + buffer protocol + Any Python object that can be automatically converted into bytes, such + as ``bytes``, ``bytearray``, ``memoryview`` and ``str`` objects, which + all implement the "buffer protocol". + + board + Typically this refers to a printed circuit board (PCB) containing a + :term:`microcontroller ` and supporting components. + MicroPython firmware is typically provided per-board, as the firmware + contains both MCU-specific functionality but also board-level + functionality such as drivers or pin names. + + bytecode + A compact representation of a Python program that generated by + compiling the Python source code. This is what the VM actually + executes. Bytecode is typically generated automatically at runtime and + is invisible to the user. Note that while :term:`CPython` and + MicroPython both use bytecode, the format is different. You can also + pre-compile source code offline using the :term:`cross-compiler`. + + callee-owned tuple + This is a MicroPython-specific construct where, for efficiency + reasons, some built-in functions or methods may re-use the same + underlying tuple object to return data. This avoids having to allocate + a new tuple for every call, and reduces heap fragmentation. Programs + should not hold references to callee-owned tuples and instead only + extract data from them (or make a copy). + + CircuitPython + A variant of MicroPython developed by `Adafruit Industries + `_. + + CPython + CPython is the reference implementation of the Python programming + language, and the most well-known one. It is, however, one of many + implementations (including Jython, IronPython, PyPy, and MicroPython). + While MicroPython's implementation differs substantially from CPython, + it aims to maintain as much compatibility as possible. + + cross-compiler + Also known as ``mpy-cross``. This tool runs on your PC and converts a + :term:`.py file` containing MicroPython code into a :term:`.mpy file` + containing MicroPython bytecode. This means it loads faster (the board + doesn't have to compile the code), and uses less space on flash (the + bytecode is more space efficient). + + driver + A MicroPython library that implements support for a particular + component, such as a sensor or display. + + FFI + Acronym for Foreign Function Interface. A mechanism used by the + :term:`MicroPython Unix port` to access operating system functionality. + This is not available on :term:`baremetal` ports. + + filesystem + Most MicroPython ports and boards provide a filesystem stored in flash + that is available to user code via the standard Python file APIs such + as ``open()``. Some boards also make this internal filesystem + accessible to the host via USB mass-storage. + + frozen module + A Python module that has been cross compiled and bundled into the + firmware image. This reduces RAM requirements as the code is executed + directly from flash. + + Garbage Collector + A background process that runs in Python (and MicroPython) to reclaim + unused memory in the :term:`heap`. + + GPIO + General-purpose input/output. The simplest means to control electrical + signals (commonly referred to as "pins") on a microcontroller. GPIO + typically allows pins to be either input or output, and to set or get + their digital value (logical "0" or "1"). MicroPython abstracts GPIO + access using the :class:`machine.Pin` and :class:`machine.Signal` + classes. + + GPIO port + A group of :term:`GPIO` pins, usually based on hardware properties of + these pins (e.g. controllable by the same register). + + heap + A region of RAM where MicroPython stores dynamic data. It is managed + automatically by the :term:`Garbage Collector`. Different MCUs and + boards have vastly different amounts of RAM available for the heap, so + this will affect how complex your program can be. + + interned string + An optimisation used by MicroPython to improve the efficiency of + working with strings. An interned string is referenced by its (unique) + identity rather than its address and can therefore be quickly compared + just by its identifier. It also means that identical strings can be + de-duplicated in memory. String interning is almost always invisible to + the user. + + MCU + Microcontroller. Microcontrollers usually have much less resources + than a desktop, laptop, or phone, but are smaller, cheaper and + require much less power. MicroPython is designed to be small and + optimized enough to run on an average modern microcontroller. + + MicroPython port + MicroPython supports different :term:`boards `, RTOSes, and + OSes, and can be relatively easily adapted to new systems. MicroPython + with support for a particular system is called a "port" to that + system. Different ports may have widely different functionality. This + documentation is intended to be a reference of the generic APIs + available across different ports ("MicroPython core"). Note that some + ports may still omit some APIs described here (e.g. due to resource + constraints). Any such differences, and port-specific extensions + beyond the MicroPython core functionality, would be described in the + separate port-specific documentation. + + MicroPython Unix port + The unix port is one of the major :term:`MicroPython ports + `. It is intended to run on POSIX-compatible + operating systems, like Linux, MacOS, FreeBSD, Solaris, etc. It also + serves as the basis of Windows port. The Unix port is very useful for + quick development and testing of the MicroPython language and + machine-independent features. It can also function in a similar way to + :term:`CPython`'s ``python`` executable. + + .mpy file + The output of the :term:`cross-compiler`. A compiled form of a + :term:`.py file` that contains MicroPython bytecode instead of Python + source code. + + native + Usually refers to "native code", i.e. machine code for the target + microcontroller (such as ARM Thumb, Xtensa, x86/x64). The ``@native`` + decorator can be applied to a MicroPython function to generate native + code instead of bytecode for that function, which will likely be + faster but use more RAM. + + port + Usually short for :term:`MicroPython port`, but could also refer to + :term:`GPIO port`. + + .py file + A file containing Python source code. + + REPL + An acronym for "Read, Eval, Print, Loop". This is the interactive + Python prompt, useful for debugging or testing short snippets of code. + Most MicroPython boards make a REPL available over a UART, and this is + typically accessible on a host PC via USB. + + stream + Also known as a "file-like object". An Python object which provides + sequential read-write access to the underlying data. A stream object + implements a corresponding interface, which consists of methods like + ``read()``, ``write()``, ``readinto()``, ``seek()``, ``flush()``, + ``close()``, etc. A stream is an important concept in MicroPython; + many I/O objects implement the stream interface, and thus can be used + consistently and interchangeably in different contexts. For more + information on streams in MicroPython, see the `io` module. + + UART + Acronym for "Universal Asynchronous Receiver/Transmitter". This is a + peripheral that sends data over a pair of pins (TX & RX). Many boards + include a way to make at least one of the UARTs available to a host PC + as a serial port over USB. diff --git a/docs/shared_bindings_matrix.py b/docs/shared_bindings_matrix.py index e87769f06c..4db365b754 100644 --- a/docs/shared_bindings_matrix.py +++ b/docs/shared_bindings_matrix.py @@ -224,7 +224,7 @@ def support_matrix_by_board(use_branded_name=True): # flatmap with comprehensions boards = dict(sorted([board for matrix in mapped_exec for board in matrix])) - #print(json.dumps(boards, indent=2)) + # print(json.dumps(boards, indent=2)) return boards if __name__ == '__main__': diff --git a/docs/templates/replace.inc b/docs/templates/replace.inc index 2636045f6e..14f1875eee 100644 --- a/docs/templates/replace.inc +++ b/docs/templates/replace.inc @@ -4,6 +4,6 @@ .. |see_cpython_module| replace:: - *This module implements a subset of the corresponding* ``CPython`` *module, - as described below. For more information, refer to the original* - ``CPython`` *documentation:* + *This module implements a subset of the corresponding* :term:`CPython` *module, + as described below. For more information, refer to the original + CPython documentation:* diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk new file mode 100644 index 0000000000..dd96e63379 --- /dev/null +++ b/extmod/btstack/btstack.mk @@ -0,0 +1,57 @@ +# Makefile directives for BlueKitchen BTstack + +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) + +MICROPY_BLUETOOTH_BTSTACK_USB ?= 0 + +BTSTACK_EXTMOD_DIR = extmod/btstack + +EXTMOD_SRC_C += extmod/btstack/modbluetooth_btstack.c + +INC += -I$(TOP)/$(BTSTACK_EXTMOD_DIR) + +CFLAGS_MOD += -DMICROPY_BLUETOOTH_BTSTACK=1 + +BTSTACK_DIR = $(TOP)/lib/btstack + +ifneq ($(wildcard $(BTSTACK_DIR)/src),) + +include $(BTSTACK_DIR)/src/Makefile.inc +include $(BTSTACK_DIR)/src/ble/Makefile.inc + +INC += -I$(BTSTACK_DIR)/src +INC += -I$(BTSTACK_DIR)/3rd-party/bluedroid/decoder/include +INC += -I$(BTSTACK_DIR)/3rd-party/bluedroid/encoder/include +INC += -I$(BTSTACK_DIR)/3rd-party/md5 +INC += -I$(BTSTACK_DIR)/3rd-party/yxml + +SRC_BTSTACK = \ + $(addprefix lib/btstack/src/, $(SRC_FILES)) \ + $(addprefix lib/btstack/src/ble/, $(filter-out %_tlv.c, $(SRC_BLE_FILES))) \ + lib/btstack/platform/embedded/btstack_run_loop_embedded.c + +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1) +SRC_BTSTACK += \ + lib/btstack/platform/libusb/hci_transport_h2_libusb.c + +CFLAGS += $(shell pkg-config libusb-1.0 --cflags) +LDFLAGS += $(shell pkg-config libusb-1.0 --libs) +endif + +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_ENABLE_CLASSIC),1) +include $(BTSTACK_DIR)/src/classic/Makefile.inc +SRC_BTSTACK += \ + $(addprefix lib/btstack/src/classic/, $(SRC_CLASSIC_FILES)) +endif + +LIB_SRC_C += $(SRC_BTSTACK) + +# Suppress some warnings. +BTSTACK_WARNING_CFLAGS = -Wno-old-style-definition -Wno-unused-variable -Wno-unused-parameter +ifneq ($(CC),clang) +BTSTACK_WARNING_CFLAGS += -Wno-format +endif +$(BUILD)/lib/btstack/src/%.o: CFLAGS += $(BTSTACK_WARNING_CFLAGS) + +endif +endif diff --git a/extmod/btstack/btstack_config.h b/extmod/btstack/btstack_config.h new file mode 100644 index 0000000000..f420f47a5b --- /dev/null +++ b/extmod/btstack/btstack_config.h @@ -0,0 +1,47 @@ +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H + +// BTstack features that can be enabled +#define ENABLE_BLE +#define ENABLE_LE_PERIPHERAL +#define ENABLE_LE_CENTRAL +// #define ENABLE_CLASSIC +#define ENABLE_LE_DATA_CHANNELS +// #define ENABLE_LOG_INFO +#define ENABLE_LOG_ERROR + +// BTstack configuration. buffers, sizes, ... +#define HCI_ACL_PAYLOAD_SIZE 1021 +#define MAX_NR_GATT_CLIENTS 1 +#define MAX_NR_HCI_CONNECTIONS 1 +#define MAX_NR_L2CAP_SERVICES 3 +#define MAX_NR_L2CAP_CHANNELS 3 +#define MAX_NR_RFCOMM_MULTIPLEXERS 1 +#define MAX_NR_RFCOMM_SERVICES 1 +#define MAX_NR_RFCOMM_CHANNELS 1 +#define MAX_NR_BTSTACK_LINK_KEY_DB_MEMORY_ENTRIES 2 +#define MAX_NR_BNEP_SERVICES 1 +#define MAX_NR_BNEP_CHANNELS 1 +#define MAX_NR_HFP_CONNECTIONS 1 +#define MAX_NR_WHITELIST_ENTRIES 1 +#define MAX_NR_SM_LOOKUP_ENTRIES 3 +#define MAX_NR_SERVICE_RECORD_ITEMS 1 +#define MAX_NR_AVDTP_STREAM_ENDPOINTS 1 +#define MAX_NR_AVDTP_CONNECTIONS 1 +#define MAX_NR_AVRCP_CONNECTIONS 1 + +#define MAX_NR_LE_DEVICE_DB_ENTRIES 4 + +// Link Key DB and LE Device DB using TLV on top of Flash Sector interface +// #define NVM_NUM_DEVICE_DB_ENTRIES 16 + +// We don't give btstack a malloc, so use a fixed-size ATT DB. +#define MAX_ATT_DB_SIZE 512 + +// BTstack HAL configuration +#define HAVE_EMBEDDED_TIME_MS + +// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A). +#define HCI_RESET_RESEND_TIMEOUT_MS 1000 + +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c new file mode 100644 index 0000000000..8f0c82974c --- /dev/null +++ b/extmod/btstack/modbluetooth_btstack.c @@ -0,0 +1,1047 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "extmod/btstack/modbluetooth_btstack.h" +#include "extmod/modbluetooth.h" + +#include "lib/btstack/src/btstack.h" + +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) + +#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME +#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY BTSTACK" +#endif + +// How long to wait for a controller to init/deinit. +// Some controllers can take up to 5-6 seconds in normal operation. +STATIC const uint32_t BTSTACK_INIT_DEINIT_TIMEOUT_MS = 15000; + +// We need to know the attribute handle for the GAP device name (see GAP_DEVICE_NAME_UUID) +// so it can be put into the gatts_db before registering the services, and accessed +// efficiently when requesting an attribute in att_read_callback. Because this is the +// first characteristic of the first service, it always has a handle value of 3. +STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; + +volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; + +STATIC int btstack_error_to_errno(int err) { + DEBUG_EVENT_printf(" --> btstack error: %d\n", err); + if (err == ERROR_CODE_SUCCESS) { + return 0; + } else if (err == BTSTACK_ACL_BUFFERS_FULL || err == BTSTACK_MEMORY_ALLOC_FAILED) { + return MP_ENOMEM; + } else if (err == GATT_CLIENT_IN_WRONG_STATE) { + return MP_EALREADY; + } else if (err == GATT_CLIENT_BUSY) { + return MP_EBUSY; + } else if (err == GATT_CLIENT_NOT_CONNECTED) { + return MP_ENOTCONN; + } else { + return MP_EINVAL; + } +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uuid128) { + mp_obj_bluetooth_uuid_t result; + if (uuid16 != 0) { + result.data[0] = uuid16 & 0xff; + result.data[1] = (uuid16 >> 8) & 0xff; + result.type = MP_BLUETOOTH_UUID_TYPE_16; + } else { + reverse_128(uuid128, result.data); + result.type = MP_BLUETOOTH_UUID_TYPE_128; + } + return result; +} +#endif + +// Notes on supporting background ops (e.g. an attempt to gatts_notify while +// an existing notification is in progress): + +// GATTS Notify/Indicate (att_server_notify/indicate) +// * When available, copies buffer immediately. +// * Otherwise fails with BTSTACK_ACL_BUFFERS_FULL +// * Use att_server_request_to_send_notification/indication to get callback +// * Takes btstack_context_callback_registration_t (and takes ownership) and conn_handle. +// * Callback is invoked with just the context member of the btstack_context_callback_registration_t + +// GATTC Write without response (gatt_client_write_value_of_characteristic_without_response) +// * When available, copies buffer immediately. +// * Otherwise, fails with GATT_CLIENT_BUSY. +// * Use gatt_client_request_can_write_without_response_event to get callback +// * Takes btstack_packet_handler_t (function pointer) and conn_handle +// * Callback is invoked, use gatt_event_can_write_without_response_get_handle to get the conn_handle (no other context) +// * There can only be one pending gatt_client_request_can_write_without_response_event (otherwise we fail with EALREADY). + +// GATTC Write with response (gatt_client_write_value_of_characteristic) +// * When peripheral is available, takes ownership of buffer. +// * Otherwise, fails with GATT_CLIENT_IN_WRONG_STATE (we fail the operation). +// * Raises GATT_EVENT_QUERY_COMPLETE to the supplied packet handler. + +// For notify/indicate/write-without-response that proceed immediately, nothing extra required. +// For all other cases, buffer needs to be copied and protected from GC. +// For notify/indicate: +// * btstack_context_callback_registration_t: +// * needs to be malloc'ed +// * needs to be protected from GC +// * context arg needs to point back to the callback registration so it can be freed and un-protected +// For write-without-response +// * only the conn_handle is available in the callback +// * so we need a queue of conn_handle->(value_handle, copied buffer) + +// Pending operation types. +enum { + // Queued for sending when possible. + MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, // Waiting for context callback + MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, // Waiting for context callback + MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, // Waiting for conn handle + // Hold buffer pointer until complete. + MP_BLUETOOTH_BTSTACK_PENDING_WRITE, // Waiting for write done event +}; + +// Pending operation: +// - Holds a GC reference to the copied outgoing buffer. +// - Provides enough information for the callback handler to execute the desired operation. +struct _mp_btstack_pending_op_t { + btstack_linked_item_t *next; // Must be first field to match btstack_linked_item. + + // See enum above. + uint16_t op_type; + + // For all op types. + uint16_t conn_handle; + uint16_t value_handle; + + // For notify/indicate only. + // context_registration.context will point back to this struct. + btstack_context_callback_registration_t context_registration; + + // For notify/indicate/write-without-response, this is the actual buffer to send. + // For write-with-response, just holding onto the buffer for GC ref. + size_t len; + uint8_t buf[]; +}; + +// Must hold MICROPY_PY_BLUETOOTH_ENTER. +STATIC void btstack_remove_pending_operation(mp_btstack_pending_op_t *pending_op, bool del) { + bool removed = btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op); + assert(removed); + (void)removed; + if (del) { + m_del_var(mp_btstack_pending_op_t, uint8_t, pending_op->len, pending_op); + } +} + +// Called in response to a gatts_notify/indicate being unable to complete, which then calls +// att_server_request_to_send_notification. +// We now have an opportunity to re-try the operation with an empty ACL buffer. +STATIC void btstack_notify_indicate_ready_handler(void *context) { + MICROPY_PY_BLUETOOTH_ENTER + mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)context; + DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler op_type=%d conn_handle=%d value_handle=%d len=%lu\n", pending_op->op_type, pending_op->conn_handle, pending_op->value_handle, pending_op->len); + if (pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY) { + int err = att_server_notify(pending_op->conn_handle, pending_op->value_handle, pending_op->buf, pending_op->len); + DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending notification err=%d\n", err); + assert(err == ERROR_CODE_SUCCESS); + (void)err; + } else { + assert(pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE); + int err = att_server_indicate(pending_op->conn_handle, pending_op->value_handle, NULL, 0); + DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending indication err=%d\n", err); + assert(err == ERROR_CODE_SUCCESS); + (void)err; + } + // Can't free the pending op as we're in IRQ context. Leave it for the GC. + btstack_remove_pending_operation(pending_op, false /* del */); + MICROPY_PY_BLUETOOTH_EXIT +} + +// Register a pending background operation -- copies the buffer, and makes it known to the GC. +STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, const uint8_t *buf, size_t len) { + DEBUG_EVENT_printf("btstack_enqueue_pending_operation op_type=%d conn_handle=%d value_handle=%d len=%lu\n", op_type, conn_handle, value_handle, len); + mp_btstack_pending_op_t *pending_op = m_new_obj_var(mp_btstack_pending_op_t, uint8_t, len); + pending_op->op_type = op_type; + pending_op->conn_handle = conn_handle; + pending_op->value_handle = value_handle; + pending_op->len = len; + memcpy(pending_op->buf, buf, len); + + if (op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY || op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE) { + pending_op->context_registration.callback = &btstack_notify_indicate_ready_handler; + pending_op->context_registration.context = pending_op; + } + + MICROPY_PY_BLUETOOTH_ENTER + bool added = btstack_linked_list_add(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op); + assert(added); + (void)added; + MICROPY_PY_BLUETOOTH_EXIT + + return pending_op; +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +// Cleans up a pending op of the specified type for this conn_handle (and if specified, value_handle). +// Used by MP_BLUETOOTH_BTSTACK_PENDING_WRITE and MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE. +// At the moment, both will set value_handle=0xffff as the events do not know their value_handle. +// TODO: Can we make btstack give us the value_handle for regular write (with response) so that we +// know for sure that we're using the correct entry. +STATIC mp_btstack_pending_op_t *btstack_finish_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, bool del) { + MICROPY_PY_BLUETOOTH_ENTER + DEBUG_EVENT_printf("btstack_finish_pending_operation op_type=%d conn_handle=%d value_handle=%d\n", op_type, conn_handle, value_handle); + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops); + while (btstack_linked_list_iterator_has_next(&it)) { + mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)btstack_linked_list_iterator_next(&it); + + if (pending_op->op_type == op_type && pending_op->conn_handle == conn_handle && (value_handle == 0xffff || pending_op->value_handle == value_handle)) { + DEBUG_EVENT_printf("btstack_finish_pending_operation: found value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len); + btstack_remove_pending_operation(pending_op, del); + MICROPY_PY_BLUETOOTH_EXIT + return del ? NULL : pending_op; + } + } + DEBUG_EVENT_printf("btstack_finish_pending_operation: not found\n"); + MICROPY_PY_BLUETOOTH_EXIT + return NULL; +} +#endif + +// This needs to be separate to btstack_packet_handler otherwise we get +// dual-delivery of the HCI_EVENT_LE_META event. +STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + DEBUG_EVENT_printf("btstack_packet_handler_att_server(packet_type=%u, packet=%p)\n", packet_type, packet); + if (packet_type != HCI_EVENT_PACKET) { + return; + } + + uint8_t event_type = hci_event_packet_get_type(packet); + + if (event_type == ATT_EVENT_CONNECTED) { + DEBUG_EVENT_printf(" --> att connected\n"); + // The ATT_EVENT_*CONNECTED events are fired for both peripheral and central role, with no way to tell which. + // So we use the HCI_EVENT_LE_META event directly in the main packet handler. + } else if (event_type == ATT_EVENT_DISCONNECTED) { + DEBUG_EVENT_printf(" --> att disconnected\n"); + } else if (event_type == ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE) { + DEBUG_EVENT_printf(" --> att indication complete\n"); + uint16_t conn_handle = att_event_handle_value_indication_complete_get_conn_handle(packet); + uint16_t value_handle = att_event_handle_value_indication_complete_get_attribute_handle(packet); + uint8_t status = att_event_handle_value_indication_complete_get_status(packet); + mp_bluetooth_gatts_on_indicate_complete(conn_handle, value_handle, status); + } else if (event_type == HCI_EVENT_LE_META || event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { + // Ignore, duplicated by att_server.c. + } else { + DEBUG_EVENT_printf(" --> hci att server event type: unknown (0x%02x)\n", event_type); + } +} + +STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) { + DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); + if (packet_type != HCI_EVENT_PACKET) { + return; + } + + uint8_t event_type = hci_event_packet_get_type(packet); + + if (event_type == HCI_EVENT_LE_META) { + DEBUG_EVENT_printf(" --> hci le meta\n"); + if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) { + uint16_t conn_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + uint8_t addr_type = hci_subevent_le_connection_complete_get_peer_address_type(packet); + bd_addr_t addr; + hci_subevent_le_connection_complete_get_peer_address(packet, addr); + uint16_t irq_event; + if (hci_subevent_le_connection_complete_get_role(packet) == 0) { + // Master role. + irq_event = MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT; + } else { + // Slave role. + irq_event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT; + } + mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, addr_type, addr); + } + } else if (event_type == BTSTACK_EVENT_STATE) { + uint8_t state = btstack_event_state_get_state(packet); + DEBUG_EVENT_printf(" --> btstack event state 0x%02x\n", state); + if (state == HCI_STATE_WORKING) { + // Signal that initialisation has completed. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; + } else if (state == HCI_STATE_OFF) { + // Signal that de-initialisation has completed. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; + } + } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { + DEBUG_EVENT_printf(" --> hci transport packet sent\n"); + } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { + DEBUG_EVENT_printf(" --> hci command complete\n"); + } else if (event_type == HCI_EVENT_COMMAND_STATUS) { + DEBUG_EVENT_printf(" --> hci command status\n"); + } else if (event_type == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { + DEBUG_EVENT_printf(" --> hci number of completed packets\n"); + } else if (event_type == BTSTACK_EVENT_NR_CONNECTIONS_CHANGED) { + DEBUG_EVENT_printf(" --> btstack # conns changed\n"); + } else if (event_type == HCI_EVENT_VENDOR_SPECIFIC) { + DEBUG_EVENT_printf(" --> hci vendor specific\n"); + } else if (event_type == GATT_EVENT_MTU) { + DEBUG_EVENT_printf(" --> hci MTU\n"); + } else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { + DEBUG_EVENT_printf(" --> hci disconnect complete\n"); + uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet); + const hci_connection_t *conn = hci_connection_for_handle(conn_handle); + uint16_t irq_event; + if (conn == NULL || conn->role == 0) { + // Master role. + irq_event = MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT; + } else { + // Slave role. + irq_event = MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT; + } + uint8_t addr[6] = {0}; + mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + } else if (event_type == GAP_EVENT_ADVERTISING_REPORT) { + DEBUG_EVENT_printf(" --> gap advertising report\n"); + bd_addr_t address; + gap_event_advertising_report_get_address(packet, address); + uint8_t adv_event_type = gap_event_advertising_report_get_advertising_event_type(packet); + uint8_t address_type = gap_event_advertising_report_get_address_type(packet); + int8_t rssi = gap_event_advertising_report_get_rssi(packet); + uint8_t length = gap_event_advertising_report_get_data_length(packet); + const uint8_t *data = gap_event_advertising_report_get_data(packet); + mp_bluetooth_gap_on_scan_result(address_type, address, adv_event_type, rssi, data, length); + } else if (event_type == GATT_EVENT_QUERY_COMPLETE) { + uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); + uint16_t status = gatt_event_query_complete_get_att_status(packet); + DEBUG_EVENT_printf(" --> gatt query complete irq=%d conn_handle=%d status=%d\n", irq, conn_handle, status); + if (irq == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { + // TODO there is no value_handle available to pass here. + // TODO try and get this implemented in btstack. + mp_bluetooth_gattc_on_read_write_status(irq, conn_handle, 0xffff, status); + // Unref the saved buffer for the write operation on this conn_handle. + if (irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { + btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, 0xffff, false /* del */); + } + } else if (irq == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || + irq == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || + irq == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { + mp_bluetooth_gattc_on_discover_complete(irq, conn_handle, status); + } + } else if (event_type == GATT_EVENT_SERVICE_QUERY_RESULT) { + DEBUG_EVENT_printf(" --> gatt service query result\n"); + uint16_t conn_handle = gatt_event_service_query_result_get_handle(packet); + gatt_client_service_t service; + gatt_event_service_query_result_get_service(packet, &service); + mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(service.uuid16, service.uuid128); + mp_bluetooth_gattc_on_primary_service_result(conn_handle, service.start_group_handle, service.end_group_handle, &service_uuid); + } else if (event_type == GATT_EVENT_CHARACTERISTIC_QUERY_RESULT) { + DEBUG_EVENT_printf(" --> gatt characteristic query result\n"); + uint16_t conn_handle = gatt_event_characteristic_query_result_get_handle(packet); + gatt_client_characteristic_t characteristic; + gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic); + mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(characteristic.uuid16, characteristic.uuid128); + mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic.start_handle, characteristic.value_handle, characteristic.properties, &characteristic_uuid); + } else if (event_type == GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT) { + DEBUG_EVENT_printf(" --> gatt descriptor query result\n"); + uint16_t conn_handle = gatt_event_all_characteristic_descriptors_query_result_get_handle(packet); + gatt_client_characteristic_descriptor_t descriptor; + gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &descriptor); + mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(descriptor.uuid16, descriptor.uuid128); + mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor.handle, &descriptor_uuid); + } else if (event_type == GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT) { + DEBUG_EVENT_printf(" --> gatt characteristic value query result\n"); + uint16_t conn_handle = gatt_event_characteristic_value_query_result_get_handle(packet); + uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet); + uint16_t len = gatt_event_characteristic_value_query_result_get_value_length(packet); + const uint8_t *data = gatt_event_characteristic_value_query_result_get_value(packet); + mp_uint_t atomic_state; + len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, value_handle, len, &atomic_state); + mp_bluetooth_gattc_on_data_available_chunk(data, len); + mp_bluetooth_gattc_on_data_available_end(atomic_state); + } else if (event_type == GATT_EVENT_NOTIFICATION) { + DEBUG_EVENT_printf(" --> gatt notification\n"); + uint16_t conn_handle = gatt_event_notification_get_handle(packet); + uint16_t value_handle = gatt_event_notification_get_value_handle(packet); + uint16_t len = gatt_event_notification_get_value_length(packet); + const uint8_t *data = gatt_event_notification_get_value(packet); + mp_uint_t atomic_state; + len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, conn_handle, value_handle, len, &atomic_state); + mp_bluetooth_gattc_on_data_available_chunk(data, len); + mp_bluetooth_gattc_on_data_available_end(atomic_state); + } else if (event_type == GATT_EVENT_INDICATION) { + DEBUG_EVENT_printf(" --> gatt indication\n"); + uint16_t conn_handle = gatt_event_indication_get_handle(packet); + uint16_t value_handle = gatt_event_indication_get_value_handle(packet); + uint16_t len = gatt_event_indication_get_value_length(packet); + const uint8_t *data = gatt_event_indication_get_value(packet); + mp_uint_t atomic_state; + len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, len, &atomic_state); + mp_bluetooth_gattc_on_data_available_chunk(data, len); + mp_bluetooth_gattc_on_data_available_end(atomic_state); + } else if (event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE) { + uint16_t conn_handle = gatt_event_can_write_without_response_get_handle(packet); + DEBUG_EVENT_printf(" --> gatt can write without response %d\n", conn_handle); + mp_btstack_pending_op_t *pending_op = btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, 0xffff, false /* !del */); + if (pending_op) { + DEBUG_EVENT_printf(" --> ready for value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len); + gatt_client_write_value_of_characteristic_without_response(pending_op->conn_handle, pending_op->value_handle, pending_op->len, (uint8_t *)pending_op->buf); + // Note: Can't "del" the pending_op from IRQ context. Leave it for the GC. + } + + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + } else { + DEBUG_EVENT_printf(" --> hci event type: unknown (0x%02x)\n", event_type); + } +} + +// Because the packet handler callbacks don't support an argument, we use a specific +// handler when we need to provide additional state to the handler (in the "irq" parameter). +// This is the generic handler for when you don't need extra state. +STATIC void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, 0); +} + +STATIC btstack_packet_callback_registration_t hci_event_callback_registration = { + .callback = &btstack_packet_handler_generic +}; + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +// For when the handler is being used for service discovery. +STATIC void btstack_packet_handler_discover_services(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE); +} + +// For when the handler is being used for characteristic discovery. +STATIC void btstack_packet_handler_discover_characteristics(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE); +} + +// For when the handler is being used for descriptor discovery. +STATIC void btstack_packet_handler_discover_descriptors(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE); +} + +// For when the handler is being used for a read query. +STATIC void btstack_packet_handler_read(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_READ_DONE); +} + +// For when the handler is being used for write-with-response. +STATIC void btstack_packet_handler_write_with_response(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +STATIC btstack_timer_source_t btstack_init_deinit_timeout; + +STATIC void btstack_init_deinit_timeout_handler(btstack_timer_source_t *ds) { + (void)ds; + + // Stop waiting for initialisation. + // This signals both the loops in mp_bluetooth_init and mp_bluetooth_deinit, + // as well as ports that run a polling loop. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; +} + +int mp_bluetooth_init(void) { + DEBUG_EVENT_printf("mp_bluetooth_init\n"); + + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + return 0; + } + + // Clean up if necessary. + mp_bluetooth_deinit(); + + btstack_memory_init(); + + MP_STATE_PORT(bluetooth_btstack_root_pointers) = m_new0(mp_bluetooth_btstack_root_pointers_t, 1); + mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db); + + // Set the default GAP device name. + const char *gap_name = MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME; + size_t gap_len = strlen(gap_name); + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, gap_len); + mp_bluetooth_gap_set_device_name((const uint8_t *)gap_name, gap_len); + + mp_bluetooth_btstack_port_init(); + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_STARTING; + + l2cap_init(); + le_device_db_init(); + sm_init(); + + // Set blank ER/IR keys to suppress BTstack warning. + // TODO handle this correctly. + sm_key_t dummy_key; + memset(dummy_key, 0, sizeof(dummy_key)); + sm_set_er(dummy_key); + sm_set_ir(dummy_key); + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + gatt_client_init(); + #endif + + // Register for HCI events. + hci_add_event_handler(&hci_event_callback_registration); + + // Register for ATT server events. + att_server_register_packet_handler(&btstack_packet_handler_att_server); + + // Set a timeout for HCI initialisation. + btstack_run_loop_set_timer(&btstack_init_deinit_timeout, BTSTACK_INIT_DEINIT_TIMEOUT_MS); + btstack_run_loop_set_timer_handler(&btstack_init_deinit_timeout, btstack_init_deinit_timeout_handler); + btstack_run_loop_add_timer(&btstack_init_deinit_timeout); + + // Either the HCI event will set state to ACTIVE, or the timeout will set it to TIMEOUT. + mp_bluetooth_btstack_port_start(); + while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING) { + MICROPY_EVENT_POLL_HOOK + } + btstack_run_loop_remove_timer(&btstack_init_deinit_timeout); + + // Check for timeout. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + // Required to stop the polling loop. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; + // Attempt a shutdown (may not do anything). + mp_bluetooth_btstack_port_deinit(); + + // Clean up. + MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + return MP_ETIMEDOUT; + } + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + // Enable GATT_EVENT_NOTIFICATION/GATT_EVENT_INDICATION for all connections and handles. + gatt_client_listen_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification, &btstack_packet_handler_generic, GATT_CLIENT_ANY_CONNECTION, NULL); + #endif + + return 0; +} + +void mp_bluetooth_deinit(void) { + DEBUG_EVENT_printf("mp_bluetooth_deinit\n"); + + // Nothing to do if not initialised. + if (!MP_STATE_PORT(bluetooth_btstack_root_pointers)) { + return; + } + + mp_bluetooth_gap_advertise_stop(); + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + // Remove our registration for notify/indicate. + gatt_client_stop_listening_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification); + #endif + + // Set a timer that will forcibly set the state to TIMEOUT, which will stop the loop below. + btstack_run_loop_set_timer(&btstack_init_deinit_timeout, BTSTACK_INIT_DEINIT_TIMEOUT_MS); + btstack_run_loop_add_timer(&btstack_init_deinit_timeout); + + // This should result in a clean shutdown, which will set the state to OFF. + // On Unix this is blocking (it joins on the poll thread), on other ports the loop below will wait unil + // either timeout or clean shutdown. + mp_bluetooth_btstack_port_deinit(); + while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + MICROPY_EVENT_POLL_HOOK + } + btstack_run_loop_remove_timer(&btstack_init_deinit_timeout); + + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; + MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; +} + +bool mp_bluetooth_is_active(void) { + return mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; +} + +void mp_bluetooth_get_device_addr(uint8_t *addr) { + mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr); +} + +size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { + uint8_t *value = NULL; + size_t value_len = 0; + mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, &value, &value_len); + *buf = value; + return value_len; +} + +int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { + return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len); +} + +int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { + DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_start\n"); + uint16_t adv_int_min = interval_us / 625; + uint16_t adv_int_max = interval_us / 625; + uint8_t adv_type = connectable ? 0 : 2; + bd_addr_t null_addr = {0}; + + uint8_t direct_address_type = 0; + uint8_t channel_map = 0x07; // Use all three broadcast channels. + uint8_t filter_policy = 0x00; // None. + + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, direct_address_type, null_addr, channel_map, filter_policy); + + // Copy the adv_data and sr_data into a persistent buffer (which is findable via the btstack root pointers). + size_t total_bytes = adv_data_len + sr_data_len; + if (total_bytes > MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data_alloc) { + // Resize if necessary. + MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data = m_new(uint8_t, total_bytes); + MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data_alloc = total_bytes; + } + uint8_t *data = MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data; + + if (adv_data) { + memcpy(data, (uint8_t *)adv_data, adv_data_len); + gap_advertisements_set_data(adv_data_len, data); + data += adv_data_len; + } + if (sr_data) { + memcpy(data, (uint8_t *)sr_data, sr_data_len); + gap_scan_response_set_data(sr_data_len, data); + } + + gap_advertisements_enable(true); + return 0; +} + +void mp_bluetooth_gap_advertise_stop(void) { + DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_stop\n"); + gap_advertisements_enable(false); + MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data_alloc = 0; + MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data = NULL; +} + +int mp_bluetooth_gatts_register_service_begin(bool append) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_begin\n"); + if (!append) { + // This will reset the DB. + // Becase the DB is statically allocated, there's no problem with just re-initing it. + // Note this would be a memory leak if we enabled HAVE_MALLOC (there's no API to free the existing db). + att_db_util_init(); + + att_db_util_add_service_uuid16(GAP_SERVICE_UUID); + uint16_t handle = att_db_util_add_characteristic_uuid16(GAP_DEVICE_NAME_UUID, ATT_PROPERTY_READ | ATT_PROPERTY_DYNAMIC, ATT_SECURITY_NONE, ATT_SECURITY_NONE, NULL, 0); + assert(handle == BTSTACK_GAP_DEVICE_NAME_HANDLE); + (void)handle; + + att_db_util_add_service_uuid16(0x1801); + att_db_util_add_characteristic_uuid16(0x2a05, ATT_PROPERTY_READ, ATT_SECURITY_NONE, ATT_SECURITY_NONE, NULL, 0); + } + + return 0; +} + +STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + (void)connection_handle; + DEBUG_EVENT_printf("btstack: att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); + if (!entry) { + DEBUG_EVENT_printf("btstack: att_read_callback handle not found\n"); + return 0; // TODO: Find status code for not-found. + } + + return att_read_callback_handle_blob(entry->data, entry->data_len, offset, buffer, buffer_size); +} + +STATIC int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + (void)offset; + (void)transaction_mode; + DEBUG_EVENT_printf("btstack: att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); + if (!entry) { + DEBUG_EVENT_printf("btstack: att_write_callback handle not found\n"); + return 0; // TODO: Find status code for not-found. + } + + // TODO: Use `offset` arg. + size_t append_offset = 0; + if (entry->append) { + append_offset = entry->data_len; + } + entry->data_len = MIN(entry->data_alloc, buffer_size + append_offset); + memcpy(entry->data + append_offset, buffer, entry->data_len - append_offset); + + mp_bluetooth_gatts_on_write(connection_handle, att_handle); + + return 0; +} + +STATIC inline uint16_t get_uuid16(const mp_obj_bluetooth_uuid_t *uuid) { + return (uuid->data[1] << 8) | uuid->data[0]; +} + +int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service\n"); + // Note: btstack expects BE UUIDs (which it immediately convertes to LE). + // So we have to convert all our modbluetooth LE UUIDs to BE just for the att_db_util_add_* methods (using get_uuid16 above, and reverse_128 from btstackutil.h). + + // TODO: btstack's att_db_util_add_* methods have no bounds checking or validation. + // Need some way to prevent additional services being added if we're out of space in the static buffer. + + if (service_uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + att_db_util_add_service_uuid16(get_uuid16(service_uuid)); + } else if (service_uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(service_uuid->data, buffer); + att_db_util_add_service_uuid128(buffer); + } else { + return MP_EINVAL; + } + + size_t handle_index = 0; + size_t descriptor_index = 0; + static uint8_t cccb_buf[2] = {0}; + + for (size_t i = 0; i < num_characteristics; ++i) { + uint16_t props = characteristic_flags[i] | ATT_PROPERTY_DYNAMIC; + uint16_t read_permission = ATT_SECURITY_NONE; + uint16_t write_permission = ATT_SECURITY_NONE; + if (characteristic_uuids[i]->type == MP_BLUETOOTH_UUID_TYPE_16) { + handles[handle_index] = att_db_util_add_characteristic_uuid16(get_uuid16(characteristic_uuids[i]), props, read_permission, write_permission, NULL, 0); + } else if (characteristic_uuids[i]->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(characteristic_uuids[i]->data, buffer); + handles[handle_index] = att_db_util_add_characteristic_uuid128(buffer, props, read_permission, write_permission, NULL, 0); + } else { + return MP_EINVAL; + } + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index], MP_BLUETOOTH_DEFAULT_ATTR_LEN); + // If a NOTIFY or INDICATE characteristic is added, then we need to manage a value for the CCCB. + if (props & (ATT_PROPERTY_NOTIFY | ATT_PROPERTY_INDICATE)) { + // btstack creates the CCCB as the next handle. + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN); + int ret = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf)); + if (ret) { + return ret; + } + } + DEBUG_EVENT_printf("Registered char with handle %u\n", handles[handle_index]); + ++handle_index; + + for (size_t j = 0; j < num_descriptors[i]; ++j) { + props = descriptor_flags[descriptor_index] | ATT_PROPERTY_DYNAMIC; + read_permission = ATT_SECURITY_NONE; + write_permission = ATT_SECURITY_NONE; + + if (descriptor_uuids[descriptor_index]->type == MP_BLUETOOTH_UUID_TYPE_16) { + handles[handle_index] = att_db_util_add_descriptor_uuid16(get_uuid16(descriptor_uuids[descriptor_index]), props, read_permission, write_permission, NULL, 0); + } else if (descriptor_uuids[descriptor_index]->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(descriptor_uuids[descriptor_index]->data, buffer); + handles[handle_index] = att_db_util_add_descriptor_uuid128(buffer, props, read_permission, write_permission, NULL, 0); + } else { + return MP_EINVAL; + } + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index], MP_BLUETOOTH_DEFAULT_ATTR_LEN); + DEBUG_EVENT_printf("Registered desc with handle %u\n", handles[handle_index]); + ++descriptor_index; + ++handle_index; + } + } + + return 0; +} + +int mp_bluetooth_gatts_register_service_end(void) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_end\n"); + att_server_init(att_db_util_get_address(), &att_read_callback, &att_write_callback); + return 0; +} + +int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_read\n"); + return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); +} + +int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_write\n"); + return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); +} + +int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_notify\n"); + // Note: btstack doesn't appear to support sending a notification without a value, so include the stored value. + uint8_t *data = NULL; + size_t len = 0; + mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); + return mp_bluetooth_gatts_notify_send(conn_handle, value_handle, data, len); +} + +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send\n"); + + // Attempt to send immediately. If it succeeds, btstack will copy the buffer. + MICROPY_PY_BLUETOOTH_ENTER + int err = att_server_notify(conn_handle, value_handle, value, value_len); + MICROPY_PY_BLUETOOTH_EXIT + + if (err == BTSTACK_ACL_BUFFERS_FULL) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send: ACL buffer full, scheduling callback\n"); + // Schedule callback, making a copy of the buffer. + mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, conn_handle, value_handle, value, value_len); + + err = att_server_request_to_send_notification(&pending_op->context_registration, conn_handle); + + if (err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); + } + + return 0; + } else { + return btstack_error_to_errno(err); + } +} + +int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate\n"); + + uint8_t *data = NULL; + size_t len = 0; + mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); + + // Indicate will raise ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE when + // acknowledged (or timeout/error). + + // Attempt to send immediately, will copy buffer. + MICROPY_PY_BLUETOOTH_ENTER + int err = att_server_indicate(conn_handle, value_handle, data, len); + MICROPY_PY_BLUETOOTH_EXIT + + if (err == BTSTACK_ACL_BUFFERS_FULL) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate: ACL buffer full, scheduling callback\n"); + // Schedule callback, making a copy of the buffer. + mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, conn_handle, value_handle, data, len); + + err = att_server_request_to_send_indication(&pending_op->context_registration, conn_handle); + + if (err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); + } + + return 0; + } else { + return btstack_error_to_errno(err); + } +} + +int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { + DEBUG_EVENT_printf("mp_bluetooth_gatts_set_buffer\n"); + return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, len, append); +} + +int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { + DEBUG_EVENT_printf("mp_bluetooth_gap_disconnect\n"); + gap_disconnect(conn_handle); + return 0; +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +STATIC btstack_timer_source_t scan_duration_timeout; + +STATIC void scan_duration_timeout_handler(btstack_timer_source_t *ds) { + (void)ds; + mp_bluetooth_gap_scan_stop(); +} + +int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { + DEBUG_EVENT_printf("mp_bluetooth_gap_scan_start\n"); + + if (duration_ms > 0) { + btstack_run_loop_set_timer(&scan_duration_timeout, duration_ms); + btstack_run_loop_set_timer_handler(&scan_duration_timeout, scan_duration_timeout_handler); + btstack_run_loop_add_timer(&scan_duration_timeout); + } + + gap_set_scan_parameters(active_scan ? 1 : 0, interval_us / 625, window_us / 625); + gap_start_scan(); + + return 0; +} + +int mp_bluetooth_gap_scan_stop(void) { + DEBUG_EVENT_printf("mp_bluetooth_gap_scan_stop\n"); + btstack_run_loop_remove_timer(&scan_duration_timeout); + gap_stop_scan(); + mp_bluetooth_gap_on_scan_complete(); + return 0; +} + +int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { + DEBUG_EVENT_printf("mp_bluetooth_gap_peripheral_connect\n"); + + uint16_t conn_scan_interval = 60000 / 625; + uint16_t conn_scan_window = 30000 / 625; + uint16_t conn_interval_min = 10000 / 1250; + uint16_t conn_interval_max = 30000 / 1250; + uint16_t conn_latency = 4; + uint16_t supervision_timeout = duration_ms / 10; // default = 720 + uint16_t min_ce_length = 10000 / 625; + uint16_t max_ce_length = 30000 / 625; + + gap_set_connection_parameters(conn_scan_interval, conn_scan_window, conn_interval_min, conn_interval_max, conn_latency, supervision_timeout, min_ce_length, max_ce_length); + + bd_addr_t btstack_addr; + memcpy(btstack_addr, addr, BD_ADDR_LEN); + return btstack_error_to_errno(gap_connect(btstack_addr, addr_type)); +} + +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_primary_services\n"); + uint8_t err; + if (uuid) { + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + err = gatt_client_discover_primary_services_by_uuid16(&btstack_packet_handler_discover_services, conn_handle, get_uuid16(uuid)); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(uuid->data, buffer); + err = gatt_client_discover_primary_services_by_uuid128(&btstack_packet_handler_discover_services, conn_handle, buffer); + } else { + DEBUG_EVENT_printf(" --> unknown UUID size\n"); + return MP_EINVAL; + } + } else { + err = gatt_client_discover_primary_services(&btstack_packet_handler_discover_services, conn_handle); + } + return btstack_error_to_errno(err); +} + +int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const mp_obj_bluetooth_uuid_t *uuid) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_characteristics\n"); + gatt_client_service_t service = { + // Only start/end handles needed for gatt_client_discover_characteristics_for_service. + .start_group_handle = start_handle, + .end_group_handle = end_handle, + .uuid16 = 0, + .uuid128 = {0}, + }; + uint8_t err; + if (uuid) { + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + err = gatt_client_discover_characteristics_for_service_by_uuid16(&btstack_packet_handler_discover_characteristics, conn_handle, &service, get_uuid16(uuid)); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(uuid->data, buffer); + err = gatt_client_discover_characteristics_for_service_by_uuid128(&btstack_packet_handler_discover_characteristics, conn_handle, &service, buffer); + } else { + DEBUG_EVENT_printf(" --> unknown UUID size\n"); + return MP_EINVAL; + } + } else { + err = gatt_client_discover_characteristics_for_service(&btstack_packet_handler_discover_characteristics, conn_handle, &service); + } + return btstack_error_to_errno(err); +} + +int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_descriptors\n"); + gatt_client_characteristic_t characteristic = { + // Only start/end handles needed for gatt_client_discover_characteristic_descriptors. + .start_handle = start_handle, + .value_handle = 0, + .end_handle = end_handle, + .properties = 0, + .uuid16 = 0, + .uuid128 = {0}, + }; + return btstack_error_to_errno(gatt_client_discover_characteristic_descriptors(&btstack_packet_handler_discover_descriptors, conn_handle, &characteristic)); +} + +int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_read\n"); + return btstack_error_to_errno(gatt_client_read_value_of_characteristic_using_value_handle(&btstack_packet_handler_read, conn_handle, value_handle)); +} + +int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_write\n"); + + // We should be distinguishing between gatt_client_write_value_of_characteristic vs + // gatt_client_write_characteristic_descriptor_using_descriptor_handle. + // However both are implemented using send_gatt_write_attribute_value_request under the hood, + // and we get the exact same event to the packet handler. + // Same story for the "without response" version. + + int err; + mp_btstack_pending_op_t *pending_op = NULL; + + if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) { + // If possible, this will send immediately, copying the buffer directly to the ACL buffer. + err = gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value); + if (err == GATT_CLIENT_BUSY) { + DEBUG_EVENT_printf("mp_bluetooth_gattc_write: client busy\n"); + // Can't send right now, need to take a copy of the buffer and add it to the queue. + pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, value_handle, value, *value_len); + // Notify when this conn_handle can write. + err = gatt_client_request_can_write_without_response_event(&btstack_packet_handler_generic, conn_handle); + } else { + DEBUG_EVENT_printf("mp_bluetooth_gattc_write: other failure: %d\n", err); + } + } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { + // Pending operation copies the value buffer and keeps a GC reference + // until the response comes back (there is always a response). + pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, value_handle, value, *value_len); + err = gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, pending_op->len, pending_op->buf); + } else { + return MP_EINVAL; + } + + if (pending_op && err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); + } + + return btstack_error_to_errno(err); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h new file mode 100644 index 0000000000..2fad86f226 --- /dev/null +++ b/extmod/btstack/modbluetooth_btstack.h @@ -0,0 +1,70 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "extmod/modbluetooth.h" + +#include "lib/btstack/src/btstack.h" + +typedef struct _mp_btstack_pending_op_t mp_btstack_pending_op_t; + +typedef struct _mp_bluetooth_btstack_root_pointers_t { + // This stores both the advertising data and the scan response data, concatenated together. + uint8_t *adv_data; + // Total length of both. + size_t adv_data_alloc; + + // Characteristic (and descriptor) value storage. + mp_gatts_db_t gatts_db; + + btstack_linked_list_t pending_ops; + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + // Registration for notify/indicate events. + gatt_client_notification_t notification; + #endif +} mp_bluetooth_btstack_root_pointers_t; + +enum { + MP_BLUETOOTH_BTSTACK_STATE_OFF, + MP_BLUETOOTH_BTSTACK_STATE_STARTING, + MP_BLUETOOTH_BTSTACK_STATE_ACTIVE, + MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT, +}; + +extern volatile int mp_bluetooth_btstack_state; + +void mp_bluetooth_btstack_port_init(void); +void mp_bluetooth_btstack_port_deinit(void); +void mp_bluetooth_btstack_port_start(void); + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H diff --git a/extmod/extmod.mk b/extmod/extmod.mk index c734866927..7ad3d7d851 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -51,7 +51,7 @@ CFLAGS_MOD += -DMICROPY_PY_USSL=1 ifeq ($(MICROPY_SSL_AXTLS),1) CFLAGS_MOD += -DMICROPY_SSL_AXTLS=1 -I$(TOP)/lib/axtls/ssl -I$(TOP)/lib/axtls/crypto -I$(TOP)/extmod/axtls-include AXTLS_DIR = lib/axtls -$(BUILD)/$(AXTLS_DIR)/%.o: CFLAGS += -Wno-all -Wno-unused-parameter -Wno-uninitialized -Wno-sign-compare -Wno-old-style-definition $(AXTLS_DEFS_EXTRA) +$(BUILD)/$(AXTLS_DIR)/%.o: CFLAGS += -Wno-all -Wno-unused-parameter -Wno-uninitialized -Wno-sign-compare -Wno-old-style-definition -Dmp_stream_errno=errno $(AXTLS_DEFS_EXTRA) SRC_MOD += $(addprefix $(AXTLS_DIR)/,\ ssl/asn1.c \ ssl/loader.c \ diff --git a/extmod/modbtree.c b/extmod/modbtree.c index 4922651137..d37fe9b37c 100644 --- a/extmod/modbtree.c +++ b/extmod/modbtree.c @@ -18,6 +18,7 @@ typedef struct _mp_obj_btree_t { mp_obj_base_t base; + mp_obj_t stream; // retain a reference to prevent GC from reclaiming it DB *db; mp_obj_t start_key; mp_obj_t end_key; @@ -44,9 +45,10 @@ void __dbpanic(DB *db) { mp_printf(&mp_plat_print, "__dbpanic(%p)\n", db); } -STATIC mp_obj_btree_t *btree_new(DB *db) { +STATIC mp_obj_btree_t *btree_new(DB *db, mp_obj_t stream) { mp_obj_btree_t *o = m_new_obj(mp_obj_btree_t); o->base.type = &btree_type; + o->stream = stream; o->db = db; o->start_key = mp_const_none; o->end_key = mp_const_none; @@ -228,14 +230,14 @@ STATIC mp_obj_t btree_iternext(mp_obj_t self_in) { } STATIC mp_obj_t btree_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { - mp_obj_btree_t *self = mp_instance_cast_to_native_base(self_in, &btree_type); + mp_obj_btree_t *self = mp_obj_cast_to_native_base(self_in, &btree_type); if (value == MP_OBJ_NULL) { // delete DBT key; key.data = (void *)mp_obj_str_get_data(index, &key.size); int res = __bt_delete(self->db, &key, 0); if (res == RET_SPECIAL) { - nlr_raise(mp_obj_new_exception(&mp_type_KeyError)); + mp_raise_type(&mp_type_KeyError); } CHECK_ERROR(res); return mp_const_none; @@ -245,7 +247,7 @@ STATIC mp_obj_t btree_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { key.data = (void *)mp_obj_str_get_data(index, &key.size); int res = __bt_get(self->db, &key, &val, 0); if (res == RET_SPECIAL) { - nlr_raise(mp_obj_new_exception(&mp_type_KeyError)); + mp_raise_type(&mp_type_KeyError); } CHECK_ERROR(res); return mp_obj_new_bytes(val.data, val.size); @@ -340,7 +342,7 @@ STATIC mp_obj_t mod_btree_open(size_t n_args, const mp_obj_t *pos_args, mp_map_t if (db == NULL) { mp_raise_OSError(errno); } - return MP_OBJ_FROM_PTR(btree_new(db)); + return MP_OBJ_FROM_PTR(btree_new(db, pos_args[0])); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_btree_open_obj, 1, mod_btree_open); diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index 3560cc38dc..2524e21541 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -22,6 +22,10 @@ typedef struct _mp_obj_framebuf_t { uint8_t format; } mp_obj_framebuf_t; +#if !MICROPY_ENABLE_DYNRUNTIME +STATIC const mp_obj_type_t mp_type_framebuf; +#endif + typedef void (*setpixel_t)(const mp_obj_framebuf_t *, int, int, uint32_t); typedef uint32_t (*getpixel_t)(const mp_obj_framebuf_t *, int, int); typedef void (*fill_rect_t)(const mp_obj_framebuf_t *, int, int, int, int, uint32_t); @@ -278,7 +282,7 @@ STATIC mp_obj_t framebuf_make_new(const mp_obj_type_t *type, size_t n_args, cons case FRAMEBUF_GS8: break; default: - mp_raise_ValueError(translate("invalid format")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid format")); } return MP_OBJ_FROM_PTR(o); @@ -288,7 +292,7 @@ STATIC const mp_obj_type_t mp_type_framebuf; // Helper to ensure we have the native super class instead of a subclass. static mp_obj_framebuf_t *native_framebuf(mp_obj_t framebuf_obj) { - mp_obj_t native_framebuf = mp_instance_cast_to_native_base(framebuf_obj, &mp_type_framebuf); + mp_obj_t native_framebuf = mp_obj_cast_to_native_base(framebuf_obj, &mp_type_framebuf); mp_obj_assert_native_inited(native_framebuf); return MP_OBJ_TO_PTR(native_framebuf); } diff --git a/extmod/moduasyncio.c b/extmod/moduasyncio.c new file mode 100644 index 0000000000..2b647d06f2 --- /dev/null +++ b/extmod/moduasyncio.c @@ -0,0 +1,294 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/smallint.h" +#include "py/pairheap.h" +#include "py/mphal.h" + +#if MICROPY_PY_UASYNCIO + +typedef struct _mp_obj_task_t { + mp_pairheap_t pairheap; + mp_obj_t coro; + mp_obj_t data; + mp_obj_t waiting; + + mp_obj_t ph_key; +} mp_obj_task_t; + +typedef struct _mp_obj_task_queue_t { + mp_obj_base_t base; + mp_obj_task_t *heap; +} mp_obj_task_queue_t; + +STATIC const mp_obj_type_t task_queue_type; +STATIC const mp_obj_type_t task_type; + +STATIC mp_obj_t task_queue_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); + +/******************************************************************************/ +// Ticks for task ordering in pairing heap + +STATIC mp_obj_t ticks(void) { + return MP_OBJ_NEW_SMALL_INT(mp_hal_ticks_ms() & (MICROPY_PY_UTIME_TICKS_PERIOD - 1)); +} + +STATIC mp_int_t ticks_diff(mp_obj_t t1_in, mp_obj_t t0_in) { + mp_uint_t t0 = MP_OBJ_SMALL_INT_VALUE(t0_in); + mp_uint_t t1 = MP_OBJ_SMALL_INT_VALUE(t1_in); + mp_int_t diff = ((t1 - t0 + MICROPY_PY_UTIME_TICKS_PERIOD / 2) & (MICROPY_PY_UTIME_TICKS_PERIOD - 1)) + - MICROPY_PY_UTIME_TICKS_PERIOD / 2; + return diff; +} + +STATIC int task_lt(mp_pairheap_t *n1, mp_pairheap_t *n2) { + mp_obj_task_t *t1 = (mp_obj_task_t *)n1; + mp_obj_task_t *t2 = (mp_obj_task_t *)n2; + return MP_OBJ_SMALL_INT_VALUE(ticks_diff(t1->ph_key, t2->ph_key)) < 0; +} + +/******************************************************************************/ +// TaskQueue class + +STATIC mp_obj_t task_queue_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + (void)args; + mp_arg_check_num(n_args, kw_args, 0, 0, false); + mp_obj_task_queue_t *self = m_new_obj(mp_obj_task_queue_t); + self->base.type = type; + self->heap = (mp_obj_task_t *)mp_pairheap_new(task_lt); + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t task_queue_peek(mp_obj_t self_in) { + mp_obj_task_queue_t *self = MP_OBJ_TO_PTR(self_in); + if (self->heap == NULL) { + return mp_const_none; + } else { + return MP_OBJ_FROM_PTR(self->heap); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_queue_peek_obj, task_queue_peek); + +STATIC mp_obj_t task_queue_push_sorted(size_t n_args, const mp_obj_t *args) { + mp_obj_task_queue_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_task_t *task = MP_OBJ_TO_PTR(args[1]); + task->data = mp_const_none; + if (n_args == 2) { + task->ph_key = ticks(); + } else { + assert(mp_obj_is_small_int(args[2])); + task->ph_key = args[2]; + } + self->heap = (mp_obj_task_t *)mp_pairheap_push(task_lt, &self->heap->pairheap, &task->pairheap); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(task_queue_push_sorted_obj, 2, 3, task_queue_push_sorted); + +STATIC mp_obj_t task_queue_pop_head(mp_obj_t self_in) { + mp_obj_task_queue_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_task_t *head = (mp_obj_task_t *)mp_pairheap_peek(task_lt, &self->heap->pairheap); + if (head == NULL) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap")); + } + self->heap = (mp_obj_task_t *)mp_pairheap_pop(task_lt, &self->heap->pairheap); + return MP_OBJ_FROM_PTR(head); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_queue_pop_head_obj, task_queue_pop_head); + +STATIC mp_obj_t task_queue_remove(mp_obj_t self_in, mp_obj_t task_in) { + mp_obj_task_queue_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_task_t *task = MP_OBJ_TO_PTR(task_in); + self->heap = (mp_obj_task_t *)mp_pairheap_delete(task_lt, &self->heap->pairheap, &task->pairheap); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(task_queue_remove_obj, task_queue_remove); + +STATIC const mp_rom_map_elem_t task_queue_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_peek), MP_ROM_PTR(&task_queue_peek_obj) }, + { MP_ROM_QSTR(MP_QSTR_push_sorted), MP_ROM_PTR(&task_queue_push_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_push_head), MP_ROM_PTR(&task_queue_push_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_pop_head), MP_ROM_PTR(&task_queue_pop_head_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&task_queue_remove_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(task_queue_locals_dict, task_queue_locals_dict_table); + +STATIC const mp_obj_type_t task_queue_type = { + { &mp_type_type }, + .name = MP_QSTR_TaskQueue, + .make_new = task_queue_make_new, + .locals_dict = (mp_obj_dict_t *)&task_queue_locals_dict, +}; + +/******************************************************************************/ +// Task class + +// This is the core uasyncio context with cur_task, _task_queue and CancelledError. +STATIC mp_obj_t uasyncio_context = MP_OBJ_NULL; + +STATIC mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + mp_arg_check_num(n_args, kw_args, 1, 2, false); + mp_obj_task_t *self = m_new_obj(mp_obj_task_t); + self->pairheap.base.type = type; + mp_pairheap_init_node(task_lt, &self->pairheap); + self->coro = args[0]; + self->data = mp_const_none; + self->waiting = mp_const_none; + self->ph_key = MP_OBJ_NEW_SMALL_INT(0); + if (n_args == 2) { + uasyncio_context = args[1]; + } + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t task_cancel(mp_obj_t self_in) { + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + // Check if task is already finished. + if (self->coro == mp_const_none) { + return mp_const_false; + } + // Can't cancel self (not supported yet). + mp_obj_t cur_task = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task)); + if (self_in == cur_task) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("can't cancel self")); + } + // If Task waits on another task then forward the cancel to the one it's waiting on. + while (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(self->data)), MP_OBJ_FROM_PTR(&task_type))) { + self = MP_OBJ_TO_PTR(self->data); + } + + mp_obj_t _task_queue = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__task_queue)); + + // Reschedule Task as a cancelled task. + mp_obj_t dest[3]; + mp_load_method_maybe(self->data, MP_QSTR_remove, dest); + if (dest[0] != MP_OBJ_NULL) { + // Not on the main running queue, remove the task from the queue it's on. + dest[2] = MP_OBJ_FROM_PTR(self); + mp_call_method_n_kw(1, 0, dest); + // _task_queue.push_head(self) + dest[0] = _task_queue; + dest[1] = MP_OBJ_FROM_PTR(self); + task_queue_push_sorted(2, dest); + } else if (ticks_diff(self->ph_key, ticks()) > 0) { + // On the main running queue but scheduled in the future, so bring it forward to now. + // _task_queue.remove(self) + task_queue_remove(_task_queue, MP_OBJ_FROM_PTR(self)); + // _task_queue.push_head(self) + dest[0] = _task_queue; + dest[1] = MP_OBJ_FROM_PTR(self); + task_queue_push_sorted(2, dest); + } + + self->data = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_CancelledError)); + + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_cancel_obj, task_cancel); + +STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] == MP_OBJ_NULL) { + // Load + if (attr == MP_QSTR_coro) { + dest[0] = self->coro; + } else if (attr == MP_QSTR_data) { + dest[0] = self->data; + } else if (attr == MP_QSTR_waiting) { + if (self->waiting != mp_const_none) { + dest[0] = self->waiting; + } + } else if (attr == MP_QSTR_cancel) { + dest[0] = MP_OBJ_FROM_PTR(&task_cancel_obj); + dest[1] = self_in; + } else if (attr == MP_QSTR_ph_key) { + dest[0] = self->ph_key; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store + if (attr == MP_QSTR_coro) { + self->coro = dest[1]; + dest[0] = MP_OBJ_NULL; + } else if (attr == MP_QSTR_data) { + self->data = dest[1]; + dest[0] = MP_OBJ_NULL; + } else if (attr == MP_QSTR_waiting) { + self->waiting = dest[1]; + dest[0] = MP_OBJ_NULL; + } + } +} + +STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + (void)iter_buf; + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + if (self->waiting == mp_const_none) { + self->waiting = task_queue_make_new(&task_queue_type, 0, NULL, NULL); + } + return self_in; +} + +STATIC mp_obj_t task_iternext(mp_obj_t self_in) { + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + if (self->coro == mp_const_none) { + // Task finished, raise return value to caller so it can continue. + nlr_raise(self->data); + } else { + // Put calling task on waiting queue. + mp_obj_t cur_task = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task)); + mp_obj_t args[2] = { self->waiting, cur_task }; + task_queue_push_sorted(2, args); + // Set calling task's data to this task that it waits on, to double-link it. + ((mp_obj_task_t *)MP_OBJ_TO_PTR(cur_task))->data = self_in; + } + return mp_const_none; +} + +STATIC const mp_obj_type_t task_type = { + { &mp_type_type }, + .name = MP_QSTR_Task, + .make_new = task_make_new, + .attr = task_attr, + .getiter = task_getiter, + .iternext = task_iternext, +}; + +/******************************************************************************/ +// C-level uasyncio module + +STATIC const mp_rom_map_elem_t mp_module_uasyncio_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__uasyncio) }, + { MP_ROM_QSTR(MP_QSTR_TaskQueue), MP_ROM_PTR(&task_queue_type) }, + { MP_ROM_QSTR(MP_QSTR_Task), MP_ROM_PTR(&task_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_uasyncio_globals, mp_module_uasyncio_globals_table); + +const mp_obj_module_t mp_module_uasyncio = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_uasyncio_globals, +}; + +#endif // MICROPY_PY_UASYNCIO diff --git a/extmod/modubinascii.c b/extmod/modubinascii.c index 3fae9e9e4d..e2435d108e 100644 --- a/extmod/modubinascii.c +++ b/extmod/modubinascii.c @@ -9,7 +9,6 @@ #include "py/runtime.h" #include "py/binary.h" -#include "extmod/modubinascii.h" static void check_not_unicode(const mp_obj_t arg) { #if MICROPY_CPYTHON_COMPAT @@ -19,7 +18,7 @@ static void check_not_unicode(const mp_obj_t arg) { #endif } -mp_obj_t mod_binascii_hexlify(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t mod_binascii_hexlify(size_t n_args, const mp_obj_t *args) { // Second argument is for an extension to allow a separator to be used // between values. const char *sep = NULL; @@ -59,14 +58,14 @@ mp_obj_t mod_binascii_hexlify(size_t n_args, const mp_obj_t *args) { } return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_hexlify_obj, 1, 2, mod_binascii_hexlify); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_hexlify_obj, 1, 2, mod_binascii_hexlify); -mp_obj_t mod_binascii_unhexlify(mp_obj_t data) { +STATIC mp_obj_t mod_binascii_unhexlify(mp_obj_t data) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ); if ((bufinfo.len & 1) != 0) { - mp_raise_ValueError(translate("odd-length string")); + mp_raise_ValueError(MP_ERROR_TEXT("odd-length string")); } vstr_t vstr; vstr_init_len(&vstr, bufinfo.len / 2); @@ -77,7 +76,7 @@ mp_obj_t mod_binascii_unhexlify(mp_obj_t data) { if (unichar_isxdigit(hex_ch)) { hex_byte += unichar_xdigit_value(hex_ch); } else { - mp_raise_ValueError(translate("non-hex digit found")); + mp_raise_ValueError(MP_ERROR_TEXT("non-hex digit found")); } if (i & 1) { hex_byte <<= 4; @@ -88,7 +87,7 @@ mp_obj_t mod_binascii_unhexlify(mp_obj_t data) { } return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } -MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_unhexlify_obj, mod_binascii_unhexlify); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_unhexlify_obj, mod_binascii_unhexlify); // If ch is a character in the base64 alphabet, and is not a pad character, then // the corresponding integer between 0 and 63, inclusively, is returned. @@ -109,7 +108,7 @@ static int mod_binascii_sextet(byte ch) { } } -mp_obj_t mod_binascii_a2b_base64(mp_obj_t data) { +STATIC mp_obj_t mod_binascii_a2b_base64(mp_obj_t data) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ); byte *in = bufinfo.buf; @@ -145,14 +144,14 @@ mp_obj_t mod_binascii_a2b_base64(mp_obj_t data) { } if (nbits) { - mp_raise_ValueError(translate("incorrect padding")); + mp_raise_ValueError(MP_ERROR_TEXT("incorrect padding")); } return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } -MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_a2b_base64_obj, mod_binascii_a2b_base64); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_a2b_base64_obj, mod_binascii_a2b_base64); -mp_obj_t mod_binascii_b2a_base64(mp_obj_t data) { +STATIC mp_obj_t mod_binascii_b2a_base64(mp_obj_t data) { check_not_unicode(data); mp_buffer_info_t bufinfo; mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ); @@ -203,12 +202,12 @@ mp_obj_t mod_binascii_b2a_base64(mp_obj_t data) { *out = '\n'; return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } -MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_b2a_base64_obj, mod_binascii_b2a_base64); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_binascii_b2a_base64_obj, mod_binascii_b2a_base64); #if MICROPY_PY_UBINASCII_CRC32 #include "../../lib/uzlib/src/tinf.h" -mp_obj_t mod_binascii_crc32(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t mod_binascii_crc32(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo; check_not_unicode(args[0]); mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); @@ -216,11 +215,9 @@ mp_obj_t mod_binascii_crc32(size_t n_args, const mp_obj_t *args) { crc = uzlib_crc32(bufinfo.buf, bufinfo.len, crc ^ 0xffffffff); return mp_obj_new_int_from_uint(crc ^ 0xffffffff); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_crc32_obj, 1, 2, mod_binascii_crc32); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_crc32_obj, 1, 2, mod_binascii_crc32); #endif -#if MICROPY_PY_UBINASCII - STATIC const mp_rom_map_elem_t mp_module_binascii_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_binascii) }, { MP_ROM_QSTR(MP_QSTR_hexlify), MP_ROM_PTR(&mod_binascii_hexlify_obj) }, @@ -238,5 +235,3 @@ const mp_obj_module_t mp_module_ubinascii = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_binascii_globals, }; - -#endif // MICROPY_PY_UBINASCII diff --git a/extmod/modubinascii.h b/extmod/modubinascii.h deleted file mode 100644 index eb47e286bf..0000000000 --- a/extmod/modubinascii.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2014 Paul Sokolovsky -// SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) -// -// SPDX-License-Identifier: MIT - -#ifndef MICROPY_INCLUDED_EXTMOD_MODUBINASCII_H -#define MICROPY_INCLUDED_EXTMOD_MODUBINASCII_H - -extern mp_obj_t mod_binascii_hexlify(size_t n_args, const mp_obj_t *args); -extern mp_obj_t mod_binascii_unhexlify(mp_obj_t data); -extern mp_obj_t mod_binascii_a2b_base64(mp_obj_t data); -extern mp_obj_t mod_binascii_b2a_base64(mp_obj_t data); -extern mp_obj_t mod_binascii_crc32(size_t n_args, const mp_obj_t *args); - -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_hexlify_obj); -MP_DECLARE_CONST_FUN_OBJ_1(mod_binascii_unhexlify_obj); -MP_DECLARE_CONST_FUN_OBJ_1(mod_binascii_a2b_base64_obj); -MP_DECLARE_CONST_FUN_OBJ_1(mod_binascii_b2a_base64_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_binascii_crc32_obj); - -#endif // MICROPY_INCLUDED_EXTMOD_MODUBINASCII_H diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 1f98672895..7705dbc16e 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -98,7 +98,7 @@ typedef struct _mp_obj_uctypes_struct_t { } mp_obj_uctypes_struct_t; STATIC NORETURN void syntax_error(void) { - mp_raise_TypeError(translate("syntax error in uctypes descriptor")); + mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor")); } STATIC mp_obj_t uctypes_struct_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { @@ -118,11 +118,7 @@ STATIC void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_p (void)kind; mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in); const char *typen = "unk"; - if (mp_obj_is_type(self->desc, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - || mp_obj_is_type(self->desc, &mp_type_ordereddict) - #endif - ) { + if (mp_obj_is_dict_or_ordereddict(self->desc)) { typen = "STRUCT"; } else if (mp_obj_is_type(self->desc, &mp_type_tuple)) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->desc); @@ -195,11 +191,7 @@ STATIC mp_uint_t uctypes_struct_agg_size(mp_obj_tuple_t *t, int layout_type, mp_ } STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_t *max_field_size) { - if (!mp_obj_is_type(desc_in, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - && !mp_obj_is_type(desc_in, &mp_type_ordereddict) - #endif - ) { + if (!mp_obj_is_dict_or_ordereddict(desc_in)) { if (mp_obj_is_type(desc_in, &mp_type_tuple)) { return uctypes_struct_agg_size((mp_obj_tuple_t *)MP_OBJ_TO_PTR(desc_in), layout_type, max_field_size); } else if (mp_obj_is_small_int(desc_in)) { @@ -207,7 +199,7 @@ STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_ // but scalar structure field is lowered into native Python int, so all // type info is lost. So, we cannot say if it's scalar type description, // or such lowered scalar. - mp_raise_TypeError(translate("Cannot unambiguously get sizeof scalar")); + mp_raise_TypeError(MP_ERROR_TEXT("cannot unambiguously get sizeof scalar")); } syntax_error(); } @@ -341,9 +333,9 @@ STATIC mp_obj_t get_aligned(uint val_type, void *p, mp_int_t index) { return mp_obj_new_int_from_ll(((int64_t *)p)[index]); #if MICROPY_PY_BUILTINS_FLOAT case FLOAT32: - return mp_obj_new_float(((float *)p)[index]); + return mp_obj_new_float_from_f(((float *)p)[index]); case FLOAT64: - return mp_obj_new_float(((double *)p)[index]); + return mp_obj_new_float_from_d(((double *)p)[index]); #endif default: assert(0); @@ -354,11 +346,10 @@ STATIC mp_obj_t get_aligned(uint val_type, void *p, mp_int_t index) { STATIC void set_aligned(uint val_type, void *p, mp_int_t index, mp_obj_t val) { #if MICROPY_PY_BUILTINS_FLOAT if (val_type == FLOAT32 || val_type == FLOAT64) { - mp_float_t v = mp_obj_get_float(val); if (val_type == FLOAT32) { - ((float *)p)[index] = v; + ((float *)p)[index] = mp_obj_get_float_to_f(val); } else { - ((double *)p)[index] = v; + ((double *)p)[index] = mp_obj_get_float_to_d(val); } return; } @@ -400,12 +391,8 @@ STATIC void set_aligned(uint val_type, void *p, mp_int_t index, mp_obj_t val) { STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set_val) { mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in); - if (!mp_obj_is_type(self->desc, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - && !mp_obj_is_type(self->desc, &mp_type_ordereddict) - #endif - ) { - mp_raise_TypeError(translate("struct: no fields")); + if (!mp_obj_is_dict_or_ordereddict(self->desc)) { + mp_raise_TypeError(MP_ERROR_TEXT("struct: no fields")); } mp_obj_t deref = mp_obj_dict_get(self->desc, MP_OBJ_NEW_QSTR(attr)); @@ -530,7 +517,7 @@ STATIC void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t base_in, mp_obj_t index_in, mp_obj_t value) { - mp_obj_uctypes_struct_t *self = mp_instance_cast_to_native_base(base_in, &uctypes_struct_type); + mp_obj_uctypes_struct_t *self = mp_obj_cast_to_native_base(base_in, &uctypes_struct_type); if (value == MP_OBJ_NULL) { // delete @@ -538,7 +525,7 @@ STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t base_in, mp_obj_t index_in, mp_ob } else { // load / store if (!mp_obj_is_type(self->desc, &mp_type_tuple)) { - mp_raise_TypeError(translate("struct: cannot index")); + mp_raise_TypeError(MP_ERROR_TEXT("struct: can't index")); } mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->desc); @@ -552,7 +539,7 @@ STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t base_in, mp_obj_t index_in, mp_ob uint val_type = GET_TYPE(arr_sz, VAL_TYPE_BITS); arr_sz &= VALUE_MASK(VAL_TYPE_BITS); if (index >= arr_sz) { - mp_raise_IndexError_varg(translate("%q index out of range"), MP_QSTR_struct); + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("struct: index out of range")); } if (t->len == 2) { diff --git a/extmod/moduheapq.c b/extmod/moduheapq.c index b3c996216d..34867a5838 100644 --- a/extmod/moduheapq.c +++ b/extmod/moduheapq.c @@ -14,7 +14,7 @@ STATIC mp_obj_list_t *uheapq_get_heap(mp_obj_t heap_in) { if (!mp_obj_is_type(heap_in, &mp_type_list)) { - mp_raise_TypeError(translate("heap must be a list")); + mp_raise_TypeError(MP_ERROR_TEXT("heap must be a list")); } return MP_OBJ_TO_PTR(heap_in); } @@ -62,7 +62,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_uheapq_heappush_obj, mod_uheapq_heappush); STATIC mp_obj_t mod_uheapq_heappop(mp_obj_t heap_in) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); if (heap->len == 0) { - mp_raise_IndexError(translate("empty heap")); + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap")); } mp_obj_t item = heap->items[0]; heap->len -= 1; diff --git a/extmod/modujson.c b/extmod/modujson.c index 60e725c500..a9d231785a 100644 --- a/extmod/modujson.c +++ b/extmod/modujson.c @@ -135,7 +135,7 @@ STATIC mp_obj_t _mod_ujson_load(mp_obj_t stream_obj, bool return_first_json) { stack.len = 0; stack.items = NULL; mp_obj_t stack_top = MP_OBJ_NULL; - mp_obj_type_t *stack_top_type = NULL; + const mp_obj_type_t *stack_top_type = NULL; mp_obj_t stack_key = MP_OBJ_NULL; S_NEXT(s); for (;;) { @@ -339,7 +339,7 @@ success: return stack_top; fail: - mp_raise_ValueError(translate("syntax error in JSON")); + mp_raise_ValueError(MP_ERROR_TEXT("syntax error in JSON")); } STATIC mp_obj_t mod_ujson_load(mp_obj_t stream_obj) { diff --git a/extmod/modurandom.c b/extmod/modurandom.c index 1d5799f743..2b6970c4ed 100644 --- a/extmod/modurandom.c +++ b/extmod/modurandom.c @@ -132,7 +132,7 @@ STATIC mp_obj_t mod_urandom_choice(mp_obj_t seq) { if (len > 0) { return mp_obj_subscr(seq, mp_obj_new_int(yasmarang_randbelow(len)), MP_OBJ_SENTINEL); } else { - nlr_raise(mp_obj_new_exception(&mp_type_IndexError)); + mp_raise_type(&mp_type_IndexError); } } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_urandom_choice_obj, mod_urandom_choice); @@ -141,21 +141,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_urandom_choice_obj, mod_urandom_choice); // returns a number in the range [0..1) using Yasmarang to fill in the fraction bits STATIC mp_float_t yasmarang_float(void) { - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE - typedef uint64_t mp_float_int_t; - #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - typedef uint32_t mp_float_int_t; - #endif - union { - mp_float_t f; - #if MP_ENDIANNESS_LITTLE - struct { mp_float_int_t frc : MP_FLOAT_FRAC_BITS, exp : MP_FLOAT_EXP_BITS, sgn : 1; - } p; - #else - struct { mp_float_int_t sgn : 1, exp : MP_FLOAT_EXP_BITS, frc : MP_FLOAT_FRAC_BITS; - } p; - #endif - } u; + mp_float_union_t u; u.p.sgn = 0; u.p.exp = (1 << (MP_FLOAT_EXP_BITS - 1)) - 1; if (MP_FLOAT_FRAC_BITS <= 32) { diff --git a/extmod/modure.c b/extmod/modure.c index 305fa314be..dead69586c 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -18,7 +18,7 @@ #include "re1.5/re1.5.h" -#if CIRCUITPY_RE_DEBUG +#if MICROPY_PY_URE_DEBUG #define FLAG_DEBUG 0x1000 #endif @@ -34,6 +34,10 @@ typedef struct _mp_obj_match_t { const char *caps[0]; } mp_obj_match_t; +STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args); +#if !MICROPY_ENABLE_DYNRUNTIME +STATIC const mp_obj_type_t re_type; +#endif STATIC void match_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -156,7 +160,12 @@ STATIC void re_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t STATIC mp_obj_t ure_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { (void)n_args; - mp_obj_re_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_re_t *self; + if (mp_obj_is_type(args[0], &re_type)) { + self = MP_OBJ_TO_PTR(args[0]); + } else { + self = MP_OBJ_TO_PTR(mod_re_compile(1, args)); + } Subject subj; size_t len; subj.begin = mp_obj_str_get_data(args[1], &len); @@ -245,7 +254,7 @@ STATIC mp_obj_t re_split(size_t n_args, const mp_obj_t *args) { mp_obj_t s = mp_obj_new_str_of_type(str_type, (const byte *)subj.begin, caps[0] - subj.begin); mp_obj_list_append(retval, s); if (self->re.sub > 0) { - mp_raise_NotImplementedError(translate("Splitting with sub-captures")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("Splitting with sub-captures")); } subj.begin = caps[1]; if (maxsplit > 0 && --maxsplit == 0) { @@ -408,7 +417,7 @@ STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { } mp_obj_re_t *o = m_new_obj_var(mp_obj_re_t, char, size); o->base.type = &re_type; - #if CIRCUITPY_RE_DEBUG + #if MICROPY_PY_URE_DEBUG int flags = 0; if (n_args > 1) { flags = mp_obj_get_int(args[1]); @@ -419,9 +428,9 @@ STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { int error = re1_5_compilecode(&o->re, re_str); if (error != 0) { error: - mp_raise_ValueError(translate("Error in regex")); + mp_raise_ValueError(MP_ERROR_TEXT("Error in regex")); } - #if CIRCUITPY_RE_DEBUG + #if MICROPY_PY_URE_DEBUG if (flags & FLAG_DEBUG) { re1_5_dumpcode(&o->re); } @@ -452,7 +461,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_search_obj, 2, 4, mod_re_search); #if MICROPY_PY_URE_SUB STATIC mp_obj_t mod_re_sub(size_t n_args, const mp_obj_t *args) { mp_obj_t self = mod_re_compile(1, args); - return re_sub_helper(self, n_args, args); + return re_sub_helper(MP_OBJ_TO_PTR(self), n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_sub_obj, 3, 5, mod_re_sub); #endif @@ -470,7 +479,7 @@ STATIC const mp_rom_map_elem_t mp_module_re_globals_table[] = { #if MICROPY_PY_URE_SUB { MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&mod_re_sub_obj) }, #endif - #if CIRCUITPY_RE_DEBUG + #if MICROPY_PY_URE_DEBUG { MP_ROM_QSTR(MP_QSTR_DEBUG), MP_ROM_INT(FLAG_DEBUG) }, #endif }; @@ -488,7 +497,7 @@ const mp_obj_module_t mp_module_ure = { #define re1_5_fatal(x) assert(!x) #include "re1.5/compilecode.c" -#if CIRCUITPY_RE_DEBUG +#if MICROPY_PY_URE_DEBUG #include "re1.5/dumpcode.c" #endif #include "re1.5/recursiveloop.c" diff --git a/extmod/moduselect.c b/extmod/moduselect.c index 50587184c0..eecc13c92f 100644 --- a/extmod/moduselect.c +++ b/extmod/moduselect.c @@ -105,7 +105,7 @@ STATIC mp_obj_t select_select(size_t n_args, const mp_obj_t *args) { if (n_args == 4) { if (args[3] != mp_const_none) { #if MICROPY_PY_BUILTINS_FLOAT - float timeout_f = mp_obj_get_float(args[3]); + float timeout_f = mp_obj_get_float_to_f(args[3]); if (timeout_f >= 0) { timeout = (mp_uint_t)(timeout_f * 1000); } diff --git a/extmod/modutimeq.c b/extmod/modutimeq.c index 3ef7646e96..94bb4f1024 100644 --- a/extmod/modutimeq.c +++ b/extmod/modutimeq.c @@ -107,7 +107,7 @@ STATIC mp_obj_t mod_utimeq_heappush(size_t n_args, const mp_obj_t *args) { mp_obj_t heap_in = args[0]; mp_obj_utimeq_t *heap = utimeq_get_heap(heap_in); if (heap->len == heap->alloc) { - mp_raise_IndexError(translate("queue overflow")); + mp_raise_IndexError(MP_ERROR_TEXT("queue overflow")); } mp_uint_t l = heap->len; heap->items[l].time = MP_OBJ_SMALL_INT_VALUE(args[1]); @@ -123,7 +123,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_utimeq_heappush_obj, 4, 4, mod_ut STATIC mp_obj_t mod_utimeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) { mp_obj_utimeq_t *heap = utimeq_get_heap(heap_in); if (heap->len == 0) { - mp_raise_IndexError(translate("empty heap")); + mp_raise_IndexError(MP_ERROR_TEXT("empty heap")); } mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref); if (!mp_obj_is_type(list_ref, &mp_type_list) || ret->len < 3) { @@ -148,7 +148,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_utimeq_heappop_obj, mod_utimeq_heappop); STATIC mp_obj_t mod_utimeq_peektime(mp_obj_t heap_in) { mp_obj_utimeq_t *heap = utimeq_get_heap(heap_in); if (heap->len == 0) { - mp_raise_IndexError(translate("empty heap")); + mp_raise_IndexError(MP_ERROR_TEXT("empty heap")); } struct qentry *item = &heap->items[0]; diff --git a/extmod/moduzlib.c b/extmod/moduzlib.c index 1e8d0cd365..e580075546 100644 --- a/extmod/moduzlib.c +++ b/extmod/moduzlib.c @@ -43,7 +43,7 @@ STATIC int read_src_stream(TINF_DATA *data) { mp_raise_OSError(err); } if (out_sz == 0) { - nlr_raise(mp_obj_new_exception(&mp_type_EOFError)); + mp_raise_type(&mp_type_EOFError); } return c; } @@ -74,7 +74,7 @@ STATIC mp_obj_t decompio_make_new(const mp_obj_type_t *type, size_t n_args, cons dict_opt = uzlib_zlib_parse_header(&o->decomp); if (dict_opt < 0) { header_error: - mp_raise_ValueError(translate("compression header")); + mp_raise_ValueError(MP_ERROR_TEXT("compression header")); } dict_sz = 1 << dict_opt; } else { diff --git a/extmod/re1.5/compilecode.c b/extmod/re1.5/compilecode.c index 936f6ed28a..811742eea1 100644 --- a/extmod/re1.5/compilecode.c +++ b/extmod/re1.5/compilecode.c @@ -87,6 +87,9 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode) PC++; // Skip # of pair byte prog->len++; for (cnt = 0; *re != ']'; re++, cnt++) { + if (*re == '\\') { + ++re; + } if (!*re) return NULL; const char *b = re; if (*re == '\\') { diff --git a/extmod/uasyncio/__init__.py b/extmod/uasyncio/__init__.py new file mode 100644 index 0000000000..08f924cf29 --- /dev/null +++ b/extmod/uasyncio/__init__.py @@ -0,0 +1,29 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George + +from .core import * + +__version__ = (3, 0, 0) + +_attrs = { + "wait_for": "funcs", + "wait_for_ms": "funcs", + "gather": "funcs", + "Event": "event", + "Lock": "lock", + "open_connection": "stream", + "start_server": "stream", + "StreamReader": "stream", + "StreamWriter": "stream", +} + +# Lazy loader, effectively does: +# global attr +# from .mod import attr +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, None, None, True, 1), attr) + globals()[attr] = value + return value diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py new file mode 100644 index 0000000000..045b4cd139 --- /dev/null +++ b/extmod/uasyncio/core.py @@ -0,0 +1,277 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George + +from time import ticks_ms as ticks, ticks_diff, ticks_add +import sys, select + +# Import TaskQueue and Task, preferring built-in C code over Python code +try: + from _uasyncio import TaskQueue, Task +except: + from .task import TaskQueue, Task + + +################################################################################ +# Exceptions + + +class CancelledError(BaseException): + pass + + +class TimeoutError(Exception): + pass + + +# Used when calling Loop.call_exception_handler +_exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None} + + +################################################################################ +# Sleep functions + +# "Yield" once, then raise StopIteration +class SingletonGenerator: + def __init__(self): + self.state = None + self.exc = StopIteration() + + def __iter__(self): + return self + + def __next__(self): + if self.state is not None: + _task_queue.push_sorted(cur_task, self.state) + self.state = None + return None + else: + self.exc.__traceback__ = None + raise self.exc + + +# Pause task execution for the given time (integer in milliseconds, uPy extension) +# Use a SingletonGenerator to do it without allocating on the heap +def sleep_ms(t, sgen=SingletonGenerator()): + assert sgen.state is None + sgen.state = ticks_add(ticks(), max(0, t)) + return sgen + + +# Pause task execution for the given time (in seconds) +def sleep(t): + return sleep_ms(int(t * 1000)) + + +################################################################################ +# Queue and poller for stream IO + + +class IOQueue: + def __init__(self): + self.poller = select.poll() + self.map = {} # maps id(stream) to [task_waiting_read, task_waiting_write, stream] + + def _enqueue(self, s, idx): + if id(s) not in self.map: + entry = [None, None, s] + entry[idx] = cur_task + self.map[id(s)] = entry + self.poller.register(s, select.POLLIN if idx == 0 else select.POLLOUT) + else: + sm = self.map[id(s)] + assert sm[idx] is None + assert sm[1 - idx] is not None + sm[idx] = cur_task + self.poller.modify(s, select.POLLIN | select.POLLOUT) + # Link task to this IOQueue so it can be removed if needed + cur_task.data = self + + def _dequeue(self, s): + del self.map[id(s)] + self.poller.unregister(s) + + def queue_read(self, s): + self._enqueue(s, 0) + + def queue_write(self, s): + self._enqueue(s, 1) + + def remove(self, task): + while True: + del_s = None + for k in self.map: # Iterate without allocating on the heap + q0, q1, s = self.map[k] + if q0 is task or q1 is task: + del_s = s + break + if del_s is not None: + self._dequeue(s) + else: + break + + def wait_io_event(self, dt): + for s, ev in self.poller.ipoll(dt): + sm = self.map[id(s)] + # print('poll', s, sm, ev) + if ev & ~select.POLLOUT and sm[0] is not None: + # POLLIN or error + _task_queue.push_head(sm[0]) + sm[0] = None + if ev & ~select.POLLIN and sm[1] is not None: + # POLLOUT or error + _task_queue.push_head(sm[1]) + sm[1] = None + if sm[0] is None and sm[1] is None: + self._dequeue(s) + elif sm[0] is None: + self.poller.modify(s, select.POLLOUT) + else: + self.poller.modify(s, select.POLLIN) + + +################################################################################ +# Main run loop + +# Ensure the awaitable is a task +def _promote_to_task(aw): + return aw if isinstance(aw, Task) else create_task(aw) + + +# Create and schedule a new task from a coroutine +def create_task(coro): + if not hasattr(coro, "send"): + raise TypeError("coroutine expected") + t = Task(coro, globals()) + _task_queue.push_head(t) + return t + + +# Keep scheduling tasks until there are none left to schedule +def run_until_complete(main_task=None): + global cur_task + excs_all = (CancelledError, Exception) # To prevent heap allocation in loop + excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop + while True: + # Wait until the head of _task_queue is ready to run + dt = 1 + while dt > 0: + dt = -1 + t = _task_queue.peek() + if t: + # A task waiting on _task_queue; "ph_key" is time to schedule task at + dt = max(0, ticks_diff(t.ph_key, ticks())) + elif not _io_queue.map: + # No tasks can be woken so finished running + return + # print('(poll {})'.format(dt), len(_io_queue.map)) + _io_queue.wait_io_event(dt) + + # Get next task to run and continue it + t = _task_queue.pop_head() + cur_task = t + try: + # Continue running the coroutine, it's responsible for rescheduling itself + exc = t.data + if not exc: + t.coro.send(None) + else: + t.data = None + t.coro.throw(exc) + except excs_all as er: + # Check the task is not on any event queue + assert t.data is None + # This task is done, check if it's the main task and then loop should stop + if t is main_task: + if isinstance(er, StopIteration): + return er.value + raise er + # Save return value of coro to pass up to caller + t.data = er + # Schedule any other tasks waiting on the completion of this task + waiting = False + if hasattr(t, "waiting"): + while t.waiting.peek(): + _task_queue.push_head(t.waiting.pop_head()) + waiting = True + t.waiting = None # Free waiting queue head + # Print out exception for detached tasks + if not waiting and not isinstance(er, excs_stop): + _exc_context["exception"] = er + _exc_context["future"] = t + Loop.call_exception_handler(_exc_context) + # Indicate task is done + t.coro = None + + +# Create a new task from a coroutine and run it until it finishes +def run(coro): + return run_until_complete(create_task(coro)) + + +################################################################################ +# Event loop wrapper + + +async def _stopper(): + pass + + +_stop_task = None + + +class Loop: + _exc_handler = None + + def create_task(coro): + return create_task(coro) + + def run_forever(): + global _stop_task + _stop_task = Task(_stopper(), globals()) + run_until_complete(_stop_task) + # TODO should keep running until .stop() is called, even if there're no tasks left + + def run_until_complete(aw): + return run_until_complete(_promote_to_task(aw)) + + def stop(): + global _stop_task + if _stop_task is not None: + _task_queue.push_head(_stop_task) + # If stop() is called again, do nothing + _stop_task = None + + def close(): + pass + + def set_exception_handler(handler): + Loop._exc_handler = handler + + def get_exception_handler(): + return Loop._exc_handler + + def default_exception_handler(loop, context): + print(context["message"]) + print("future:", context["future"], "coro=", context["future"].coro) + sys.print_exception(context["exception"]) + + def call_exception_handler(context): + (Loop._exc_handler or Loop.default_exception_handler)(Loop, context) + + +# The runq_len and waitq_len arguments are for legacy uasyncio compatibility +def get_event_loop(runq_len=0, waitq_len=0): + return Loop + + +def new_event_loop(): + global _task_queue, _io_queue + # TaskQueue of Task instances + _task_queue = TaskQueue() + # Task queue and poller for stream IO + _io_queue = IOQueue() + return Loop + + +# Initialise default event loop +new_event_loop() diff --git a/extmod/uasyncio/event.py b/extmod/uasyncio/event.py new file mode 100644 index 0000000000..31cb00e055 --- /dev/null +++ b/extmod/uasyncio/event.py @@ -0,0 +1,31 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019-2020 Damien P. George + +from . import core + +# Event class for primitive events that can be waited on, set, and cleared +class Event: + def __init__(self): + self.state = False # False=unset; True=set + self.waiting = core.TaskQueue() # Queue of Tasks waiting on completion of this event + + def is_set(self): + return self.state + + def set(self): + # Event becomes set, schedule any tasks waiting on it + while self.waiting.peek(): + core._task_queue.push_head(self.waiting.pop_head()) + self.state = True + + def clear(self): + self.state = False + + async def wait(self): + if not self.state: + # Event not set, put the calling task on the event's waiting queue + self.waiting.push_head(core.cur_task) + # Set calling task's data to the event's queue so it can be removed if needed + core.cur_task.data = self.waiting + yield + return True diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py new file mode 100644 index 0000000000..6e1305c94f --- /dev/null +++ b/extmod/uasyncio/funcs.py @@ -0,0 +1,54 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019-2020 Damien P. George + +from . import core + + +async def wait_for(aw, timeout, sleep=core.sleep): + aw = core._promote_to_task(aw) + if timeout is None: + return await aw + + def cancel(aw, timeout, sleep): + await sleep(timeout) + aw.cancel() + + cancel_task = core.create_task(cancel(aw, timeout, sleep)) + try: + ret = await aw + except core.CancelledError: + # Ignore CancelledError from aw, it's probably due to timeout + pass + finally: + # Cancel the "cancel" task if it's still active (optimisation instead of cancel_task.cancel()) + if cancel_task.coro is not None: + core._task_queue.remove(cancel_task) + if cancel_task.coro is None: + # Cancel task ran to completion, ie there was a timeout + raise core.TimeoutError + return ret + + +def wait_for_ms(aw, timeout): + return wait_for(aw, timeout, core.sleep_ms) + + +async def gather(*aws, return_exceptions=False): + ts = [core._promote_to_task(aw) for aw in aws] + for i in range(len(ts)): + try: + # TODO handle cancel of gather itself + # if ts[i].coro: + # iter(ts[i]).waiting.push_head(cur_task) + # try: + # yield + # except CancelledError as er: + # # cancel all waiting tasks + # raise er + ts[i] = await ts[i] + except Exception as er: + if return_exceptions: + ts[i] = er + else: + raise er + return ts diff --git a/extmod/uasyncio/lock.py b/extmod/uasyncio/lock.py new file mode 100644 index 0000000000..bddca295b6 --- /dev/null +++ b/extmod/uasyncio/lock.py @@ -0,0 +1,53 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019-2020 Damien P. George + +from . import core + +# Lock class for primitive mutex capability +class Lock: + def __init__(self): + # The state can take the following values: + # - 0: unlocked + # - 1: locked + # - : unlocked but this task has been scheduled to acquire the lock next + self.state = 0 + # Queue of Tasks waiting to acquire this Lock + self.waiting = core.TaskQueue() + + def locked(self): + return self.state == 1 + + def release(self): + if self.state != 1: + raise RuntimeError("Lock not acquired") + if self.waiting.peek(): + # Task(s) waiting on lock, schedule next Task + self.state = self.waiting.pop_head() + core._task_queue.push_head(self.state) + else: + # No Task waiting so unlock + self.state = 0 + + async def acquire(self): + if self.state != 0: + # Lock unavailable, put the calling Task on the waiting queue + self.waiting.push_head(core.cur_task) + # Set calling task's data to the lock's queue so it can be removed if needed + core.cur_task.data = self.waiting + try: + yield + except core.CancelledError as er: + if self.state == core.cur_task: + # Cancelled while pending on resume, schedule next waiting Task + self.state = 1 + self.release() + raise er + # Lock available, set it as locked + self.state = 1 + return True + + async def __aenter__(self): + return await self.acquire() + + async def __aexit__(self, exc_type, exc, tb): + return self.release() diff --git a/extmod/uasyncio/manifest.py b/extmod/uasyncio/manifest.py new file mode 100644 index 0000000000..f5fa27bfca --- /dev/null +++ b/extmod/uasyncio/manifest.py @@ -0,0 +1,13 @@ +# This list of frozen files doesn't include task.py because that's provided by the C module. +freeze( + "..", + ( + "uasyncio/__init__.py", + "uasyncio/core.py", + "uasyncio/event.py", + "uasyncio/funcs.py", + "uasyncio/lock.py", + "uasyncio/stream.py", + ), + opt=3, +) diff --git a/extmod/uasyncio/stream.py b/extmod/uasyncio/stream.py new file mode 100644 index 0000000000..b6d787e4f0 --- /dev/null +++ b/extmod/uasyncio/stream.py @@ -0,0 +1,158 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019-2020 Damien P. George + +from . import core + + +class Stream: + def __init__(self, s, e={}): + self.s = s + self.e = e + self.out_buf = b"" + + def get_extra_info(self, v): + return self.e[v] + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.close() + + def close(self): + pass + + async def wait_closed(self): + # TODO yield? + self.s.close() + + async def read(self, n): + yield core._io_queue.queue_read(self.s) + return self.s.read(n) + + async def readexactly(self, n): + r = b"" + while n: + yield core._io_queue.queue_read(self.s) + r2 = self.s.read(n) + if r2 is not None: + if not len(r2): + raise EOFError + r += r2 + n -= len(r2) + return r + + async def readline(self): + l = b"" + while True: + yield core._io_queue.queue_read(self.s) + l2 = self.s.readline() # may do multiple reads but won't block + l += l2 + if not l2 or l[-1] == 10: # \n (check l in case l2 is str) + return l + + def write(self, buf): + self.out_buf += buf + + async def drain(self): + mv = memoryview(self.out_buf) + off = 0 + while off < len(mv): + yield core._io_queue.queue_write(self.s) + ret = self.s.write(mv[off:]) + if ret is not None: + off += ret + self.out_buf = b"" + + +# Stream can be used for both reading and writing to save code size +StreamReader = Stream +StreamWriter = Stream + + +# Create a TCP stream connection to a remote host +async def open_connection(host, port): + from uerrno import EINPROGRESS + import usocket as socket + + ai = socket.getaddrinfo(host, port)[0] # TODO this is blocking! + s = socket.socket() + s.setblocking(False) + ss = Stream(s) + try: + s.connect(ai[-1]) + except OSError as er: + if er.args[0] != EINPROGRESS: + raise er + yield core._io_queue.queue_write(s) + return ss, ss + + +# Class representing a TCP stream server, can be closed and used in "async with" +class Server: + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + self.close() + await self.wait_closed() + + def close(self): + self.task.cancel() + + async def wait_closed(self): + await self.task + + async def _serve(self, cb, host, port, backlog): + import usocket as socket + + ai = socket.getaddrinfo(host, port)[0] # TODO this is blocking! + s = socket.socket() + s.setblocking(False) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(ai[-1]) + s.listen(backlog) + self.task = core.cur_task + # Accept incoming connections + while True: + try: + yield core._io_queue.queue_read(s) + except core.CancelledError: + # Shutdown server + s.close() + return + try: + s2, addr = s.accept() + except: + # Ignore a failed accept + continue + s2.setblocking(False) + s2s = Stream(s2, {"peername": addr}) + core.create_task(cb(s2s, s2s)) + + +# Helper function to start a TCP stream server, running as a new task +# TODO could use an accept-callback on socket read activity instead of creating a task +async def start_server(cb, host, port, backlog=5): + s = Server() + core.create_task(s._serve(cb, host, port, backlog)) + return s + + +################################################################################ +# Legacy uasyncio compatibility + + +async def stream_awrite(self, buf, off=0, sz=-1): + if off != 0 or sz != -1: + buf = memoryview(buf) + if sz == -1: + sz = len(buf) + buf = buf[off : off + sz] + self.write(buf) + await self.drain() + + +Stream.aclose = Stream.wait_closed +Stream.awrite = stream_awrite +Stream.awritestr = stream_awrite # TODO explicitly convert to bytes? diff --git a/extmod/uasyncio/task.py b/extmod/uasyncio/task.py new file mode 100644 index 0000000000..1788cf0ed0 --- /dev/null +++ b/extmod/uasyncio/task.py @@ -0,0 +1,168 @@ +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019-2020 Damien P. George + +# This file contains the core TaskQueue based on a pairing heap, and the core Task class. +# They can optionally be replaced by C implementations. + +from . import core + + +# pairing-heap meld of 2 heaps; O(1) +def ph_meld(h1, h2): + if h1 is None: + return h2 + if h2 is None: + return h1 + lt = core.ticks_diff(h1.ph_key, h2.ph_key) < 0 + if lt: + if h1.ph_child is None: + h1.ph_child = h2 + else: + h1.ph_child_last.ph_next = h2 + h1.ph_child_last = h2 + h2.ph_next = None + h2.ph_rightmost_parent = h1 + return h1 + else: + h1.ph_next = h2.ph_child + h2.ph_child = h1 + if h1.ph_next is None: + h2.ph_child_last = h1 + h1.ph_rightmost_parent = h2 + return h2 + + +# pairing-heap pairing operation; amortised O(log N) +def ph_pairing(child): + heap = None + while child is not None: + n1 = child + child = child.ph_next + n1.ph_next = None + if child is not None: + n2 = child + child = child.ph_next + n2.ph_next = None + n1 = ph_meld(n1, n2) + heap = ph_meld(heap, n1) + return heap + + +# pairing-heap delete of a node; stable, amortised O(log N) +def ph_delete(heap, node): + if node is heap: + child = heap.ph_child + node.ph_child = None + return ph_pairing(child) + # Find parent of node + parent = node + while parent.ph_next is not None: + parent = parent.ph_next + parent = parent.ph_rightmost_parent + # Replace node with pairing of its children + if node is parent.ph_child and node.ph_child is None: + parent.ph_child = node.ph_next + node.ph_next = None + return heap + elif node is parent.ph_child: + child = node.ph_child + next = node.ph_next + node.ph_child = None + node.ph_next = None + node = ph_pairing(child) + parent.ph_child = node + else: + n = parent.ph_child + while node is not n.ph_next: + n = n.ph_next + child = node.ph_child + next = node.ph_next + node.ph_child = None + node.ph_next = None + node = ph_pairing(child) + if node is None: + node = n + else: + n.ph_next = node + node.ph_next = next + if next is None: + node.ph_rightmost_parent = parent + parent.ph_child_last = node + return heap + + +# TaskQueue class based on the above pairing-heap functions. +class TaskQueue: + def __init__(self): + self.heap = None + + def peek(self): + return self.heap + + def push_sorted(self, v, key): + v.data = None + v.ph_key = key + v.ph_child = None + v.ph_next = None + self.heap = ph_meld(v, self.heap) + + def push_head(self, v): + self.push_sorted(v, core.ticks()) + + def pop_head(self): + v = self.heap + self.heap = ph_pairing(self.heap.ph_child) + return v + + def remove(self, v): + self.heap = ph_delete(self.heap, v) + + +# Task class representing a coroutine, can be waited on and cancelled. +class Task: + def __init__(self, coro, globals=None): + self.coro = coro # Coroutine of this Task + self.data = None # General data for queue it is waiting on + self.ph_key = 0 # Pairing heap + self.ph_child = None # Paring heap + self.ph_child_last = None # Paring heap + self.ph_next = None # Paring heap + self.ph_rightmost_parent = None # Paring heap + + def __iter__(self): + if not hasattr(self, "waiting"): + # Lazily allocated head of linked list of Tasks waiting on completion of this task. + self.waiting = TaskQueue() + return self + + def __next__(self): + if not self.coro: + # Task finished, raise return value to caller so it can continue. + raise self.data + else: + # Put calling task on waiting queue. + self.waiting.push_head(core.cur_task) + # Set calling task's data to this task that it waits on, to double-link it. + core.cur_task.data = self + + def cancel(self): + # Check if task is already finished. + if self.coro is None: + return False + # Can't cancel self (not supported yet). + if self is core.cur_task: + raise RuntimeError("can't cancel self") + # If Task waits on another task then forward the cancel to the one it's waiting on. + while isinstance(self.data, Task): + self = self.data + # Reschedule Task as a cancelled task. + if hasattr(self.data, "remove"): + # Not on the main running queue, remove the task from the queue it's on. + self.data.remove(self) + core._task_queue.push_head(self) + elif core.ticks_diff(self.ph_key, core.ticks()) > 0: + # On the main running queue but scheduled in the future, so bring it forward to now. + core._task_queue.remove(self) + core._task_queue.push_head(self) + self.data = core.CancelledError + return True diff --git a/extmod/vfs.c b/extmod/vfs.c index 5827e4573a..b02cefdb69 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -181,8 +181,8 @@ STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { 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_PTR(&mp_const_false_obj)} }, - { MP_QSTR_mkfs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_false_obj)} }, + { 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 @@ -277,10 +277,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_umount_obj, mp_vfs_umount); 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_PTR(&mp_const_none_obj)} }, + { 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_PTR(&mp_const_none_obj)} }, + { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, }; // parse args @@ -302,7 +302,6 @@ 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); - MP_STATE_VM(vfs_cur) = vfs; 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 @@ -314,9 +313,11 @@ mp_obj_t mp_vfs_chdir(mp_obj_t path_in) { 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); @@ -493,4 +494,25 @@ mp_obj_t mp_vfs_statvfs(mp_obj_t path_in) { } 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 diff --git a/extmod/vfs.h b/extmod/vfs.h index 7692701cdf..abb563e9a9 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -101,6 +101,7 @@ mp_obj_t mp_vfs_rmdir(mp_obj_t path_in); mp_obj_t mp_vfs_stat(mp_obj_t path_in); mp_obj_t mp_vfs_statvfs(mp_obj_t path_in); +int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point); mp_obj_t mp_vfs_ilistdir_it_iternext(mp_obj_t self_in); MP_DECLARE_CONST_FUN_OBJ_KW(mp_vfs_mount_obj); diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index 5ff316a4a9..5d4ac0cb46 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -12,6 +12,7 @@ #endif #include +#include "py/obj.h" #include "py/objproperty.h" #include "py/runtime.h" #include "py/mperrno.h" @@ -340,9 +341,9 @@ STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(fno.fsize); // st_size - t->items[7] = seconds; // st_atime - t->items[8] = seconds; // st_mtime - t->items[9] = seconds; // st_ctime + t->items[7] = seconds; // st_atime + t->items[8] = seconds; // st_mtime + t->items[9] = seconds; // st_ctime return MP_OBJ_FROM_PTR(t); } @@ -440,7 +441,7 @@ STATIC const mp_obj_property_t fat_vfs_label_obj = { .base.type = &mp_type_property, .proxy = {(mp_obj_t)&fat_vfs_getlabel_obj, (mp_obj_t)&fat_vfs_setlabel_obj, - (mp_obj_t)&mp_const_none_obj}, + MP_ROM_NONE}, }; #endif diff --git a/extmod/vfs_fat_file.c b/extmod/vfs_fat_file.c index 798b9ab18c..910ec9efdb 100644 --- a/extmod/vfs_fat_file.c +++ b/extmod/vfs_fat_file.c @@ -130,9 +130,9 @@ STATIC mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, // Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO, // but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor STATIC const mp_arg_t file_open_args[] = { - { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, + { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} }, - { MP_QSTR_encoding, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, + { MP_QSTR_encoding, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, }; #define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index 90a1996f9c..a53f66f2d6 100644 --- a/extmod/vfs_lfs.c +++ b/extmod/vfs_lfs.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,18 +25,20 @@ */ #include "py/runtime.h" +#include "py/mphal.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 }; +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 }, { 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 @@ -98,9 +100,13 @@ mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode #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; @@ -109,14 +115,25 @@ typedef struct _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]) { + uint64_t ns = 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" diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index a76514f4c6..d4e4e9d40e 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,8 +31,10 @@ #include "py/stream.h" #include "py/binary.h" #include "py/objarray.h" +#include "py/objstr.h" #include "py/mperrno.h" #include "extmod/vfs.h" +#include "lib/timeutils/timeutils.h" STATIC int MP_VFS_LFSx(dev_ioctl)(const struct LFSx_API (config) * c, int cmd, int arg, bool must_return_int) { mp_obj_t ret = mp_vfs_blockdev_ioctl(c->context, cmd, arg); @@ -119,6 +121,9 @@ STATIC mp_obj_t MP_VFS_LFSx(make_new)(const mp_obj_type_t * type, size_t n_args, self->base.type = type; vstr_init(&self->cur_dir, 16); vstr_add_byte(&self->cur_dir, '/'); + #if LFS_BUILD_VERSION == 2 + self->enable_mtime = args[LFS_MAKE_ARG_mtime].u_bool; + #endif MP_VFS_LFSx(init_config)(self, args[LFS_MAKE_ARG_bdev].u_obj, args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int); int ret = LFSx_API(mount)(&self->lfs, &self->config); @@ -236,10 +241,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(rmdir_obj), MP_VFS_LFSx(rmdir)); STATIC mp_obj_t MP_VFS_LFSx(rename)(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); const char *path_old = MP_VFS_LFSx(make_path)(self, path_old_in); + const char *path = mp_obj_str_get_str(path_new_in); vstr_t path_new; vstr_init(&path_new, vstr_len(&self->cur_dir)); - vstr_add_strn(&path_new, vstr_str(&self->cur_dir), vstr_len(&self->cur_dir)); - vstr_add_str(&path_new, mp_obj_str_get_str(path_new_in)); + if (path[0] != '/') { + vstr_add_strn(&path_new, vstr_str(&self->cur_dir), vstr_len(&self->cur_dir)); + } + vstr_add_str(&path_new, path); int ret = LFSx_API(rename)(&self->lfs, path_old, vstr_null_terminated_str(&path_new)); vstr_clear(&path_new); if (ret < 0) { @@ -283,8 +291,45 @@ STATIC mp_obj_t MP_VFS_LFSx(chdir)(mp_obj_t self_in, mp_obj_t path_in) { } // If not at root add trailing / to make it easy to build paths + // and then normalise the path if (vstr_len(&self->cur_dir) != 1) { vstr_add_byte(&self->cur_dir, '/'); + + #define CWD_LEN (vstr_len(&self->cur_dir)) + size_t to = 1; + size_t from = 1; + char *cwd = vstr_str(&self->cur_dir); + while (from < CWD_LEN) { + for (; cwd[from] == '/' && from < CWD_LEN; ++from) { + // Scan for the start + } + if (from > to) { + // Found excessive slash chars, squeeze them out + vstr_cut_out_bytes(&self->cur_dir, to, from - to); + from = to; + } + for (; cwd[from] != '/' && from < CWD_LEN; ++from) { + // Scan for the next / + } + if ((from - to) == 1 && cwd[to] == '.') { + // './', ignore + vstr_cut_out_bytes(&self->cur_dir, to, ++from - to); + from = to; + } else if ((from - to) == 2 && cwd[to] == '.' && cwd[to + 1] == '.') { + // '../', skip back + if (to > 1) { + // Only skip back if not at the tip + for (--to; to > 1 && cwd[to - 1] != '/'; --to) { + // Skip back + } + } + vstr_cut_out_bytes(&self->cur_dir, to, ++from - to); + from = to; + } else { + // Normal element, keep it and just move the offset + to = ++from; + } + } } return mp_const_none; @@ -304,13 +349,29 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(MP_VFS_LFSx(getcwd_obj), MP_VFS_LFSx(getcwd)); STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); - const char *path = mp_obj_str_get_str(path_in); + const char *path = MP_VFS_LFSx(make_path)(self, path_in); struct LFSx_API (info) info; int ret = LFSx_API(stat)(&self->lfs, path, &info); if (ret < 0) { mp_raise_OSError(-ret); } + mp_uint_t mtime = 0; + #if LFS_BUILD_VERSION == 2 + uint8_t mtime_buf[8]; + lfs2_ssize_t sz = lfs2_getattr(&self->lfs, path, LFS_ATTR_MTIME, &mtime_buf, sizeof(mtime_buf)); + if (sz == sizeof(mtime_buf)) { + uint64_t ns = 0; + for (size_t i = sizeof(mtime_buf); i > 0; --i) { + ns = ns << 8 | mtime_buf[i - 1]; + } + mtime = timeutils_seconds_since_2000_from_nanoseconds_since_1970(ns); + #if MICROPY_EPOCH_IS_1970 + mtime += TIMEUTILS_SECONDS_1970_TO_2000; + #endif + } + #endif + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR); // st_mode t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino @@ -319,9 +380,9 @@ STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size - t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime - t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime - t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime + t->items[7] = mp_obj_new_int_from_uint(mtime); // st_atime + t->items[8] = mp_obj_new_int_from_uint(mtime); // st_mtime + t->items[9] = mp_obj_new_int_from_uint(mtime); // st_ctime return MP_OBJ_FROM_PTR(t); } @@ -400,6 +461,8 @@ STATIC MP_DEFINE_CONST_DICT(MP_VFS_LFSx(locals_dict), MP_VFS_LFSx(locals_dict_ta STATIC mp_import_stat_t MP_VFS_LFSx(import_stat)(void *self_in, const char *path) { MP_OBJ_VFS_LFSx *self = self_in; struct LFSx_API (info) info; + mp_obj_str_t path_obj = { { &mp_type_str }, 0, 0, (const byte *)path }; + path = MP_VFS_LFSx(make_path)(self, MP_OBJ_FROM_PTR(&path_obj)); int ret = LFSx_API(stat)(&self->lfs, path, &info); if (ret == 0) { if (info.type == LFSx_MACRO(_TYPE_REG)) { diff --git a/extmod/vfs_lfsx_file.c b/extmod/vfs_lfsx_file.c index 7935827e7c..80536717c0 100644 --- a/extmod/vfs_lfsx_file.c +++ b/extmod/vfs_lfsx_file.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -101,6 +101,17 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod #endif o->cfg.buffer = &o->file_buffer[0]; + #if LFS_BUILD_VERSION == 2 + if (self->enable_mtime) { + lfs_get_mtime(&o->mtime[0]); + o->attrs[0].type = LFS_ATTR_MTIME; + o->attrs[0].buffer = &o->mtime[0]; + o->attrs[0].size = sizeof(o->mtime); + o->cfg.attrs = &o->attrs[0]; + o->cfg.attr_count = MP_ARRAY_SIZE(o->attrs); + } + #endif + const char *path = MP_VFS_LFSx(make_path)(self, path_in); int ret = LFSx_API(file_opencfg)(&self->lfs, &o->file, path, flags, &o->cfg); if (ret < 0) { @@ -131,6 +142,11 @@ STATIC mp_uint_t MP_VFS_LFSx(file_read)(mp_obj_t self_in, void *buf, mp_uint_t s STATIC mp_uint_t MP_VFS_LFSx(file_write)(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in); MP_VFS_LFSx(check_open)(self); + #if LFS_BUILD_VERSION == 2 + if (self->vfs->enable_mtime) { + lfs_get_mtime(&self->mtime[0]); + } + #endif LFSx_API(ssize_t) sz = LFSx_API(file_write)(&self->vfs->lfs, &self->file, buf, size); if (sz < 0) { *errcode = -sz; diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index 73f4a1d874..fd4b5f42b2 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -5,6 +5,8 @@ #include "py/runtime.h" #include "py/mperrno.h" +#include "py/mphal.h" +#include "py/mpthread.h" #include "extmod/vfs.h" #include "extmod/vfs_posix.h" @@ -149,12 +151,15 @@ STATIC mp_obj_t vfs_posix_ilistdir_it_iternext(mp_obj_t self_in) { } for (;;) { + MP_THREAD_GIL_EXIT(); struct dirent *dirent = readdir(self->dir); if (dirent == NULL) { closedir(self->dir); + MP_THREAD_GIL_ENTER(); self->dir = NULL; return MP_OBJ_STOP_ITERATION; } + MP_THREAD_GIL_ENTER(); const char *fn = dirent->d_name; if (fn[0] == '.' && (fn[1] == 0 || fn[1] == '.')) { @@ -208,7 +213,9 @@ STATIC mp_obj_t vfs_posix_ilistdir(mp_obj_t self_in, mp_obj_t path_in) { if (path[0] == '\0') { path = "."; } + MP_THREAD_GIL_EXIT(); iter->dir = opendir(path); + MP_THREAD_GIL_ENTER(); if (iter->dir == NULL) { mp_raise_OSError(errno); } @@ -224,7 +231,10 @@ typedef struct _mp_obj_listdir_t { STATIC mp_obj_t vfs_posix_mkdir(mp_obj_t self_in, mp_obj_t path_in) { mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); - int ret = mkdir(vfs_posix_get_path_str(self, path_in), 0777); + const char *path = vfs_posix_get_path_str(self, path_in); + MP_THREAD_GIL_EXIT(); + int ret = mkdir(path, 0777); + MP_THREAD_GIL_ENTER(); if (ret != 0) { mp_raise_OSError(errno); } @@ -241,7 +251,9 @@ STATIC mp_obj_t vfs_posix_rename(mp_obj_t self_in, mp_obj_t old_path_in, mp_obj_ mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); const char *old_path = vfs_posix_get_path_str(self, old_path_in); const char *new_path = vfs_posix_get_path_str(self, new_path_in); + MP_THREAD_GIL_EXIT(); int ret = rename(old_path, new_path); + MP_THREAD_GIL_ENTER(); if (ret != 0) { mp_raise_OSError(errno); } @@ -257,21 +269,20 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_rmdir_obj, vfs_posix_rmdir); STATIC mp_obj_t vfs_posix_stat(mp_obj_t self_in, mp_obj_t path_in) { mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); struct stat sb; - int ret = stat(vfs_posix_get_path_str(self, path_in), &sb); - if (ret != 0) { - mp_raise_OSError(errno); - } + const char *path = vfs_posix_get_path_str(self, path_in); + int ret; + MP_HAL_RETRY_SYSCALL(ret, stat(path, &sb), mp_raise_OSError(err)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); - t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.st_ino); - t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.st_dev); - t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.st_nlink); - t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.st_uid); - t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.st_gid); - t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.st_size); - t->items[7] = MP_OBJ_NEW_SMALL_INT(sb.st_atime); - t->items[8] = MP_OBJ_NEW_SMALL_INT(sb.st_mtime); - t->items[9] = MP_OBJ_NEW_SMALL_INT(sb.st_ctime); + t->items[1] = mp_obj_new_int_from_uint(sb.st_ino); + t->items[2] = mp_obj_new_int_from_uint(sb.st_dev); + t->items[3] = mp_obj_new_int_from_uint(sb.st_nlink); + t->items[4] = mp_obj_new_int_from_uint(sb.st_uid); + t->items[5] = mp_obj_new_int_from_uint(sb.st_gid); + t->items[6] = mp_obj_new_int_from_uint(sb.st_size); + t->items[7] = mp_obj_new_int_from_uint(sb.st_atime); + t->items[8] = mp_obj_new_int_from_uint(sb.st_mtime); + t->items[9] = mp_obj_new_int_from_uint(sb.st_ctime); return MP_OBJ_FROM_PTR(t); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_stat_obj, vfs_posix_stat); @@ -300,10 +311,8 @@ STATIC mp_obj_t vfs_posix_statvfs(mp_obj_t self_in, mp_obj_t path_in) { mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); STRUCT_STATVFS sb; const char *path = vfs_posix_get_path_str(self, path_in); - int ret = STATVFS(path, &sb); - if (ret != 0) { - mp_raise_OSError(errno); - } + int ret; + MP_HAL_RETRY_SYSCALL(ret, STATVFS(path, &sb), mp_raise_OSError(err)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.f_bsize); t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.f_frsize); diff --git a/extmod/vfs_posix_file.c b/extmod/vfs_posix_file.c index 0f6195bd4f..3a6f763102 100644 --- a/extmod/vfs_posix_file.c +++ b/extmod/vfs_posix_file.c @@ -3,14 +3,17 @@ // // SPDX-License-Identifier: MIT +#include "py/mphal.h" +#include "py/mpthread.h" #include "py/runtime.h" #include "py/stream.h" #include "extmod/vfs_posix.h" #include "supervisor/shared/translate.h" -#if defined(MICROPY_VFS_POSIX) && MICROPY_VFS_POSIX +#if defined(MICROPY_VFS_POSIX) && MICROPY_VFS_POSIX || MICROPY_VFS_POSIX_FILE #include +#include #ifdef _WIN32 #define fsync _commit @@ -24,7 +27,7 @@ typedef struct _mp_obj_vfs_posix_file_t { #ifdef MICROPY_CPYTHON_COMPAT STATIC void check_fd_is_open(const mp_obj_vfs_posix_file_t *o) { if (o->fd < 0) { - mp_raise_ValueError(translate("I/O operation on closed file")); + mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file")); } } #else @@ -80,17 +83,15 @@ mp_obj_t mp_vfs_posix_file_open(const mp_obj_type_t *type, mp_obj_t file_in, mp_ } const char *fname = mp_obj_str_get_str(fid); - int fd = open(fname, mode_x | mode_rw, 0644); - if (fd == -1) { - mp_raise_OSError(errno); - } + int fd; + MP_HAL_RETRY_SYSCALL(fd, open(fname, mode_x | mode_rw, 0644), mp_raise_OSError(err)); o->fd = fd; return MP_OBJ_FROM_PTR(o); } STATIC mp_obj_t vfs_posix_file_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { - { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, + { 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)} }, }; @@ -115,46 +116,49 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(vfs_posix_file___exit___obj, 4, 4, vf STATIC mp_uint_t vfs_posix_file_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { mp_obj_vfs_posix_file_t *o = MP_OBJ_TO_PTR(o_in); check_fd_is_open(o); - mp_int_t r = read(o->fd, buf, size); - if (r == -1) { - *errcode = errno; + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, read(o->fd, buf, size), { + *errcode = err; return MP_STREAM_ERROR; - } - return r; + }); + return (mp_uint_t)r; } STATIC mp_uint_t vfs_posix_file_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { mp_obj_vfs_posix_file_t *o = MP_OBJ_TO_PTR(o_in); check_fd_is_open(o); - mp_int_t r = write(o->fd, buf, size); - while (r == -1 && errno == EINTR) { - if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); - } - r = write(o->fd, buf, size); - } - if (r == -1) { - *errcode = errno; + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, write(o->fd, buf, size), { + *errcode = err; return MP_STREAM_ERROR; - } - return r; + }); + return (mp_uint_t)r; } STATIC mp_uint_t vfs_posix_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { mp_obj_vfs_posix_file_t *o = MP_OBJ_TO_PTR(o_in); check_fd_is_open(o); switch (request) { - case MP_STREAM_FLUSH: - if (fsync(o->fd) < 0) { - *errcode = errno; + case MP_STREAM_FLUSH: { + int ret; + MP_HAL_RETRY_SYSCALL(ret, fsync(o->fd), { + if (err == EINVAL + && (o->fd == STDIN_FILENO || o->fd == STDOUT_FILENO || o->fd == STDERR_FILENO)) { + // fsync(stdin/stdout/stderr) may fail with EINVAL, but don't propagate that + // error out. Because data is not buffered by us, and stdin/out/err.flush() + // should just be a no-op. + return 0; + } + *errcode = err; return MP_STREAM_ERROR; - } + }); return 0; + } case MP_STREAM_SEEK: { struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; + MP_THREAD_GIL_EXIT(); off_t off = lseek(o->fd, s->offset, s->whence); + MP_THREAD_GIL_ENTER(); if (off == (off_t)-1) { *errcode = errno; return MP_STREAM_ERROR; @@ -163,11 +167,15 @@ STATIC mp_uint_t vfs_posix_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_ return 0; } case MP_STREAM_CLOSE: + MP_THREAD_GIL_EXIT(); close(o->fd); + MP_THREAD_GIL_ENTER(); #ifdef MICROPY_CPYTHON_COMPAT o->fd = -1; #endif return 0; + case MP_STREAM_GET_FILENO: + return o->fd; default: *errcode = EINVAL; return MP_STREAM_ERROR; @@ -234,4 +242,4 @@ const mp_obj_vfs_posix_file_t mp_sys_stdin_obj = {{&mp_type_textio}, STDIN_FILEN const mp_obj_vfs_posix_file_t mp_sys_stdout_obj = {{&mp_type_textio}, STDOUT_FILENO}; const mp_obj_vfs_posix_file_t mp_sys_stderr_obj = {{&mp_type_textio}, STDERR_FILENO}; -#endif // MICROPY_VFS_POSIX +#endif // MICROPY_VFS_POSIX || MICROPY_VFS_POSIX_FILE diff --git a/extmod/vfs_reader.c b/extmod/vfs_reader.c index d9abc3c957..c87fcd76fd 100644 --- a/extmod/vfs_reader.c +++ b/extmod/vfs_reader.c @@ -50,8 +50,11 @@ STATIC void mp_reader_vfs_close(void *data) { 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 arg = mp_obj_new_str(filename, strlen(filename)); - rf->file = mp_vfs_open(1, &arg, (mp_map_t *)&mp_const_empty_map); + 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) { diff --git a/lib/embed/__errno.c b/lib/embed/__errno.c new file mode 100644 index 0000000000..86417a02dd --- /dev/null +++ b/lib/embed/__errno.c @@ -0,0 +1,13 @@ +// This file provides a version of __errno() for embedded systems that do not have one. +// This function is needed for expressions of the form: &errno + +static int embed_errno; + +#if defined(__linux__) +int *__errno_location(void) +#else +int *__errno(void) +#endif +{ + return &embed_errno; +} diff --git a/lib/embed/abort_.c b/lib/embed/abort_.c index 3eeb42d9e4..45a2e0dedd 100644 --- a/lib/embed/abort_.c +++ b/lib/embed/abort_.c @@ -5,5 +5,5 @@ NORETURN void abort_(void); NORETURN void abort_(void) { - mp_raise_msg(&mp_type_RuntimeError, translate("abort() called")); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called")); } diff --git a/lib/libc/string0.c b/lib/libc/string0.c index ba8e3c4e88..3ebb580989 100644 --- a/lib/libc/string0.c +++ b/lib/libc/string0.c @@ -173,6 +173,25 @@ char *strcpy(char *dest, const char *src) { return dest; } +// Public Domain implementation of strncpy from: +// http://en.wikibooks.org/wiki/C_Programming/Strings#The_strncpy_function +char *strncpy(char *s1, const char *s2, size_t n) { + char *dst = s1; + const char *src = s2; + /* Copy bytes, one at a time. */ + while (n > 0) { + n--; + if ((*dst++ = *src++) == '\0') { + /* If we get here, we found a null character at the end + of s2, so use memset to put null bytes at the end of + s1. */ + memset(dst, '\0', n); + break; + } + } + return s1; + } + // needed because gcc optimises strcpy + strcat to this char *stpcpy(char *dest, const char *src) { while (*src) { diff --git a/lib/libm/asinfacosf.c b/lib/libm/asinfacosf.c index 07ecad3f3f..22dbb3d228 100644 --- a/lib/libm/asinfacosf.c +++ b/lib/libm/asinfacosf.c @@ -23,15 +23,15 @@ // dpgeorge: pio2 was double in original implementation of asinf static const float -pio2_hi = 1.5707962513e+00, /* 0x3fc90fda */ -pio2_lo = 7.5497894159e-08; /* 0x33a22168 */ +pio2_hi = 1.5707962513e+00f, /* 0x3fc90fda */ +pio2_lo = 7.5497894159e-08f; /* 0x33a22168 */ static const float /* coefficients for R(x^2) */ -pS0 = 1.6666586697e-01, -pS1 = -4.2743422091e-02, -pS2 = -8.6563630030e-03, -qS1 = -7.0662963390e-01; +pS0 = 1.6666586697e-01f, +pS1 = -4.2743422091e-02f, +pS2 = -8.6563630030e-03f, +qS1 = -7.0662963390e-01f; static float R(float z) { diff --git a/lib/libm/atan2f.c b/lib/libm/atan2f.c index 03d000c9e1..dcbaf821b5 100644 --- a/lib/libm/atan2f.c +++ b/lib/libm/atan2f.c @@ -22,8 +22,8 @@ #include "libm.h" static const float -pi = 3.1415927410e+00, /* 0x40490fdb */ -pi_lo = -8.7422776573e-08; /* 0xb3bbbd2e */ +pi = 3.1415927410e+00f, /* 0x40490fdb */ +pi_lo = -8.7422776573e-08f; /* 0xb3bbbd2e */ float atan2f(float y, float x) { diff --git a/lib/libm/atanf.c b/lib/libm/atanf.c index 053fc1b6d6..40bc363c29 100644 --- a/lib/libm/atanf.c +++ b/lib/libm/atanf.c @@ -23,25 +23,25 @@ #include "libm.h" static const float atanhi[] = { - 4.6364760399e-01, /* atan(0.5)hi 0x3eed6338 */ - 7.8539812565e-01, /* atan(1.0)hi 0x3f490fda */ - 9.8279368877e-01, /* atan(1.5)hi 0x3f7b985e */ - 1.5707962513e+00, /* atan(inf)hi 0x3fc90fda */ + 4.6364760399e-01f, /* atan(0.5)hi 0x3eed6338 */ + 7.8539812565e-01f, /* atan(1.0)hi 0x3f490fda */ + 9.8279368877e-01f, /* atan(1.5)hi 0x3f7b985e */ + 1.5707962513e+00f, /* atan(inf)hi 0x3fc90fda */ }; static const float atanlo[] = { - 5.0121582440e-09, /* atan(0.5)lo 0x31ac3769 */ - 3.7748947079e-08, /* atan(1.0)lo 0x33222168 */ - 3.4473217170e-08, /* atan(1.5)lo 0x33140fb4 */ - 7.5497894159e-08, /* atan(inf)lo 0x33a22168 */ + 5.0121582440e-09f, /* atan(0.5)lo 0x31ac3769 */ + 3.7748947079e-08f, /* atan(1.0)lo 0x33222168 */ + 3.4473217170e-08f, /* atan(1.5)lo 0x33140fb4 */ + 7.5497894159e-08f, /* atan(inf)lo 0x33a22168 */ }; static const float aT[] = { - 3.3333328366e-01, - -1.9999158382e-01, - 1.4253635705e-01, - -1.0648017377e-01, - 6.1687607318e-02, + 3.3333328366e-01f, + -1.9999158382e-01f, + 1.4253635705e-01f, + -1.0648017377e-01f, + 6.1687607318e-02f, }; float atanf(float x) diff --git a/lib/libm/ef_rem_pio2.c b/lib/libm/ef_rem_pio2.c index 1a95475032..386c6fde45 100644 --- a/lib/libm/ef_rem_pio2.c +++ b/lib/libm/ef_rem_pio2.c @@ -93,16 +93,16 @@ static const float #else static float #endif -zero = 0.0000000000e+00, /* 0x00000000 */ -half = 5.0000000000e-01, /* 0x3f000000 */ -two8 = 2.5600000000e+02, /* 0x43800000 */ -invpio2 = 6.3661980629e-01, /* 0x3f22f984 */ -pio2_1 = 1.5707855225e+00, /* 0x3fc90f80 */ -pio2_1t = 1.0804334124e-05, /* 0x37354443 */ -pio2_2 = 1.0804273188e-05, /* 0x37354400 */ -pio2_2t = 6.0770999344e-11, /* 0x2e85a308 */ -pio2_3 = 6.0770943833e-11, /* 0x2e85a300 */ -pio2_3t = 6.1232342629e-17; /* 0x248d3132 */ +zero = 0.0000000000e+00f, /* 0x00000000 */ +half = 5.0000000000e-01f, /* 0x3f000000 */ +two8 = 2.5600000000e+02f, /* 0x43800000 */ +invpio2 = 6.3661980629e-01f, /* 0x3f22f984 */ +pio2_1 = 1.5707855225e+00f, /* 0x3fc90f80 */ +pio2_1t = 1.0804334124e-05f, /* 0x37354443 */ +pio2_2 = 1.0804273188e-05f, /* 0x37354400 */ +pio2_2t = 6.0770999344e-11f, /* 0x2e85a308 */ +pio2_3 = 6.0770943833e-11f, /* 0x2e85a300 */ +pio2_3t = 6.1232342629e-17f; /* 0x248d3132 */ #ifdef __STDC__ __int32_t __ieee754_rem_pio2f(float x, float *y) diff --git a/lib/libm/ef_sqrt.c b/lib/libm/ef_sqrt.c index 8615dfc172..2c1ae6fce0 100644 --- a/lib/libm/ef_sqrt.c +++ b/lib/libm/ef_sqrt.c @@ -25,9 +25,9 @@ #include "fdlibm.h" #ifdef __STDC__ -static const float one = 1.0, tiny=1.0e-30; +static const float one = 1.0f, tiny=1.0e-30f; #else -static float one = 1.0, tiny=1.0e-30; +static float one = 1.0f, tiny=1.0e-30f; #endif // sqrtf is exactly __ieee754_sqrtf when _IEEE_LIBM defined diff --git a/lib/libm/erf_lgamma.c b/lib/libm/erf_lgamma.c index 5116e3c1b1..6d675f13bd 100644 --- a/lib/libm/erf_lgamma.c +++ b/lib/libm/erf_lgamma.c @@ -33,77 +33,77 @@ static const float #else static float #endif -two23= 8.3886080000e+06, /* 0x4b000000 */ -half= 5.0000000000e-01, /* 0x3f000000 */ -one = 1.0000000000e+00, /* 0x3f800000 */ -pi = 3.1415927410e+00, /* 0x40490fdb */ -a0 = 7.7215664089e-02, /* 0x3d9e233f */ -a1 = 3.2246702909e-01, /* 0x3ea51a66 */ -a2 = 6.7352302372e-02, /* 0x3d89f001 */ -a3 = 2.0580807701e-02, /* 0x3ca89915 */ -a4 = 7.3855509982e-03, /* 0x3bf2027e */ -a5 = 2.8905137442e-03, /* 0x3b3d6ec6 */ -a6 = 1.1927076848e-03, /* 0x3a9c54a1 */ -a7 = 5.1006977446e-04, /* 0x3a05b634 */ -a8 = 2.2086278477e-04, /* 0x39679767 */ -a9 = 1.0801156895e-04, /* 0x38e28445 */ -a10 = 2.5214456400e-05, /* 0x37d383a2 */ -a11 = 4.4864096708e-05, /* 0x383c2c75 */ -tc = 1.4616321325e+00, /* 0x3fbb16c3 */ -tf = -1.2148628384e-01, /* 0xbdf8cdcd */ +two23= 8.3886080000e+06f, /* 0x4b000000 */ +half= 5.0000000000e-01f, /* 0x3f000000 */ +one = 1.0000000000e+00f, /* 0x3f800000 */ +pi = 3.1415927410e+00f, /* 0x40490fdb */ +a0 = 7.7215664089e-02f, /* 0x3d9e233f */ +a1 = 3.2246702909e-01f, /* 0x3ea51a66 */ +a2 = 6.7352302372e-02f, /* 0x3d89f001 */ +a3 = 2.0580807701e-02f, /* 0x3ca89915 */ +a4 = 7.3855509982e-03f, /* 0x3bf2027e */ +a5 = 2.8905137442e-03f, /* 0x3b3d6ec6 */ +a6 = 1.1927076848e-03f, /* 0x3a9c54a1 */ +a7 = 5.1006977446e-04f, /* 0x3a05b634 */ +a8 = 2.2086278477e-04f, /* 0x39679767 */ +a9 = 1.0801156895e-04f, /* 0x38e28445 */ +a10 = 2.5214456400e-05f, /* 0x37d383a2 */ +a11 = 4.4864096708e-05f, /* 0x383c2c75 */ +tc = 1.4616321325e+00f, /* 0x3fbb16c3 */ +tf = -1.2148628384e-01f, /* 0xbdf8cdcd */ /* tt = -(tail of tf) */ -tt = 6.6971006518e-09, /* 0x31e61c52 */ -t0 = 4.8383611441e-01, /* 0x3ef7b95e */ -t1 = -1.4758771658e-01, /* 0xbe17213c */ -t2 = 6.4624942839e-02, /* 0x3d845a15 */ -t3 = -3.2788541168e-02, /* 0xbd064d47 */ -t4 = 1.7970675603e-02, /* 0x3c93373d */ -t5 = -1.0314224288e-02, /* 0xbc28fcfe */ -t6 = 6.1005386524e-03, /* 0x3bc7e707 */ -t7 = -3.6845202558e-03, /* 0xbb7177fe */ -t8 = 2.2596477065e-03, /* 0x3b141699 */ -t9 = -1.4034647029e-03, /* 0xbab7f476 */ -t10 = 8.8108185446e-04, /* 0x3a66f867 */ -t11 = -5.3859531181e-04, /* 0xba0d3085 */ -t12 = 3.1563205994e-04, /* 0x39a57b6b */ -t13 = -3.1275415677e-04, /* 0xb9a3f927 */ -t14 = 3.3552918467e-04, /* 0x39afe9f7 */ -u0 = -7.7215664089e-02, /* 0xbd9e233f */ -u1 = 6.3282704353e-01, /* 0x3f2200f4 */ -u2 = 1.4549225569e+00, /* 0x3fba3ae7 */ -u3 = 9.7771751881e-01, /* 0x3f7a4bb2 */ -u4 = 2.2896373272e-01, /* 0x3e6a7578 */ -u5 = 1.3381091878e-02, /* 0x3c5b3c5e */ -v1 = 2.4559779167e+00, /* 0x401d2ebe */ -v2 = 2.1284897327e+00, /* 0x4008392d */ -v3 = 7.6928514242e-01, /* 0x3f44efdf */ -v4 = 1.0422264785e-01, /* 0x3dd572af */ -v5 = 3.2170924824e-03, /* 0x3b52d5db */ -s0 = -7.7215664089e-02, /* 0xbd9e233f */ -s1 = 2.1498242021e-01, /* 0x3e5c245a */ -s2 = 3.2577878237e-01, /* 0x3ea6cc7a */ -s3 = 1.4635047317e-01, /* 0x3e15dce6 */ -s4 = 2.6642270386e-02, /* 0x3cda40e4 */ -s5 = 1.8402845599e-03, /* 0x3af135b4 */ -s6 = 3.1947532989e-05, /* 0x3805ff67 */ -r1 = 1.3920053244e+00, /* 0x3fb22d3b */ -r2 = 7.2193557024e-01, /* 0x3f38d0c5 */ -r3 = 1.7193385959e-01, /* 0x3e300f6e */ -r4 = 1.8645919859e-02, /* 0x3c98bf54 */ -r5 = 7.7794247773e-04, /* 0x3a4beed6 */ -r6 = 7.3266842264e-06, /* 0x36f5d7bd */ -w0 = 4.1893854737e-01, /* 0x3ed67f1d */ -w1 = 8.3333335817e-02, /* 0x3daaaaab */ -w2 = -2.7777778450e-03, /* 0xbb360b61 */ -w3 = 7.9365057172e-04, /* 0x3a500cfd */ -w4 = -5.9518753551e-04, /* 0xba1c065c */ -w5 = 8.3633989561e-04, /* 0x3a5b3dd2 */ -w6 = -1.6309292987e-03; /* 0xbad5c4e8 */ +tt = 6.6971006518e-09f, /* 0x31e61c52 */ +t0 = 4.8383611441e-01f, /* 0x3ef7b95e */ +t1 = -1.4758771658e-01f, /* 0xbe17213c */ +t2 = 6.4624942839e-02f, /* 0x3d845a15 */ +t3 = -3.2788541168e-02f, /* 0xbd064d47 */ +t4 = 1.7970675603e-02f, /* 0x3c93373d */ +t5 = -1.0314224288e-02f, /* 0xbc28fcfe */ +t6 = 6.1005386524e-03f, /* 0x3bc7e707 */ +t7 = -3.6845202558e-03f, /* 0xbb7177fe */ +t8 = 2.2596477065e-03f, /* 0x3b141699 */ +t9 = -1.4034647029e-03f, /* 0xbab7f476 */ +t10 = 8.8108185446e-04f, /* 0x3a66f867 */ +t11 = -5.3859531181e-04f, /* 0xba0d3085 */ +t12 = 3.1563205994e-04f, /* 0x39a57b6b */ +t13 = -3.1275415677e-04f, /* 0xb9a3f927 */ +t14 = 3.3552918467e-04f, /* 0x39afe9f7 */ +u0 = -7.7215664089e-02f, /* 0xbd9e233f */ +u1 = 6.3282704353e-01f, /* 0x3f2200f4 */ +u2 = 1.4549225569e+00f, /* 0x3fba3ae7 */ +u3 = 9.7771751881e-01f, /* 0x3f7a4bb2 */ +u4 = 2.2896373272e-01f, /* 0x3e6a7578 */ +u5 = 1.3381091878e-02f, /* 0x3c5b3c5e */ +v1 = 2.4559779167e+00f, /* 0x401d2ebe */ +v2 = 2.1284897327e+00f, /* 0x4008392d */ +v3 = 7.6928514242e-01f, /* 0x3f44efdf */ +v4 = 1.0422264785e-01f, /* 0x3dd572af */ +v5 = 3.2170924824e-03f, /* 0x3b52d5db */ +s0 = -7.7215664089e-02f, /* 0xbd9e233f */ +s1 = 2.1498242021e-01f, /* 0x3e5c245a */ +s2 = 3.2577878237e-01f, /* 0x3ea6cc7a */ +s3 = 1.4635047317e-01f, /* 0x3e15dce6 */ +s4 = 2.6642270386e-02f, /* 0x3cda40e4 */ +s5 = 1.8402845599e-03f, /* 0x3af135b4 */ +s6 = 3.1947532989e-05f, /* 0x3805ff67 */ +r1 = 1.3920053244e+00f, /* 0x3fb22d3b */ +r2 = 7.2193557024e-01f, /* 0x3f38d0c5 */ +r3 = 1.7193385959e-01f, /* 0x3e300f6e */ +r4 = 1.8645919859e-02f, /* 0x3c98bf54 */ +r5 = 7.7794247773e-04f, /* 0x3a4beed6 */ +r6 = 7.3266842264e-06f, /* 0x36f5d7bd */ +w0 = 4.1893854737e-01f, /* 0x3ed67f1d */ +w1 = 8.3333335817e-02f, /* 0x3daaaaab */ +w2 = -2.7777778450e-03f, /* 0xbb360b61 */ +w3 = 7.9365057172e-04f, /* 0x3a500cfd */ +w4 = -5.9518753551e-04f, /* 0xba1c065c */ +w5 = 8.3633989561e-04f, /* 0x3a5b3dd2 */ +w6 = -1.6309292987e-03f; /* 0xbad5c4e8 */ #ifdef __STDC__ -static const float zero= 0.0000000000e+00; +static const float zero= 0.0000000000e+00f; #else -static float zero= 0.0000000000e+00; +static float zero= 0.0000000000e+00f; #endif #ifdef __STDC__ diff --git a/lib/libm/kf_cos.c b/lib/libm/kf_cos.c index 5181664901..73b4859672 100644 --- a/lib/libm/kf_cos.c +++ b/lib/libm/kf_cos.c @@ -29,13 +29,13 @@ static const float #else static float #endif -one = 1.0000000000e+00, /* 0x3f800000 */ -C1 = 4.1666667908e-02, /* 0x3d2aaaab */ -C2 = -1.3888889225e-03, /* 0xbab60b61 */ -C3 = 2.4801587642e-05, /* 0x37d00d01 */ -C4 = -2.7557314297e-07, /* 0xb493f27c */ -C5 = 2.0875723372e-09, /* 0x310f74f6 */ -C6 = -1.1359647598e-11; /* 0xad47d74e */ +one = 1.0000000000e+00f, /* 0x3f800000 */ +C1 = 4.1666667908e-02f, /* 0x3d2aaaab */ +C2 = -1.3888889225e-03f, /* 0xbab60b61 */ +C3 = 2.4801587642e-05f, /* 0x37d00d01 */ +C4 = -2.7557314297e-07f, /* 0xb493f27c */ +C5 = 2.0875723372e-09f, /* 0x310f74f6 */ +C6 = -1.1359647598e-11f; /* 0xad47d74e */ #ifdef __STDC__ float __kernel_cosf(float x, float y) diff --git a/lib/libm/kf_rem_pio2.c b/lib/libm/kf_rem_pio2.c index b27e47ea89..cce9eb1ba3 100644 --- a/lib/libm/kf_rem_pio2.c +++ b/lib/libm/kf_rem_pio2.c @@ -38,17 +38,17 @@ static const float PIo2[] = { #else static float PIo2[] = { #endif - 1.5703125000e+00, /* 0x3fc90000 */ - 4.5776367188e-04, /* 0x39f00000 */ - 2.5987625122e-05, /* 0x37da0000 */ - 7.5437128544e-08, /* 0x33a20000 */ - 6.0026650317e-11, /* 0x2e840000 */ - 7.3896444519e-13, /* 0x2b500000 */ - 5.3845816694e-15, /* 0x27c20000 */ - 5.6378512969e-18, /* 0x22d00000 */ - 8.3009228831e-20, /* 0x1fc40000 */ - 3.2756352257e-22, /* 0x1bc60000 */ - 6.3331015649e-25, /* 0x17440000 */ + 1.5703125000e+00f, /* 0x3fc90000 */ + 4.5776367188e-04f, /* 0x39f00000 */ + 2.5987625122e-05f, /* 0x37da0000 */ + 7.5437128544e-08f, /* 0x33a20000 */ + 6.0026650317e-11f, /* 0x2e840000 */ + 7.3896444519e-13f, /* 0x2b500000 */ + 5.3845816694e-15f, /* 0x27c20000 */ + 5.6378512969e-18f, /* 0x22d00000 */ + 8.3009228831e-20f, /* 0x1fc40000 */ + 3.2756352257e-22f, /* 0x1bc60000 */ + 6.3331015649e-25f, /* 0x17440000 */ }; #ifdef __STDC__ @@ -56,10 +56,10 @@ static const float #else static float #endif -zero = 0.0, -one = 1.0, -two8 = 2.5600000000e+02, /* 0x43800000 */ -twon8 = 3.9062500000e-03; /* 0x3b800000 */ +zero = 0.0f, +one = 1.0f, +two8 = 2.5600000000e+02f, /* 0x43800000 */ +twon8 = 3.9062500000e-03f; /* 0x3b800000 */ #ifdef __STDC__ int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __uint8_t *ipio2) diff --git a/lib/libm/kf_sin.c b/lib/libm/kf_sin.c index 6ef8903cae..9f2290664a 100644 --- a/lib/libm/kf_sin.c +++ b/lib/libm/kf_sin.c @@ -29,13 +29,13 @@ static const float #else static float #endif -half = 5.0000000000e-01,/* 0x3f000000 */ -S1 = -1.6666667163e-01, /* 0xbe2aaaab */ -S2 = 8.3333337680e-03, /* 0x3c088889 */ -S3 = -1.9841270114e-04, /* 0xb9500d01 */ -S4 = 2.7557314297e-06, /* 0x3638ef1b */ -S5 = -2.5050759689e-08, /* 0xb2d72f34 */ -S6 = 1.5896910177e-10; /* 0x2f2ec9d3 */ +half = 5.0000000000e-01f,/* 0x3f000000 */ +S1 = -1.6666667163e-01f, /* 0xbe2aaaab */ +S2 = 8.3333337680e-03f, /* 0x3c088889 */ +S3 = -1.9841270114e-04f, /* 0xb9500d01 */ +S4 = 2.7557314297e-06f, /* 0x3638ef1b */ +S5 = -2.5050759689e-08f, /* 0xb2d72f34 */ +S6 = 1.5896910177e-10f; /* 0x2f2ec9d3 */ #ifdef __STDC__ float __kernel_sinf(float x, float y, int iy) diff --git a/lib/libm/kf_tan.c b/lib/libm/kf_tan.c index f0ff94c88d..305645b7a2 100644 --- a/lib/libm/kf_tan.c +++ b/lib/libm/kf_tan.c @@ -28,23 +28,23 @@ static const float #else static float #endif -one = 1.0000000000e+00, /* 0x3f800000 */ -pio4 = 7.8539812565e-01, /* 0x3f490fda */ -pio4lo= 3.7748947079e-08, /* 0x33222168 */ +one = 1.0000000000e+00f, /* 0x3f800000 */ +pio4 = 7.8539812565e-01f, /* 0x3f490fda */ +pio4lo= 3.7748947079e-08f, /* 0x33222168 */ T[] = { - 3.3333334327e-01, /* 0x3eaaaaab */ - 1.3333334029e-01, /* 0x3e088889 */ - 5.3968254477e-02, /* 0x3d5d0dd1 */ - 2.1869488060e-02, /* 0x3cb327a4 */ - 8.8632395491e-03, /* 0x3c11371f */ - 3.5920790397e-03, /* 0x3b6b6916 */ - 1.4562094584e-03, /* 0x3abede48 */ - 5.8804126456e-04, /* 0x3a1a26c8 */ - 2.4646313977e-04, /* 0x398137b9 */ - 7.8179444245e-05, /* 0x38a3f445 */ - 7.1407252108e-05, /* 0x3895c07a */ - -1.8558637748e-05, /* 0xb79bae5f */ - 2.5907305826e-05, /* 0x37d95384 */ + 3.3333334327e-01f, /* 0x3eaaaaab */ + 1.3333334029e-01f, /* 0x3e088889 */ + 5.3968254477e-02f, /* 0x3d5d0dd1 */ + 2.1869488060e-02f, /* 0x3cb327a4 */ + 8.8632395491e-03f, /* 0x3c11371f */ + 3.5920790397e-03f, /* 0x3b6b6916 */ + 1.4562094584e-03f, /* 0x3abede48 */ + 5.8804126456e-04f, /* 0x3a1a26c8 */ + 2.4646313977e-04f, /* 0x398137b9 */ + 7.8179444245e-05f, /* 0x38a3f445 */ + 7.1407252108e-05f, /* 0x3895c07a */ + -1.8558637748e-05f, /* 0xb79bae5f */ + 2.5907305826e-05f, /* 0x37d95384 */ }; #ifdef __STDC__ diff --git a/lib/libm/log1pf.c b/lib/libm/log1pf.c index 89ed250b08..306b1cd1c2 100644 --- a/lib/libm/log1pf.c +++ b/lib/libm/log1pf.c @@ -21,13 +21,13 @@ #pragma GCC diagnostic ignored "-Wfloat-equal" static const float -ln2_hi = 6.9313812256e-01, /* 0x3f317180 */ -ln2_lo = 9.0580006145e-06, /* 0x3717f7d1 */ +ln2_hi = 6.9313812256e-01f, /* 0x3f317180 */ +ln2_lo = 9.0580006145e-06f, /* 0x3717f7d1 */ /* |(log(1+s)-log(1-s))/s - Lg(s)| < 2**-34.24 (~[-4.95e-11, 4.97e-11]). */ -Lg1 = 0xaaaaaa.0p-24, /* 0.66666662693 */ -Lg2 = 0xccce13.0p-25, /* 0.40000972152 */ -Lg3 = 0x91e9ee.0p-25, /* 0.28498786688 */ -Lg4 = 0xf89e26.0p-26; /* 0.24279078841 */ +Lg1 = 0xaaaaaa.0p-24f, /* 0.66666662693 */ +Lg2 = 0xccce13.0p-25f, /* 0.40000972152 */ +Lg3 = 0x91e9ee.0p-25f, /* 0.28498786688 */ +Lg4 = 0xf89e26.0p-26f; /* 0.24279078841 */ float log1pf(float x) { diff --git a/lib/libm/math.c b/lib/libm/math.c index c3dbf657ff..09f4fc2d08 100644 --- a/lib/libm/math.c +++ b/lib/libm/math.c @@ -53,7 +53,7 @@ float copysignf(float x, float y) { } #endif -static const float _M_LN10 = 2.30258509299404; // 0x40135d8e +static const float _M_LN10 = 2.30258509299404f; // 0x40135d8e float log10f(float x) { return logf(x) / (float)_M_LN10; } #undef _M_LN2 static const float _M_LN2 = 0.6931472; @@ -142,34 +142,34 @@ float scalbnf(float x, int n) */ static const float -bp[] = {1.0, 1.5,}, -dp_h[] = { 0.0, 5.84960938e-01,}, /* 0x3f15c000 */ -dp_l[] = { 0.0, 1.56322085e-06,}, /* 0x35d1cfdc */ -two24 = 16777216.0, /* 0x4b800000 */ -huge = 1.0e30, -tiny = 1.0e-30, +bp[] = {1.0f, 1.5f,}, +dp_h[] = { 0.0f, 5.84960938e-01f,}, /* 0x3f15c000 */ +dp_l[] = { 0.0f, 1.56322085e-06f,}, /* 0x35d1cfdc */ +two24 = 16777216.0f, /* 0x4b800000 */ +huge = 1.0e30f, +tiny = 1.0e-30f, /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ -L1 = 6.0000002384e-01, /* 0x3f19999a */ -L2 = 4.2857143283e-01, /* 0x3edb6db7 */ -L3 = 3.3333334327e-01, /* 0x3eaaaaab */ -L4 = 2.7272811532e-01, /* 0x3e8ba305 */ -L5 = 2.3066075146e-01, /* 0x3e6c3255 */ -L6 = 2.0697501302e-01, /* 0x3e53f142 */ -P1 = 1.6666667163e-01, /* 0x3e2aaaab */ -P2 = -2.7777778450e-03, /* 0xbb360b61 */ -P3 = 6.6137559770e-05, /* 0x388ab355 */ -P4 = -1.6533901999e-06, /* 0xb5ddea0e */ -P5 = 4.1381369442e-08, /* 0x3331bb4c */ -lg2 = 6.9314718246e-01, /* 0x3f317218 */ -lg2_h = 6.93145752e-01, /* 0x3f317200 */ -lg2_l = 1.42860654e-06, /* 0x35bfbe8c */ -ovt = 4.2995665694e-08, /* -(128-log2(ovfl+.5ulp)) */ -cp = 9.6179670095e-01, /* 0x3f76384f =2/(3ln2) */ -cp_h = 9.6191406250e-01, /* 0x3f764000 =12b cp */ -cp_l = -1.1736857402e-04, /* 0xb8f623c6 =tail of cp_h */ -ivln2 = 1.4426950216e+00, /* 0x3fb8aa3b =1/ln2 */ -ivln2_h = 1.4426879883e+00, /* 0x3fb8aa00 =16b 1/ln2*/ -ivln2_l = 7.0526075433e-06; /* 0x36eca570 =1/ln2 tail*/ +L1 = 6.0000002384e-01f, /* 0x3f19999a */ +L2 = 4.2857143283e-01f, /* 0x3edb6db7 */ +L3 = 3.3333334327e-01f, /* 0x3eaaaaab */ +L4 = 2.7272811532e-01f, /* 0x3e8ba305 */ +L5 = 2.3066075146e-01f, /* 0x3e6c3255 */ +L6 = 2.0697501302e-01f, /* 0x3e53f142 */ +P1 = 1.6666667163e-01f, /* 0x3e2aaaab */ +P2 = -2.7777778450e-03f, /* 0xbb360b61 */ +P3 = 6.6137559770e-05f, /* 0x388ab355 */ +P4 = -1.6533901999e-06f, /* 0xb5ddea0e */ +P5 = 4.1381369442e-08f, /* 0x3331bb4c */ +lg2 = 6.9314718246e-01f, /* 0x3f317218 */ +lg2_h = 6.93145752e-01f, /* 0x3f317200 */ +lg2_l = 1.42860654e-06f, /* 0x35bfbe8c */ +ovt = 4.2995665694e-08f, /* -(128-log2(ovfl+.5ulp)) */ +cp = 9.6179670095e-01f, /* 0x3f76384f =2/(3ln2) */ +cp_h = 9.6191406250e-01f, /* 0x3f764000 =12b cp */ +cp_l = -1.1736857402e-04f, /* 0xb8f623c6 =tail of cp_h */ +ivln2 = 1.4426950216e+00f, /* 0x3fb8aa3b =1/ln2 */ +ivln2_h = 1.4426879883e+00f, /* 0x3fb8aa00 =16b 1/ln2*/ +ivln2_l = 7.0526075433e-06f; /* 0x36eca570 =1/ln2 tail*/ float powf(float x, float y) { @@ -406,7 +406,7 @@ float powf(float x, float y) */ static const float -half[2] = {0.5,-0.5}, +half[2] = {0.5f,-0.5f}, ln2hi = 6.9314575195e-1f, /* 0x3f317200 */ ln2lo = 1.4286067653e-6f, /* 0x35bfbe8e */ invln2 = 1.4426950216e+0f, /* 0x3fb8aa3b */ @@ -445,7 +445,7 @@ float expf(float x) /* argument reduction */ if (hx > 0x3eb17218) { /* if |x| > 0.5 ln2 */ if (hx > 0x3f851592) /* if |x| > 1.5 ln2 */ - k = invln2*x + half[sign]; + k = (int)(invln2*x + half[sign]); else k = 1 - sign - sign; hi = x - k*ln2hi; /* k*ln2hi is exact here */ @@ -492,17 +492,17 @@ float expf(float x) */ static const float -o_threshold = 8.8721679688e+01, /* 0x42b17180 */ -ln2_hi = 6.9313812256e-01, /* 0x3f317180 */ -ln2_lo = 9.0580006145e-06, /* 0x3717f7d1 */ +o_threshold = 8.8721679688e+01f, /* 0x42b17180 */ +ln2_hi = 6.9313812256e-01f, /* 0x3f317180 */ +ln2_lo = 9.0580006145e-06f, /* 0x3717f7d1 */ //invln2 = 1.4426950216e+00, /* 0x3fb8aa3b */ /* * Domain [-0.34568, 0.34568], range ~[-6.694e-10, 6.696e-10]: * |6 / x * (1 + 2 * (1 / (exp(x) - 1) - 1 / x)) - q(x)| < 2**-30.04 * Scaled coefficients: Qn_here = 2**n * Qn_for_q (see s_expm1.c): */ -Q1 = -3.3333212137e-2, /* -0x888868.0p-28 */ -Q2 = 1.5807170421e-3; /* 0xcf3010.0p-33 */ +Q1 = -3.3333212137e-2f, /* -0x888868.0p-28 */ +Q2 = 1.5807170421e-3f; /* 0xcf3010.0p-33 */ float expm1f(float x) { @@ -536,7 +536,7 @@ float expm1f(float x) k = -1; } } else { - k = invln2*x + (sign ? -0.5f : 0.5f); + k = (int)(invln2*x + (sign ? -0.5f : 0.5f)); t = k; hi = x - t*ln2_hi; /* t*ln2_hi is exact here */ lo = t*ln2_lo; diff --git a/lib/libm/sf_erf.c b/lib/libm/sf_erf.c index a6aec096f1..e1b0a78fb4 100644 --- a/lib/libm/sf_erf.c +++ b/lib/libm/sf_erf.c @@ -35,79 +35,79 @@ static const float #else static float #endif -tiny = 1e-30, -half= 5.0000000000e-01, /* 0x3F000000 */ -one = 1.0000000000e+00, /* 0x3F800000 */ -two = 2.0000000000e+00, /* 0x40000000 */ +tiny = 1e-30f, +half= 5.0000000000e-01f, /* 0x3F000000 */ +one = 1.0000000000e+00f, /* 0x3F800000 */ +two = 2.0000000000e+00f, /* 0x40000000 */ /* c = (subfloat)0.84506291151 */ -erx = 8.4506291151e-01, /* 0x3f58560b */ +erx = 8.4506291151e-01f, /* 0x3f58560b */ /* * Coefficients for approximation to erf on [0,0.84375] */ -efx = 1.2837916613e-01, /* 0x3e0375d4 */ -efx8= 1.0270333290e+00, /* 0x3f8375d4 */ -pp0 = 1.2837916613e-01, /* 0x3e0375d4 */ -pp1 = -3.2504209876e-01, /* 0xbea66beb */ -pp2 = -2.8481749818e-02, /* 0xbce9528f */ -pp3 = -5.7702702470e-03, /* 0xbbbd1489 */ -pp4 = -2.3763017452e-05, /* 0xb7c756b1 */ -qq1 = 3.9791721106e-01, /* 0x3ecbbbce */ -qq2 = 6.5022252500e-02, /* 0x3d852a63 */ -qq3 = 5.0813062117e-03, /* 0x3ba68116 */ -qq4 = 1.3249473704e-04, /* 0x390aee49 */ -qq5 = -3.9602282413e-06, /* 0xb684e21a */ +efx = 1.2837916613e-01f, /* 0x3e0375d4 */ +efx8= 1.0270333290e+00f, /* 0x3f8375d4 */ +pp0 = 1.2837916613e-01f, /* 0x3e0375d4 */ +pp1 = -3.2504209876e-01f, /* 0xbea66beb */ +pp2 = -2.8481749818e-02f, /* 0xbce9528f */ +pp3 = -5.7702702470e-03f, /* 0xbbbd1489 */ +pp4 = -2.3763017452e-05f, /* 0xb7c756b1 */ +qq1 = 3.9791721106e-01f, /* 0x3ecbbbce */ +qq2 = 6.5022252500e-02f, /* 0x3d852a63 */ +qq3 = 5.0813062117e-03f, /* 0x3ba68116 */ +qq4 = 1.3249473704e-04f, /* 0x390aee49 */ +qq5 = -3.9602282413e-06f, /* 0xb684e21a */ /* * Coefficients for approximation to erf in [0.84375,1.25] */ -pa0 = -2.3621185683e-03, /* 0xbb1acdc6 */ -pa1 = 4.1485610604e-01, /* 0x3ed46805 */ -pa2 = -3.7220788002e-01, /* 0xbebe9208 */ -pa3 = 3.1834661961e-01, /* 0x3ea2fe54 */ -pa4 = -1.1089469492e-01, /* 0xbde31cc2 */ -pa5 = 3.5478305072e-02, /* 0x3d1151b3 */ -pa6 = -2.1663755178e-03, /* 0xbb0df9c0 */ -qa1 = 1.0642088205e-01, /* 0x3dd9f331 */ -qa2 = 5.4039794207e-01, /* 0x3f0a5785 */ -qa3 = 7.1828655899e-02, /* 0x3d931ae7 */ -qa4 = 1.2617121637e-01, /* 0x3e013307 */ -qa5 = 1.3637083583e-02, /* 0x3c5f6e13 */ -qa6 = 1.1984500103e-02, /* 0x3c445aa3 */ +pa0 = -2.3621185683e-03f, /* 0xbb1acdc6 */ +pa1 = 4.1485610604e-01f, /* 0x3ed46805 */ +pa2 = -3.7220788002e-01f, /* 0xbebe9208 */ +pa3 = 3.1834661961e-01f, /* 0x3ea2fe54 */ +pa4 = -1.1089469492e-01f, /* 0xbde31cc2 */ +pa5 = 3.5478305072e-02f, /* 0x3d1151b3 */ +pa6 = -2.1663755178e-03f, /* 0xbb0df9c0 */ +qa1 = 1.0642088205e-01f, /* 0x3dd9f331 */ +qa2 = 5.4039794207e-01f, /* 0x3f0a5785 */ +qa3 = 7.1828655899e-02f, /* 0x3d931ae7 */ +qa4 = 1.2617121637e-01f, /* 0x3e013307 */ +qa5 = 1.3637083583e-02f, /* 0x3c5f6e13 */ +qa6 = 1.1984500103e-02f, /* 0x3c445aa3 */ /* * Coefficients for approximation to erfc in [1.25,1/0.35] */ -ra0 = -9.8649440333e-03, /* 0xbc21a093 */ -ra1 = -6.9385856390e-01, /* 0xbf31a0b7 */ -ra2 = -1.0558626175e+01, /* 0xc128f022 */ -ra3 = -6.2375331879e+01, /* 0xc2798057 */ -ra4 = -1.6239666748e+02, /* 0xc322658c */ -ra5 = -1.8460508728e+02, /* 0xc3389ae7 */ -ra6 = -8.1287437439e+01, /* 0xc2a2932b */ -ra7 = -9.8143291473e+00, /* 0xc11d077e */ -sa1 = 1.9651271820e+01, /* 0x419d35ce */ -sa2 = 1.3765776062e+02, /* 0x4309a863 */ -sa3 = 4.3456588745e+02, /* 0x43d9486f */ -sa4 = 6.4538726807e+02, /* 0x442158c9 */ -sa5 = 4.2900814819e+02, /* 0x43d6810b */ -sa6 = 1.0863500214e+02, /* 0x42d9451f */ -sa7 = 6.5702495575e+00, /* 0x40d23f7c */ -sa8 = -6.0424413532e-02, /* 0xbd777f97 */ +ra0 = -9.8649440333e-03f, /* 0xbc21a093 */ +ra1 = -6.9385856390e-01f, /* 0xbf31a0b7 */ +ra2 = -1.0558626175e+01f, /* 0xc128f022 */ +ra3 = -6.2375331879e+01f, /* 0xc2798057 */ +ra4 = -1.6239666748e+02f, /* 0xc322658c */ +ra5 = -1.8460508728e+02f, /* 0xc3389ae7 */ +ra6 = -8.1287437439e+01f, /* 0xc2a2932b */ +ra7 = -9.8143291473e+00f, /* 0xc11d077e */ +sa1 = 1.9651271820e+01f, /* 0x419d35ce */ +sa2 = 1.3765776062e+02f, /* 0x4309a863 */ +sa3 = 4.3456588745e+02f, /* 0x43d9486f */ +sa4 = 6.4538726807e+02f, /* 0x442158c9 */ +sa5 = 4.2900814819e+02f, /* 0x43d6810b */ +sa6 = 1.0863500214e+02f, /* 0x42d9451f */ +sa7 = 6.5702495575e+00f, /* 0x40d23f7c */ +sa8 = -6.0424413532e-02f, /* 0xbd777f97 */ /* * Coefficients for approximation to erfc in [1/.35,28] */ -rb0 = -9.8649431020e-03, /* 0xbc21a092 */ -rb1 = -7.9928326607e-01, /* 0xbf4c9dd4 */ -rb2 = -1.7757955551e+01, /* 0xc18e104b */ -rb3 = -1.6063638306e+02, /* 0xc320a2ea */ -rb4 = -6.3756646729e+02, /* 0xc41f6441 */ -rb5 = -1.0250950928e+03, /* 0xc480230b */ -rb6 = -4.8351919556e+02, /* 0xc3f1c275 */ -sb1 = 3.0338060379e+01, /* 0x41f2b459 */ -sb2 = 3.2579251099e+02, /* 0x43a2e571 */ -sb3 = 1.5367296143e+03, /* 0x44c01759 */ -sb4 = 3.1998581543e+03, /* 0x4547fdbb */ -sb5 = 2.5530502930e+03, /* 0x451f90ce */ -sb6 = 4.7452853394e+02, /* 0x43ed43a7 */ -sb7 = -2.2440952301e+01; /* 0xc1b38712 */ +rb0 = -9.8649431020e-03f, /* 0xbc21a092 */ +rb1 = -7.9928326607e-01f, /* 0xbf4c9dd4 */ +rb2 = -1.7757955551e+01f, /* 0xc18e104b */ +rb3 = -1.6063638306e+02f, /* 0xc320a2ea */ +rb4 = -6.3756646729e+02f, /* 0xc41f6441 */ +rb5 = -1.0250950928e+03f, /* 0xc480230b */ +rb6 = -4.8351919556e+02f, /* 0xc3f1c275 */ +sb1 = 3.0338060379e+01f, /* 0x41f2b459 */ +sb2 = 3.2579251099e+02f, /* 0x43a2e571 */ +sb3 = 1.5367296143e+03f, /* 0x44c01759 */ +sb4 = 3.1998581543e+03f, /* 0x4547fdbb */ +sb5 = 2.5530502930e+03f, /* 0x451f90ce */ +sb6 = 4.7452853394e+02f, /* 0x43ed43a7 */ +sb7 = -2.2440952301e+01f; /* 0xc1b38712 */ #ifdef __STDC__ float erff(float x) diff --git a/lib/libm/sf_frexp.c b/lib/libm/sf_frexp.c index 92cedb8bc4..097185cb7c 100644 --- a/lib/libm/sf_frexp.c +++ b/lib/libm/sf_frexp.c @@ -29,7 +29,7 @@ static const float #else static float #endif -two25 = 3.3554432000e+07; /* 0x4c000000 */ +two25 = 3.3554432000e+07f; /* 0x4c000000 */ #ifdef __STDC__ float frexpf(float x, int *eptr) diff --git a/lib/libm/sf_ldexp.c b/lib/libm/sf_ldexp.c index c177160a3c..ebddc1f9db 100644 --- a/lib/libm/sf_ldexp.c +++ b/lib/libm/sf_ldexp.c @@ -23,7 +23,6 @@ */ #include "fdlibm.h" -//#include #ifdef __STDC__ float ldexpf(float value, int exp) diff --git a/lib/libm/sf_modf.c b/lib/libm/sf_modf.c index 8bad5d2e28..8141aa87e7 100644 --- a/lib/libm/sf_modf.c +++ b/lib/libm/sf_modf.c @@ -25,9 +25,9 @@ #include "fdlibm.h" #ifdef __STDC__ -static const float one = 1.0; +static const float one = 1.0f; #else -static float one = 1.0; +static float one = 1.0f; #endif #ifdef __STDC__ diff --git a/lib/libm/wf_lgamma.c b/lib/libm/wf_lgamma.c index 8a7fb822d7..bcf3705420 100644 --- a/lib/libm/wf_lgamma.c +++ b/lib/libm/wf_lgamma.c @@ -25,8 +25,6 @@ #include "fdlibm.h" #define _IEEE_LIBM 1 -//#include -//#include #ifdef __STDC__ float lgammaf(float x) diff --git a/lib/libm_dbl/nearbyint.c b/lib/libm_dbl/nearbyint.c index 6e9b0c1f78..c3a0b88afe 100644 --- a/lib/libm_dbl/nearbyint.c +++ b/lib/libm_dbl/nearbyint.c @@ -1,4 +1,3 @@ -//#include #include /* nearbyint is the same as rint, but it must not raise the inexact exception */ diff --git a/lib/libm_dbl/round.c b/lib/libm_dbl/round.c new file mode 100644 index 0000000000..130d58d257 --- /dev/null +++ b/lib/libm_dbl/round.c @@ -0,0 +1,35 @@ +#include "libm.h" + +#if FLT_EVAL_METHOD==0 || FLT_EVAL_METHOD==1 +#define EPS DBL_EPSILON +#elif FLT_EVAL_METHOD==2 +#define EPS LDBL_EPSILON +#endif +static const double_t toint = 1/EPS; + +double round(double x) +{ + union {double f; uint64_t i;} u = {x}; + int e = u.i >> 52 & 0x7ff; + double_t y; + + if (e >= 0x3ff+52) + return x; + if (u.i >> 63) + x = -x; + if (e < 0x3ff-1) { + /* raise inexact if x!=0 */ + FORCE_EVAL(x + toint); + return 0*u.f; + } + y = x + toint - toint - x; + if (y > 0.5) + y = y + x - 1; + else if (y <= -0.5) + y = y + x + 1; + else + y = y + x; + if (u.i >> 63) + y = -y; + return y; +} diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index dea01b1c07..c8c1ca1f77 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -29,8 +29,8 @@ static int lfs2_bd_read(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { uint8_t *data = buffer; - LFS2_ASSERT(block != LFS2_BLOCK_NULL); - if (off+size > lfs2->cfg->block_size) { + if (block >= lfs2->cfg->block_count || + off+size > lfs2->cfg->block_size) { return LFS2_ERR_CORRUPT; } @@ -71,6 +71,21 @@ static int lfs2_bd_read(lfs2_t *lfs2, 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; @@ -173,7 +188,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, 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_NULL); + LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->cfg->block_count); LFS2_ASSERT(off + size <= lfs2->cfg->block_size); while (size > 0) { @@ -265,6 +280,12 @@ 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); } @@ -317,14 +338,13 @@ struct lfs2_diskoff { sizeof((struct lfs2_mattr[]){__VA_ARGS__}) / sizeof(struct lfs2_mattr) // operations on global state -static inline void lfs2_gstate_xor(struct lfs2_gstate *a, - const struct lfs2_gstate *b) { +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 struct lfs2_gstate *a) { +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; @@ -333,43 +353,30 @@ static inline bool lfs2_gstate_iszero(const struct lfs2_gstate *a) { return true; } -static inline bool lfs2_gstate_hasorphans(const struct lfs2_gstate *a) { +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 struct lfs2_gstate *a) { +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 struct lfs2_gstate *a) { +static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) { return lfs2_tag_type1(a->tag); } -static inline bool lfs2_gstate_hasmovehere(const struct lfs2_gstate *a, +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_xororphans(struct lfs2_gstate *a, - const struct lfs2_gstate *b, bool orphans) { - a->tag ^= LFS2_MKTAG(0x800, 0, 0) & (b->tag ^ ((uint32_t)orphans << 31)); -} - -static inline void lfs2_gstate_xormove(struct lfs2_gstate *a, - const struct lfs2_gstate *b, uint16_t id, const lfs2_block_t pair[2]) { - a->tag ^= LFS2_MKTAG(0x7ff, 0x3ff, 0) & (b->tag ^ ( - (id != 0x3ff) ? LFS2_MKTAG(LFS2_TYPE_DELETE, id, 0) : 0)); - a->pair[0] ^= b->pair[0] ^ ((id != 0x3ff) ? pair[0] : 0); - a->pair[1] ^= b->pair[1] ^ ((id != 0x3ff) ? pair[1] : 0); -} - -static inline void lfs2_gstate_fromle32(struct lfs2_gstate *a) { +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(struct lfs2_gstate *a) { +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]); @@ -422,6 +429,9 @@ 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]); +int lfs2_fs_traverseraw(lfs2_t *lfs2, + int (*cb)(void *data, lfs2_block_t block), void *data, + bool includeorphans); static int lfs2_fs_forceconsistency(lfs2_t *lfs2); static int lfs2_deinit(lfs2_t *lfs2); #ifdef LFS2_MIGRATE @@ -442,6 +452,19 @@ static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { return 0; } +static void lfs2_alloc_ack(lfs2_t *lfs2) { + lfs2->free.ack = lfs2->cfg->block_count; +} + +// Invalidate the lookahead buffer. This is done during mounting and +// failed traversals +static void lfs2_alloc_reset(lfs2_t *lfs2) { + lfs2->free.off = lfs2->seed % lfs2->cfg->block_size; + lfs2->free.size = 0; + lfs2->free.i = 0; + lfs2_alloc_ack(lfs2); +} + static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { while (lfs2->free.i != lfs2->free.size) { @@ -480,18 +503,14 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { // find mask of free blocks from tree memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_traverse(lfs2, lfs2_alloc_lookahead, lfs2); + int err = lfs2_fs_traverseraw(lfs2, lfs2_alloc_lookahead, lfs2, true); if (err) { + lfs2_alloc_reset(lfs2); return err; } } } -static void lfs2_alloc_ack(lfs2_t *lfs2) { - lfs2->free.ack = lfs2->cfg->block_count; -} - - /// 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, @@ -500,8 +519,9 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t ntag = dir->etag; lfs2_stag_t gdiff = 0; - if (lfs2_gstate_hasmovehere(&lfs2->gstate, dir->pair) && - lfs2_tag_id(gtag) <= lfs2_tag_id(lfs2->gstate.tag)) { + 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); } @@ -652,7 +672,7 @@ static int lfs2_dir_traverse_filter(void *p, 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, bool hasseenmove, + 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) { @@ -680,13 +700,6 @@ static int lfs2_dir_traverse(lfs2_t *lfs2, buffer = attrs[0].buffer; attrs += 1; attrcount -= 1; - } else if (!hasseenmove && - lfs2_gstate_hasmovehere(&lfs2->gpending, dir->pair)) { - // Wait, we have pending move? Handle this here (we need to - // or else we risk letting moves fall out of date) - tag = lfs2->gpending.tag & LFS2_MKTAG(0x7ff, 0x3ff, 0); - buffer = NULL; - hasseenmove = true; } else { return 0; } @@ -701,7 +714,7 @@ static int lfs2_dir_traverse(lfs2_t *lfs2, 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, hasseenmove, + dir, off, ptag, attrs, attrcount, 0, 0, 0, 0, 0, lfs2_dir_traverse_filter, &tag); if (filter < 0) { @@ -725,7 +738,7 @@ static int lfs2_dir_traverse(lfs2_t *lfs2, uint16_t fromid = lfs2_tag_size(tag); uint16_t toid = lfs2_tag_id(tag); int err = lfs2_dir_traverse(lfs2, - buffer, 0, LFS2_BLOCK_NULL, NULL, 0, true, + buffer, 0, 0xffffffff, NULL, 0, LFS2_MKTAG(0x600, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, 0, 0), fromid, fromid+1, toid-fromid+diff, @@ -759,6 +772,12 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // 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; @@ -785,7 +804,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // 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 = LFS2_BLOCK_NULL; + lfs2_tag_t ptag = 0xffffffff; uint16_t tempcount = 0; lfs2_block_t temptail[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; @@ -793,7 +812,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, lfs2_stag_t tempbesttag = besttag; dir->rev = lfs2_tole32(dir->rev); - uint32_t crc = lfs2_crc(LFS2_BLOCK_NULL, &dir->rev, sizeof(dir->rev)); + uint32_t crc = lfs2_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); dir->rev = lfs2_fromle32(dir->rev); while (true) { @@ -816,11 +835,13 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, tag = lfs2_frombe32(tag) ^ ptag; // next commit not yet programmed or we're not in valid range - if (!lfs2_tag_isvalid(tag) || - off + lfs2_tag_dsize(tag) > lfs2->cfg->block_size) { + 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; @@ -862,7 +883,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->split = tempsplit; // reset crc - crc = LFS2_BLOCK_NULL; + crc = 0xffffffff; continue; } @@ -929,6 +950,11 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, 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 @@ -940,11 +966,11 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // consider what we have good enough if (dir->off > 0) { // synthetic move - if (lfs2_gstate_hasmovehere(&lfs2->gstate, dir->pair)) { - if (lfs2_tag_id(lfs2->gstate.tag) == lfs2_tag_id(besttag)) { + 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->gstate.tag) < lfs2_tag_id(besttag)) { + lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) { besttag -= LFS2_MKTAG(0, 1, 0); } } @@ -968,21 +994,22 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->rev = revs[(r+1)%2]; } - LFS2_ERROR("Corrupted dir pair at %"PRIx32" %"PRIx32, + 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=0 can never match a tag since this + // 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, -1, 0, NULL, NULL, NULL); + 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, - struct lfs2_gstate *gstate) { - struct lfs2_gstate temp; + 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) { @@ -1234,13 +1261,14 @@ static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit, } static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { + const lfs2_off_t off1 = commit->off; + const uint32_t crc1 = commit->crc; // align to program units - const lfs2_off_t off1 = commit->off + sizeof(lfs2_tag_t); - const lfs2_off_t end = lfs2_alignup(off1 + sizeof(uint32_t), + const lfs2_off_t end = lfs2_alignup(off1 + 2*sizeof(uint32_t), lfs2->cfg->prog_size); // create crc tags to fill up remainder of commit, note that - // padding is not crcd, which lets fetches skip padding but + // 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); @@ -1250,7 +1278,7 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { } // read erased state from next program unit - lfs2_tag_t tag = LFS2_BLOCK_NULL; + lfs2_tag_t tag = 0xffffffff; int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(tag), commit->block, noff, &tag, sizeof(tag)); @@ -1276,7 +1304,7 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { commit->off += sizeof(tag)+lfs2_tag_size(tag); commit->ptag = tag ^ ((lfs2_tag_t)reset << 31); - commit->crc = LFS2_BLOCK_NULL; // reset crc for next "commit" + commit->crc = 0xffffffff; // reset crc for next "commit" } // flush buffers @@ -1287,10 +1315,16 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { // successful commit, check checksums to make sure lfs2_off_t off = commit->begin; - lfs2_off_t noff = off1; + lfs2_off_t noff = off1 + sizeof(uint32_t); while (off < end) { - uint32_t crc = LFS2_BLOCK_NULL; + 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, @@ -1328,6 +1362,9 @@ static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { } } + // 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, @@ -1343,7 +1380,7 @@ static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { // set defaults dir->off = sizeof(dir->rev); - dir->etag = LFS2_BLOCK_NULL; + dir->etag = 0xffffffff; dir->count = 0; dir->tail[0] = LFS2_BLOCK_NULL; dir->tail[1] = LFS2_BLOCK_NULL; @@ -1377,6 +1414,7 @@ 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) { @@ -1427,16 +1465,16 @@ 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[1], dir->pair[0]}; + const lfs2_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; bool relocated = false; - bool exhausted = 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, LFS2_BLOCK_NULL, attrs, attrcount, false, + source, 0, 0xffffffff, attrs, attrcount, LFS2_MKTAG(0x400, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, 0), begin, end, -begin, @@ -1476,8 +1514,14 @@ static int lfs2_dir_compact(lfs2_t *lfs2, // 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) == 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? @@ -1503,13 +1547,17 @@ static int lfs2_dir_compact(lfs2_t *lfs2, } } #ifdef LFS2_MIGRATE - } else if (lfs2_pair_cmp(dir->pair, lfs2->root) == 0 && lfs2->lfs21) { - // we can't relocate our root during migrations, as this would - // cause the superblock to get updated, which would clobber v1 + } 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 - exhausted = true; + tired = true; goto relocate; } } @@ -1517,26 +1565,19 @@ static int lfs2_dir_compact(lfs2_t *lfs2, // begin loop to commit compaction to blocks until a compact sticks while (true) { { - // There's nothing special about our global delta, so feed it into - // our local global delta - int err = lfs2_dir_getgstate(lfs2, dir, &lfs2->gdelta); - if (err) { - return err; - } - // setup commit state struct lfs2_commit commit = { .block = dir->pair[1], .off = 0, - .ptag = LFS2_BLOCK_NULL, - .crc = LFS2_BLOCK_NULL, + .ptag = 0xffffffff, + .crc = 0xffffffff, .begin = 0, .end = lfs2->cfg->block_size - 8, }; // erase block to write to - err = lfs2_bd_erase(lfs2, dir->pair[1]); + int err = lfs2_bd_erase(lfs2, dir->pair[1]); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; @@ -1558,7 +1599,7 @@ static int lfs2_dir_compact(lfs2_t *lfs2, // traverse the directory, this time writing out all unique tags err = lfs2_dir_traverse(lfs2, - source, 0, LFS2_BLOCK_NULL, attrs, attrcount, false, + source, 0, 0xffffffff, attrs, attrcount, LFS2_MKTAG(0x400, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, 0), begin, end, -begin, @@ -1586,14 +1627,25 @@ static int lfs2_dir_compact(lfs2_t *lfs2, } } - if (!relocated && !lfs2_gstate_iszero(&lfs2->gdelta)) { - // commit any globals, unless we're relocating, - // in which case our parent will steal our globals - lfs2_gstate_tole32(&lfs2->gdelta); + // 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(lfs2->gdelta)), &lfs2->gdelta); - lfs2_gstate_fromle32(&lfs2->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; @@ -1602,6 +1654,7 @@ static int lfs2_dir_compact(lfs2_t *lfs2, } } + // complete commit with crc err = lfs2_dir_commitcrc(lfs2, &commit); if (err) { if (err == LFS2_ERR_CORRUPT) { @@ -1616,10 +1669,10 @@ static int lfs2_dir_compact(lfs2_t *lfs2, dir->count = end - begin; dir->off = commit.off; dir->etag = commit.ptag; - // note we able to have already handled move here - if (lfs2_gstate_hasmovehere(&lfs2->gpending, dir->pair)) { - lfs2_gstate_xormove(&lfs2->gpending, - &lfs2->gpending, 0x3ff, NULL); + // update gstate + lfs2->gdelta = (lfs2_gstate_t){0}; + if (!relocated) { + lfs2->gdisk = lfs2->gstate; } } break; @@ -1628,31 +1681,31 @@ relocate: // commit was corrupted, drop caches and prepare to relocate block relocated = true; lfs2_cache_drop(lfs2, &lfs2->pcache); - if (!exhausted) { - LFS2_DEBUG("Bad block at %"PRIx32, dir->pair[1]); + if (!tired) { + LFS2_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); } // can't relocate superblock, filesystem is now frozen - if (lfs2_pair_cmp(oldpair, (const lfs2_block_t[2]){0, 1}) == 0) { - LFS2_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]); + 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 && !exhausted)) { + if (err && (err != LFS2_ERR_NOSPC || !tired)) { return err; } + tired = false; continue; } - if (!relocated) { - lfs2->gstate = lfs2->gpending; - lfs2->gdelta = (struct lfs2_gstate){0}; - } else { + if (relocated) { // update references if we relocated - LFS2_DEBUG("Relocating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, + 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) { @@ -1684,16 +1737,15 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, } // calculate changes to the directory - lfs2_tag_t deletetag = LFS2_BLOCK_NULL; - lfs2_tag_t createtag = LFS2_BLOCK_NULL; + 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) { - createtag = attrs[i].tag; dir->count += 1; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE) { - deletetag = attrs[i].tag; 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]; @@ -1702,26 +1754,21 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, } } - // do we have a pending move? - if (lfs2_gstate_hasmovehere(&lfs2->gpending, dir->pair)) { - deletetag = lfs2->gpending.tag & LFS2_MKTAG(0x7ff, 0x3ff, 0); - LFS2_ASSERT(dir->count > 0); - dir->count -= 1; - - // mark gdelta so we reflect the move we will fix - lfs2_gstate_xormove(&lfs2->gdelta, &lfs2->gpending, 0x3ff, NULL); - } - // should we actually drop the directory block? - if (lfs2_tag_isvalid(deletetag) && dir->count == 0) { + 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) { - return lfs2_dir_drop(lfs2, &pdir, dir); + err = lfs2_dir_drop(lfs2, &pdir, dir); + if (err) { + *dir = olddir; + return err; + } } } @@ -1731,7 +1778,7 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, .block = dir->pair[0], .off = dir->off, .ptag = dir->etag, - .crc = LFS2_BLOCK_NULL, + .crc = 0xffffffff, .begin = dir->off, .end = lfs2->cfg->block_size - 8, @@ -1740,7 +1787,7 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, // 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, false, + dir, dir->off, dir->etag, attrs, attrcount, 0, 0, 0, 0, 0, lfs2_dir_commit_commit, &(struct lfs2_dir_commit_commit){ lfs2, &commit}); @@ -1749,25 +1796,32 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } // commit any global diffs if we have any - if (!lfs2_gstate_iszero(&lfs2->gdelta)) { - err = lfs2_dir_getgstate(lfs2, dir, &lfs2->gdelta); + 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(&lfs2->gdelta); + lfs2_gstate_tole32(&delta); err = lfs2_dir_commitattr(lfs2, &commit, LFS2_MKTAG(LFS2_TYPE_MOVESTATE, 0x3ff, - sizeof(lfs2->gdelta)), &lfs2->gdelta); - lfs2_gstate_fromle32(&lfs2->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } } @@ -1778,6 +1832,7 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, if (err == LFS2_ERR_NOSPC || err == LFS2_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } @@ -1785,15 +1840,9 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, LFS2_ASSERT(commit.off % lfs2->cfg->prog_size == 0); dir->off = commit.off; dir->etag = commit.ptag; - - // note we able to have already handled move here - if (lfs2_gstate_hasmovehere(&lfs2->gpending, dir->pair)) { - lfs2_gstate_xormove(&lfs2->gpending, &lfs2->gpending, 0x3ff, NULL); - } - - // update gstate - lfs2->gstate = lfs2->gpending; - lfs2->gdelta = (struct lfs2_gstate){0}; + // and update gstate + lfs2->gdisk = lfs2->gstate; + lfs2->gdelta = (lfs2_gstate_t){0}; } else { compact: // fall back to compaction @@ -1802,33 +1851,45 @@ compact: int err = lfs2_dir_compact(lfs2, dir, attrs, attrcount, dir, 0, dir->count); if (err) { + *dir = olddir; return err; } } - // update any directories that are affected - lfs2_mdir_t copy = *dir; - - // two passes, once for things that aren't us, and one - // for things that are + // 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 (lfs2_pair_cmp(d->m.pair, copy.pair) == 0) { + if (&d->m != dir && lfs2_pair_cmp(d->m.pair, olddir.pair) == 0) { d->m = *dir; - if (d->id == lfs2_tag_id(deletetag)) { - d->m.pair[0] = LFS2_BLOCK_NULL; - d->m.pair[1] = LFS2_BLOCK_NULL; - } else if (d->id > lfs2_tag_id(deletetag)) { - d->id -= 1; - if (d->type == LFS2_TYPE_DIR) { - ((lfs2_dir_t*)d)->pos -= 1; - } - } else if (&d->m != dir && d->id >= lfs2_tag_id(createtag)) { - d->id += 1; - if (d->type == LFS2_TYPE_DIR) { - ((lfs2_dir_t*)d)->pos += 1; + 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; @@ -1854,9 +1915,10 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { return err; } - lfs2_mdir_t cwd; + struct lfs2_mlist cwd; + cwd.next = lfs2->mlist; uint16_t id; - err = lfs2_dir_find(lfs2, &cwd, &path, &id); + err = lfs2_dir_find(lfs2, &cwd.m, &path, &id); if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { LFS2_TRACE("lfs2_mkdir -> %d", (err < 0) ? err : LFS2_ERR_EXIST); return (err < 0) ? err : LFS2_ERR_EXIST; @@ -1879,7 +1941,7 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { } // find end of list - lfs2_mdir_t pred = cwd; + lfs2_mdir_t pred = cwd.m; while (pred.split) { err = lfs2_dir_fetch(lfs2, &pred, pred.tail); if (err) { @@ -1899,29 +1961,40 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { } // current block end of list? - if (cwd.split) { + 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; LFS2_TRACE("lfs2_mkdir -> %d", err); 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, LFS2_MKATTRS( + 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}, - {!cwd.split - ? LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8) - : LFS2_MKTAG(LFS2_FROM_NOOP, 0, 0), dir.pair})); + {LFS2_MKTAG_IF(!cwd.m.split, + LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs2_pair_fromle32(dir.pair); if (err) { LFS2_TRACE("lfs2_mkdir -> %d", err); @@ -1936,7 +2009,7 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); lfs2_stag_t tag = lfs2_dir_find(lfs2, &dir->m, &path, NULL); if (tag < 0) { - LFS2_TRACE("lfs2_dir_open -> %d", tag); + LFS2_TRACE("lfs2_dir_open -> %"PRId32, tag); return tag; } @@ -1955,7 +2028,7 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { 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) { - LFS2_TRACE("lfs2_dir_open -> %d", res); + LFS2_TRACE("lfs2_dir_open -> %"PRId32, res); return res; } lfs2_pair_fromle32(pair); @@ -2064,10 +2137,14 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { dir->pos = lfs2_min(2, off); off -= dir->pos; - while (off != 0) { - dir->id = lfs2_min(dir->m.count, off); - dir->pos += dir->id; - off -= dir->id; + // 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) { @@ -2080,6 +2157,8 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { LFS2_TRACE("lfs2_dir_seek -> %d", err); return err; } + + dir->id = 0; } } @@ -2103,8 +2182,6 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { return err; } - dir->m.pair[0] = dir->head[0]; - dir->m.pair[1] = dir->head[1]; dir->id = 0; dir->pos = 0; LFS2_TRACE("lfs2_dir_rewind -> %d", 0); @@ -2152,7 +2229,6 @@ static int lfs2_ctz_find(lfs2_t *lfs2, return err; } - LFS2_ASSERT(head >= 2 && head <= lfs2->cfg->block_count); current -= 1 << skip; } @@ -2172,7 +2248,6 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, if (err) { return err; } - LFS2_ASSERT(nblock >= 2 && nblock <= lfs2->cfg->block_count); { err = lfs2_bd_erase(lfs2, nblock); @@ -2189,16 +2264,16 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, return 0; } - size -= 1; - lfs2_off_t index = lfs2_ctz_index(lfs2, &size); - size += 1; + 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 (size != lfs2->cfg->block_size) { - for (lfs2_off_t i = 0; i < size; i++) { + 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, size-i, + NULL, rcache, noff-i, head, i, &data, 1); if (err) { return err; @@ -2216,19 +2291,19 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, } *block = nblock; - *off = size; + *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++) { - head = lfs2_tole32(head); + nhead = lfs2_tole32(nhead); err = lfs2_bd_prog(lfs2, pcache, rcache, true, - nblock, 4*i, &head, 4); - head = lfs2_fromle32(head); + nblock, 4*i, &nhead, 4); + nhead = lfs2_fromle32(nhead); if (err) { if (err == LFS2_ERR_CORRUPT) { goto relocate; @@ -2238,15 +2313,13 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, if (i != skips-1) { err = lfs2_bd_read(lfs2, - NULL, rcache, sizeof(head), - head, 4*i, &head, sizeof(head)); - head = lfs2_fromle32(head); + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs2_fromle32(nhead); if (err) { return err; } } - - LFS2_ASSERT(head >= 2 && head <= lfs2->cfg->block_count); } *block = nblock; @@ -2255,7 +2328,7 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, } relocate: - LFS2_DEBUG("Bad block at %"PRIx32, nblock); + LFS2_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs2_cache_drop(lfs2, pcache); @@ -2359,9 +2432,9 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, // 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_CREATE, file->id, 0)}, {LFS2_MKTAG(LFS2_TYPE_REG, file->id, nlen), path}, - {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), NULL})); + {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0)})); if (err) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; @@ -2559,7 +2632,7 @@ static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { return 0; relocate: - LFS2_DEBUG("Bad block at %"PRIx32, nblock); + LFS2_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs2_cache_drop(lfs2, &lfs2->pcache); @@ -2636,7 +2709,7 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { break; relocate: - LFS2_DEBUG("Bad block at %"PRIx32, file->block); + LFS2_DEBUG("Bad block at 0x%"PRIx32, file->block); err = lfs2_file_relocate(lfs2, file); if (err) { return err; @@ -2662,66 +2735,57 @@ int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(file->flags & LFS2_F_OPENED); - while (true) { - int err = lfs2_file_flush(lfs2, file); - if (err) { - file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_sync -> %d", err); - return err; - } - - if ((file->flags & LFS2_F_DIRTY) && - !(file->flags & LFS2_F_ERRED) && - !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) { - if (err == LFS2_ERR_NOSPC && (file->flags & LFS2_F_INLINE)) { - goto relocate; - } - file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_sync -> %d", err); - return err; - } - - file->flags &= ~LFS2_F_DIRTY; - } - + if (file->flags & LFS2_F_ERRED) { + // it's not safe to do anything if our file errored LFS2_TRACE("lfs2_file_sync -> %d", 0); return 0; + } -relocate: - // inline file doesn't fit anymore - err = lfs2_file_outline(lfs2, file); + int err = lfs2_file_flush(lfs2, file); + if (err) { + file->flags |= LFS2_F_ERRED; + LFS2_TRACE("lfs2_file_sync -> %d", err); + 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; LFS2_TRACE("lfs2_file_sync -> %d", err); return err; } + + file->flags &= ~LFS2_F_DIRTY; } + + LFS2_TRACE("lfs2_file_sync -> %d", 0); + return 0; } lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, @@ -2738,14 +2802,14 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, // flush out any writes int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_read -> %"PRId32, err); + LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } if (file->pos >= file->ctz.size) { // eof if past end - LFS2_TRACE("lfs2_file_read -> %"PRId32, 0); + LFS2_TRACE("lfs2_file_read -> %d", 0); return 0; } @@ -2761,7 +2825,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, file->ctz.head, file->ctz.size, file->pos, &file->block, &file->off); if (err) { - LFS2_TRACE("lfs2_file_read -> %"PRId32, err); + LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } else { @@ -2781,7 +2845,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), file->off, data, diff); if (err) { - LFS2_TRACE("lfs2_file_read -> %"PRId32, err); + LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } else { @@ -2789,7 +2853,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, NULL, &file->cache, lfs2->cfg->block_size, file->block, file->off, data, diff); if (err) { - LFS2_TRACE("lfs2_file_read -> %"PRId32, err); + LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } @@ -2818,7 +2882,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, // drop any reads int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2829,7 +2893,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, if (file->pos + size > lfs2->file_max) { // Larger than file limit? - LFS2_TRACE("lfs2_file_write -> %"PRId32, LFS2_ERR_FBIG); + LFS2_TRACE("lfs2_file_write -> %d", LFS2_ERR_FBIG); return LFS2_ERR_FBIG; } @@ -2855,7 +2919,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, int err = lfs2_file_outline(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2872,7 +2936,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, file->pos-1, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } @@ -2887,7 +2951,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } else { @@ -2908,7 +2972,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, goto relocate; } file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } @@ -2917,7 +2981,7 @@ relocate: err = lfs2_file_relocate(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, err); + LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2944,7 +3008,7 @@ lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, // write out everything beforehand, may be noop if rdonly int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_seek -> %"PRId32, err); + LFS2_TRACE("lfs2_file_seek -> %d", err); return err; } @@ -2960,7 +3024,7 @@ lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, if (npos > lfs2->file_max) { // file position out of range - LFS2_TRACE("lfs2_file_seek -> %"PRId32, LFS2_ERR_INVAL); + LFS2_TRACE("lfs2_file_seek -> %d", LFS2_ERR_INVAL); return LFS2_ERR_INVAL; } @@ -3008,7 +3072,7 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { if (file->pos != oldsize) { lfs2_soff_t res = lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %d", res); + LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } } @@ -3017,7 +3081,7 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { while (file->pos < size) { lfs2_ssize_t res = lfs2_file_write(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %d", res); + LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } } @@ -3026,7 +3090,7 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { // restore pos lfs2_soff_t res = lfs2_file_seek(lfs2, file, pos, LFS2_SEEK_SET); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %d", res); + LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } @@ -3046,7 +3110,7 @@ int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); lfs2_soff_t res = lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { - LFS2_TRACE("lfs2_file_rewind -> %d", res); + LFS2_TRACE("lfs2_file_rewind -> %"PRId32, res); return (int)res; } @@ -3075,7 +3139,7 @@ int lfs2_stat(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) { - LFS2_TRACE("lfs2_stat -> %d", tag); + LFS2_TRACE("lfs2_stat -> %"PRId32, tag); return (int)tag; } @@ -3096,56 +3160,65 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0 || lfs2_tag_id(tag) == 0x3ff) { - LFS2_TRACE("lfs2_remove -> %d", (tag < 0) ? tag : LFS2_ERR_INVAL); + LFS2_TRACE("lfs2_remove -> %"PRId32, (tag < 0) ? tag : LFS2_ERR_INVAL); return (tag < 0) ? (int)tag : LFS2_ERR_INVAL; } - lfs2_mdir_t dir; + 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) { - LFS2_TRACE("lfs2_remove -> %d", res); + LFS2_TRACE("lfs2_remove -> %"PRId32, res); return (int)res; } lfs2_pair_fromle32(pair); - err = lfs2_dir_fetch(lfs2, &dir, pair); + err = lfs2_dir_fetch(lfs2, &dir.m, pair); if (err) { LFS2_TRACE("lfs2_remove -> %d", err); return err; } - if (dir.count > 0 || dir.split) { + if (dir.m.count > 0 || dir.m.split) { LFS2_TRACE("lfs2_remove -> %d", LFS2_ERR_NOTEMPTY); 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})); + {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(tag), 0)})); if (err) { + lfs2->mlist = dir.next; LFS2_TRACE("lfs2_remove -> %d", err); 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.pair, &cwd); + err = lfs2_fs_pred(lfs2, dir.m.pair, &cwd); if (err) { LFS2_TRACE("lfs2_remove -> %d", err); return err; } - err = lfs2_dir_drop(lfs2, &cwd, &dir); + err = lfs2_dir_drop(lfs2, &cwd, &dir.m); if (err) { LFS2_TRACE("lfs2_remove -> %d", err); return err; @@ -3170,7 +3243,8 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { lfs2_mdir_t oldcwd; lfs2_stag_t oldtag = lfs2_dir_find(lfs2, &oldcwd, &oldpath, NULL); if (oldtag < 0 || lfs2_tag_id(oldtag) == 0x3ff) { - LFS2_TRACE("lfs2_rename -> %d", (oldtag < 0) ? oldtag : LFS2_ERR_INVAL); + LFS2_TRACE("lfs2_rename -> %"PRId32, + (oldtag < 0) ? oldtag : LFS2_ERR_INVAL); return (oldtag < 0) ? (int)oldtag : LFS2_ERR_INVAL; } @@ -3180,11 +3254,17 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { 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)) { - LFS2_TRACE("lfs2_rename -> %d", (prevtag < 0) ? prevtag : LFS2_ERR_INVAL); + LFS2_TRACE("lfs2_rename -> %"PRId32, + (prevtag < 0) ? prevtag : LFS2_ERR_INVAL); return (prevtag < 0) ? (int)prevtag : LFS2_ERR_INVAL; } - lfs2_mdir_t prevdir; + // 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); @@ -3192,83 +3272,98 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_NAMETOOLONG); 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)) { LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_ISDIR); return LFS2_ERR_ISDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + LFS2_TRACE("lfs2_rename -> %d", 0); + 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) { - LFS2_TRACE("lfs2_rename -> %d", res); + LFS2_TRACE("lfs2_rename -> %"PRId32, res); return (int)res; } lfs2_pair_fromle32(prevpair); // must be empty before removal - err = lfs2_dir_fetch(lfs2, &prevdir, prevpair); + err = lfs2_dir_fetch(lfs2, &prevdir.m, prevpair); if (err) { LFS2_TRACE("lfs2_rename -> %d", err); return err; } - if (prevdir.count > 0 || prevdir.split) { + if (prevdir.m.count > 0 || prevdir.m.split) { LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_NOTEMPTY); 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; } - // create move to fix later - uint16_t newoldtagid = lfs2_tag_id(oldtag); - if (lfs2_pair_cmp(oldcwd.pair, newcwd.pair) == 0 && - prevtag == LFS2_ERR_NOENT && newid <= newoldtagid) { - // 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 - newoldtagid += 1; + if (!samepair) { + lfs2_fs_prepmove(lfs2, newoldid, oldcwd.pair); } - lfs2_fs_prepmove(lfs2, newoldtagid, oldcwd.pair); - // move over all attributes err = lfs2_dir_commit(lfs2, &newcwd, LFS2_MKATTRS( - {prevtag != LFS2_ERR_NOENT - ? LFS2_MKTAG(LFS2_TYPE_DELETE, newid, 0) - : LFS2_MKTAG(LFS2_FROM_NOOP, 0, 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(prevtag != LFS2_ERR_NOENT, + LFS2_TYPE_DELETE, newid, 0)}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0)}, + {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)})); if (err) { + lfs2->mlist = prevdir.next; LFS2_TRACE("lfs2_rename -> %d", err); return err; } // let commit clean up after move (if we're different! otherwise move // logic already fixed it for us) - if (lfs2_pair_cmp(oldcwd.pair, newcwd.pair) != 0) { - err = lfs2_dir_commit(lfs2, &oldcwd, NULL, 0); + 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)})); if (err) { + lfs2->mlist = prevdir.next; LFS2_TRACE("lfs2_rename -> %d", err); 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.pair, &newcwd); + err = lfs2_fs_pred(lfs2, prevdir.m.pair, &newcwd); if (err) { LFS2_TRACE("lfs2_rename -> %d", err); return err; } - err = lfs2_dir_drop(lfs2, &newcwd, &prevdir); + err = lfs2_dir_drop(lfs2, &newcwd, &prevdir.m); if (err) { LFS2_TRACE("lfs2_rename -> %d", err); return err; @@ -3296,7 +3391,7 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, id = 0; int err = lfs2_dir_fetch(lfs2, &cwd, lfs2->root); if (err) { - LFS2_TRACE("lfs2_getattr -> %"PRId32, err); + LFS2_TRACE("lfs2_getattr -> %d", err); return err; } } @@ -3307,7 +3402,7 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, buffer); if (tag < 0) { if (tag == LFS2_ERR_NOENT) { - LFS2_TRACE("lfs2_getattr -> %"PRId32, LFS2_ERR_NOATTR); + LFS2_TRACE("lfs2_getattr -> %d", LFS2_ERR_NOATTR); return LFS2_ERR_NOATTR; } @@ -3382,7 +3477,7 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { 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(LFS2_BLOCK_NULL / (lfs2->cfg->block_size-2*4)) + LFS2_ASSERT(4*lfs2_npw2(0xffffffff / (lfs2->cfg->block_size-2*4)) <= lfs2->cfg->block_size); // block_cycles = 0 is no longer supported. @@ -3458,9 +3553,9 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->root[1] = LFS2_BLOCK_NULL; lfs2->mlist = NULL; lfs2->seed = 0; - lfs2->gstate = (struct lfs2_gstate){0}; - lfs2->gpending = (struct lfs2_gstate){0}; - lfs2->gdelta = (struct lfs2_gstate){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 @@ -3541,7 +3636,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_superblock_tole32(&superblock); err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0), NULL}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0)}, {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); @@ -3595,7 +3690,15 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // 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), @@ -3630,7 +3733,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { 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 %"PRIu16".%"PRIu16, + LFS2_ERROR("Invalid version v%"PRIu16".%"PRIu16, major_version, minor_version); err = LFS2_ERR_INVAL; goto cleanup; @@ -3672,7 +3775,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } // has gstate? - err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gpending); + err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gstate); if (err) { goto cleanup; } @@ -3685,20 +3788,17 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } // update littlefs with gstate - lfs2->gpending.tag += !lfs2_tag_isvalid(lfs2->gpending.tag); - lfs2->gstate = lfs2->gpending; - if (lfs2_gstate_hasmove(&lfs2->gstate)) { - LFS2_DEBUG("Found move %"PRIx32" %"PRIx32" %"PRIx16, + 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_tag_id(lfs2->gstate.tag)); + lfs2->gstate.pair[1]); } + lfs2->gstate.tag += !lfs2_tag_isvalid(lfs2->gstate.tag); + lfs2->gdisk = lfs2->gstate; // setup free lookahead - lfs2->free.off = lfs2->seed % lfs2->cfg->block_size; - lfs2->free.size = 0; - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); + lfs2_alloc_reset(lfs2); LFS2_TRACE("lfs2_mount -> %d", 0); return 0; @@ -3718,10 +3818,9 @@ int lfs2_unmount(lfs2_t *lfs2) { /// Filesystem filesystem operations /// -int lfs2_fs_traverse(lfs2_t *lfs2, - int (*cb)(void *data, lfs2_block_t block), void *data) { - LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", - (void*)lfs2, (void*)(uintptr_t)cb, data); +int lfs2_fs_traverseraw(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}}; @@ -3730,7 +3829,6 @@ int lfs2_fs_traverse(lfs2_t *lfs2, if (lfs2->lfs21) { int err = lfs21_traverse(lfs2, cb, data); if (err) { - LFS2_TRACE("lfs2_fs_traverse -> %d", err); return err; } @@ -3739,11 +3837,17 @@ int lfs2_fs_traverse(lfs2_t *lfs2, } #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) { - LFS2_TRACE("lfs2_fs_traverse -> %d", err); return err; } } @@ -3751,7 +3855,6 @@ int lfs2_fs_traverse(lfs2_t *lfs2, // iterate through ids in directory int err = lfs2_dir_fetch(lfs2, &dir, dir.tail); if (err) { - LFS2_TRACE("lfs2_fs_traverse -> %d", err); return err; } @@ -3763,7 +3866,6 @@ int lfs2_fs_traverse(lfs2_t *lfs2, if (tag == LFS2_ERR_NOENT) { continue; } - LFS2_TRACE("lfs2_fs_traverse -> %d", tag); return tag; } lfs2_ctz_fromle32(&ctz); @@ -3772,9 +3874,16 @@ int lfs2_fs_traverse(lfs2_t *lfs2, err = lfs2_ctz_traverse(lfs2, NULL, &lfs2->rcache, ctz.head, ctz.size, cb, data); if (err) { - LFS2_TRACE("lfs2_fs_traverse -> %d", 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; + } + } } } } @@ -3789,7 +3898,6 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { - LFS2_TRACE("lfs2_fs_traverse -> %d", err); return err; } } @@ -3798,22 +3906,36 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->block, f->pos, cb, data); if (err) { - LFS2_TRACE("lfs2_fs_traverse -> %d", err); return err; } } } - LFS2_TRACE("lfs2_fs_traverse -> %d", 0); return 0; } +int lfs2_fs_traverse(lfs2_t *lfs2, + int (*cb)(void *data, lfs2_block_t block), void *data) { + LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", + (void*)lfs2, (void*)(uintptr_t)cb, data); + int err = lfs2_fs_traverseraw(lfs2, cb, data, true); + LFS2_TRACE("lfs2_fs_traverse -> %d", 0); + return err; +} + 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; } @@ -3856,7 +3978,14 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], // 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), @@ -3875,8 +4004,6 @@ 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_DEBUG("Relocating root %"PRIx32" %"PRIx32, - newpair[0], newpair[1]); lfs2->root[0] = newpair[0]; lfs2->root[1] = newpair[1]; } @@ -3887,6 +4014,12 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, 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 @@ -3900,8 +4033,25 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, // 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({tag, newpair})); + int err = lfs2_dir_commit(lfs2, &parent, LFS2_MKATTRS( + {LFS2_MKTAG_IF(moveid != 0x3ff, + LFS2_TYPE_DELETE, moveid, 0)}, + {tag, newpair})); lfs2_pair_fromle32(newpair); if (err) { return err; @@ -3919,9 +4069,22 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, // 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)}, {LFS2_MKTAG(LFS2_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); lfs2_pair_fromle32(newpair); if (err) { @@ -3933,40 +4096,43 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, } static void lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) { - lfs2->gpending.tag += orphans; - lfs2_gstate_xororphans(&lfs2->gdelta, &lfs2->gpending, - lfs2_gstate_hasorphans(&lfs2->gpending)); - lfs2_gstate_xororphans(&lfs2->gpending, &lfs2->gpending, - lfs2_gstate_hasorphans(&lfs2->gpending)); + 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)); } static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]) { - lfs2_gstate_xormove(&lfs2->gdelta, &lfs2->gpending, id, pair); - lfs2_gstate_xormove(&lfs2->gpending, &lfs2->gpending, id, pair); + 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; } - static int lfs2_fs_demove(lfs2_t *lfs2) { - if (!lfs2_gstate_hasmove(&lfs2->gstate)) { + if (!lfs2_gstate_hasmove(&lfs2->gdisk)) { return 0; } // Fix bad moves - LFS2_DEBUG("Fixing move %"PRIx32" %"PRIx32" %"PRIx16, - lfs2->gstate.pair[0], - lfs2->gstate.pair[1], - lfs2_tag_id(lfs2->gstate.tag)); + 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->gstate.pair); + int err = lfs2_dir_fetch(lfs2, &movedir, lfs2->gdisk.pair); if (err) { return err; } - // rely on cancel logic inside commit - err = lfs2_dir_commit(lfs2, &movedir, NULL, 0); + // 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)})); if (err) { return err; } @@ -3980,12 +4146,12 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { } // Fix any orphans - lfs2_mdir_t pdir = {.split = true}; - lfs2_mdir_t dir = {.tail = {0, 1}}; + lfs2_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs2_mdir_t dir; // iterate over all directory directory entries - while (!lfs2_pair_isnull(dir.tail)) { - int err = lfs2_dir_fetch(lfs2, &dir, dir.tail); + while (!lfs2_pair_isnull(pdir.tail)) { + int err = lfs2_dir_fetch(lfs2, &dir, pdir.tail); if (err) { return err; } @@ -4001,7 +4167,7 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { if (tag == LFS2_ERR_NOENT) { // we are an orphan - LFS2_DEBUG("Fixing orphan %"PRIx32" %"PRIx32, + LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", pdir.tail[0], pdir.tail[1]); err = lfs2_dir_drop(lfs2, &pdir, &dir); @@ -4009,7 +4175,8 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { return err; } - break; + // refetch tail + continue; } lfs2_block_t pair[2]; @@ -4022,8 +4189,9 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { if (!lfs2_pair_sync(pair, pdir.tail)) { // we have desynced - LFS2_DEBUG("Fixing half-orphan %"PRIx32" %"PRIx32, - pair[0], pair[1]); + 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( @@ -4033,16 +4201,16 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { return err; } - break; + // refetch tail + continue; } } - memcpy(&pdir, &dir, sizeof(pdir)); + pdir = dir; } // mark orphans as fixed lfs2_fs_preporphans(lfs2, -lfs2_gstate_getorphans(&lfs2->gstate)); - lfs2->gstate = lfs2->gpending; return 0; } @@ -4070,13 +4238,13 @@ static int lfs2_fs_size_count(void *p, lfs2_block_t block) { lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); lfs2_size_t size = 0; - int err = lfs2_fs_traverse(lfs2, lfs2_fs_size_count, &size); + int err = lfs2_fs_traverseraw(lfs2, lfs2_fs_size_count, &size, false); if (err) { - LFS2_TRACE("lfs2_fs_size -> %"PRId32, err); + LFS2_TRACE("lfs2_fs_size -> %d", err); return err; } - LFS2_TRACE("lfs2_fs_size -> %"PRId32, err); + LFS2_TRACE("lfs2_fs_size -> %d", err); return size; } @@ -4258,7 +4426,7 @@ static int lfs21_dir_fetch(lfs2_t *lfs2, continue; } - uint32_t crc = LFS2_BLOCK_NULL; + uint32_t crc = 0xffffffff; lfs21_dir_tole32(&test); lfs21_crc(&crc, &test, sizeof(test)); lfs21_dir_fromle32(&test); @@ -4285,7 +4453,7 @@ static int lfs21_dir_fetch(lfs2_t *lfs2, } if (!valid) { - LFS2_ERROR("Corrupted dir pair at %" PRIx32 " %" PRIx32 , + LFS2_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", tpair[0], tpair[1]); return LFS2_ERR_CORRUPT; } @@ -4473,7 +4641,8 @@ static int lfs21_mount(lfs2_t *lfs2, struct lfs21 *lfs21, } if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { - LFS2_ERROR("Invalid superblock at %d %d", 0, 1); + LFS2_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); err = LFS2_ERR_CORRUPT; goto cleanup; } @@ -4482,7 +4651,7 @@ static int lfs21_mount(lfs2_t *lfs2, struct lfs21 *lfs21, 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 %d.%d", major_version, minor_version); + LFS2_ERROR("Invalid version v%d.%d", major_version, minor_version); err = LFS2_ERR_INVAL; goto cleanup; } @@ -4608,13 +4777,15 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs21_entry_tole32(&entry1.d); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_CREATE, id, 0), NULL}, - {LFS2_MKTAG( - isdir ? LFS2_TYPE_DIR : LFS2_TYPE_REG, - id, entry1.d.nlen), name}, - {LFS2_MKTAG( - isdir ? LFS2_TYPE_DIRSTRUCT : LFS2_TYPE_CTZSTRUCT, - id, sizeof(&entry1.d.u)), &entry1.d.u})); + {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; @@ -4637,8 +4808,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_pair_tole32(dir2.pair); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 0), - dir1.d.tail})); + {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); lfs2_pair_fromle32(dir2.pair); if (err) { goto cleanup; @@ -4647,7 +4817,8 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { // Copy over first block to thread into fs. Unfortunately // if this fails there is not much we can do. - LFS2_DEBUG("Migrating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, + 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]); @@ -4693,7 +4864,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { dir2.pair[1] = dir1.pair[1]; dir2.rev = dir1.d.rev; dir2.off = sizeof(dir2.rev); - dir2.etag = LFS2_BLOCK_NULL; + dir2.etag = 0xffffffff; dir2.count = 0; dir2.tail[0] = lfs2->lfs21->root[0]; dir2.tail[1] = lfs2->lfs21->root[1]; @@ -4711,7 +4882,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_superblock_tole32(&superblock); err = lfs2_dir_commit(lfs2, &dir2, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0), NULL}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0)}, {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index 50a5e0dee6..5f4d7bd9d1 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020001 +#define LFS2_VERSION 0x00020002 #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -355,6 +355,11 @@ typedef struct lfs2_superblock { lfs2_size_t attr_max; } lfs2_superblock_t; +typedef struct lfs2_gstate { + uint32_t tag; + lfs2_block_t pair[2]; +} lfs2_gstate_t; + // The littlefs filesystem type typedef struct lfs2 { lfs2_cache_t rcache; @@ -369,10 +374,9 @@ typedef struct lfs2 { } *mlist; uint32_t seed; - struct lfs2_gstate { - uint32_t tag; - lfs2_block_t pair[2]; - } gstate, gpending, gdelta; + lfs2_gstate_t gstate; + lfs2_gstate_t gdisk; + lfs2_gstate_t gdelta; struct lfs2_free { lfs2_block_t off; diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 0f27073693..70bca717c9 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -50,31 +50,35 @@ extern "C" // Logging functions #ifdef LFS2_YES_TRACE -#define LFS2_TRACE(fmt, ...) \ - printf("lfs2_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#define LFS2_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "") #else -#define LFS2_TRACE(fmt, ...) +#define LFS2_TRACE(...) #endif #ifndef LFS2_NO_DEBUG -#define LFS2_DEBUG(fmt, ...) \ - printf("lfs2_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#define LFS2_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "") #else -#define LFS2_DEBUG(fmt, ...) +#define LFS2_DEBUG(...) #endif #ifndef LFS2_NO_WARN -#define LFS2_WARN(fmt, ...) \ - printf("lfs2_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#define LFS2_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "") #else -#define LFS2_WARN(fmt, ...) +#define LFS2_WARN(...) #endif #ifndef LFS2_NO_ERROR -#define LFS2_ERROR(fmt, ...) \ - printf("lfs2_error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#define LFS2_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "") #else -#define LFS2_ERROR(fmt, ...) +#define LFS2_ERROR(...) #endif // Runtime assertions @@ -107,7 +111,7 @@ static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) { return lfs2_aligndown(a + alignment-1, alignment); } -// Find the next smallest power of 2 less than or equal to a +// Find the smallest power of 2 greater than or equal to a static inline uint32_t lfs2_npw2(uint32_t a) { #if !defined(LFS2_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) return 32 - __builtin_clz(a-1); diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index 6da71c40f5..8cef7d4c10 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -84,6 +84,7 @@ STATIC void mp_hal_move_cursor_back(uint pos) { // snprintf needs space for the terminating null character int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos); 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); } @@ -109,6 +110,35 @@ typedef struct _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 = utf8_charlen((byte *)rl.line->buf, rl.line->len); int cont_chars = 0; @@ -161,6 +191,10 @@ int readline_process_char(int c) { 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"); @@ -255,9 +289,40 @@ int readline_process_char(int c) { 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') { @@ -365,6 +430,24 @@ delete_key: } 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); } @@ -383,6 +466,10 @@ delete_key: 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-cont_chars); diff --git a/lib/mp-readline/readline.h b/lib/mp-readline/readline.h index 9168edafa0..4658ee19e8 100644 --- a/lib/mp-readline/readline.h +++ b/lib/mp-readline/readline.h @@ -38,6 +38,7 @@ #define CHAR_CTRL_N (14) #define CHAR_CTRL_P (16) #define CHAR_CTRL_U (21) +#define CHAR_CTRL_W (23) void readline_init0(void); int readline(vstr_t *line, const char *prompt); diff --git a/lib/netutils/netutils.c b/lib/netutils/netutils.c index e82ef9270d..24a6fac99a 100644 --- a/lib/netutils/netutils.c +++ b/lib/netutils/netutils.c @@ -80,7 +80,7 @@ void netutils_parse_ipv4_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian } else if (i > 0 && s < s_top && *s == '.') { s++; } else { - mp_raise_ValueError(translate("invalid arguments")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); } } } diff --git a/lib/timeutils/timeutils.c b/lib/timeutils/timeutils.c index 6cbea31aab..5991df1dc1 100644 --- a/lib/timeutils/timeutils.c +++ b/lib/timeutils/timeutils.c @@ -159,14 +159,14 @@ mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, } void timeutils_seconds_since_epoch_to_struct_time(mp_uint_t t, timeutils_struct_time_t *tm) { - t -= EPOCH1970_EPOCH2000_DIFF_SECS; + t -= TIMEUTILS_SECONDS_1970_TO_2000; timeutils_seconds_since_2000_to_struct_time(t, tm); } mp_uint_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { mp_uint_t t = timeutils_seconds_since_2000(year, month, date, hour, minute, second); - return t + EPOCH1970_EPOCH2000_DIFF_SECS; + return t + TIMEUTILS_SECONDS_1970_TO_2000; } mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, diff --git a/lib/timeutils/timeutils.h b/lib/timeutils/timeutils.h index 1686174287..3100b95cb9 100644 --- a/lib/timeutils/timeutils.h +++ b/lib/timeutils/timeutils.h @@ -27,7 +27,9 @@ #ifndef MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H #define MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H -#define EPOCH1970_EPOCH2000_DIFF_SECS 946684800 +// The number of seconds between 1970/1/1 and 2000/1/1 is calculated using: +// time.mktime((2000,1,1,0,0,0,0,0,0)) - time.mktime((1970,1,1,0,0,0,0,0,0)) +#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800ULL) typedef struct _timeutils_struct_time_t { uint16_t tm_year; // i.e. 2014 @@ -40,6 +42,14 @@ typedef struct _timeutils_struct_time_t { uint16_t tm_yday; // 1..366 } timeutils_struct_time_t; +static inline uint64_t timeutils_seconds_since_2000_to_nanoseconds_since_1970(mp_uint_t s) { + return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; +} + +static inline mp_uint_t timeutils_seconds_since_2000_from_nanoseconds_since_1970(uint64_t ns) { + return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000; +} + bool timeutils_is_leap_year(mp_uint_t year); mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month); mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date); @@ -58,4 +68,10 @@ mp_uint_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds); +static inline uint64_t timeutils_nanoseconds_since_1970(mp_uint_t year, mp_uint_t month, + mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { + return timeutils_seconds_since_2000_to_nanoseconds_since_1970( + timeutils_seconds_since_2000(year, month, date, hour, minute, second)); +} + #endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H diff --git a/lib/utils/gchelper.h b/lib/utils/gchelper.h index 7149b6a72e..4b6ead6ba6 100644 --- a/lib/utils/gchelper.h +++ b/lib/utils/gchelper.h @@ -28,7 +28,21 @@ #include -uintptr_t gc_helper_get_sp(void); -uintptr_t gc_helper_get_regs_and_sp(uintptr_t *regs); +#if MICROPY_GCREGS_SETJMP +#include +typedef jmp_buf gc_helper_regs_t; +#else + +#if defined(__x86_64__) +typedef uintptr_t gc_helper_regs_t[6]; +#elif defined(__i386__) +typedef uintptr_t gc_helper_regs_t[4]; +#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) +typedef uintptr_t gc_helper_regs_t[10]; +#endif + +#endif + +void gc_helper_collect_regs_and_stack(void); #endif // MICROPY_INCLUDED_LIB_UTILS_GCHELPER_H diff --git a/lib/utils/gchelper_generic.c b/lib/utils/gchelper_generic.c new file mode 100644 index 0000000000..9750e8b0c8 --- /dev/null +++ b/lib/utils/gchelper_generic.c @@ -0,0 +1,156 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mpstate.h" +#include "py/gc.h" +#include "lib/utils/gchelper.h" + +#if MICROPY_ENABLE_GC + +// Even if we have specific support for an architecture, it is +// possible to force use of setjmp-based implementation. +#if !MICROPY_GCREGS_SETJMP + +// We capture here callee-save registers, i.e. ones which may contain +// interesting values held there by our callers. It doesn't make sense +// to capture caller-saved registers, because they, well, put on the +// stack already by the caller. +#if defined(__x86_64__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long rbx asm ("rbx"); + register long rbp asm ("rbp"); + register long r12 asm ("r12"); + register long r13 asm ("r13"); + register long r14 asm ("r14"); + register long r15 asm ("r15"); + #ifdef __clang__ + // TODO: + // This is dirty workaround for Clang. It tries to get around + // uncompliant (wrt to GCC) behavior of handling register variables. + // Application of this patch here is random, and done only to unbreak + // MacOS build. Better, cross-arch ways to deal with Clang issues should + // be found. + asm ("" : "=r" (rbx)); + asm ("" : "=r" (rbp)); + asm ("" : "=r" (r12)); + asm ("" : "=r" (r13)); + asm ("" : "=r" (r14)); + asm ("" : "=r" (r15)); + #endif + arr[0] = rbx; + arr[1] = rbp; + arr[2] = r12; + arr[3] = r13; + arr[4] = r14; + arr[5] = r15; +} + +#elif defined(__i386__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long ebx asm ("ebx"); + register long esi asm ("esi"); + register long edi asm ("edi"); + register long ebp asm ("ebp"); + #ifdef __clang__ + // TODO: + // This is dirty workaround for Clang. It tries to get around + // uncompliant (wrt to GCC) behavior of handling register variables. + // Application of this patch here is random, and done only to unbreak + // MacOS build. Better, cross-arch ways to deal with Clang issues should + // be found. + asm ("" : "=r" (ebx)); + asm ("" : "=r" (esi)); + asm ("" : "=r" (edi)); + asm ("" : "=r" (ebp)); + #endif + arr[0] = ebx; + arr[1] = esi; + arr[2] = edi; + arr[3] = ebp; +} + +#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) + +// Fallback implementation, prefer gchelper_m0.s or gchelper_m3.s + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long r4 asm ("r4"); + register long r5 asm ("r5"); + register long r6 asm ("r6"); + register long r7 asm ("r7"); + register long r8 asm ("r8"); + register long r9 asm ("r9"); + register long r10 asm ("r10"); + register long r11 asm ("r11"); + register long r12 asm ("r12"); + register long r13 asm ("r13"); + arr[0] = r4; + arr[1] = r5; + arr[2] = r6; + arr[3] = r7; + arr[4] = r8; + arr[5] = r9; + arr[6] = r10; + arr[7] = r11; + arr[8] = r12; + arr[9] = r13; +} + +#else + +#error "Architecture not supported for gc_helper_get_regs. Set MICROPY_GCREGS_SETJMP to use the fallback implementation." + +#endif + +#else // !MICROPY_GCREGS_SETJMP + +// Even if we have specific support for an architecture, it is +// possible to force use of setjmp-based implementation. + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + setjmp(arr); +} + +#endif // MICROPY_GCREGS_SETJMP + +// Explicitly mark this as noinline to make sure the regs variable +// is effectively at the top of the stack: otherwise, in builds where +// LTO is enabled and a lot of inlining takes place we risk a stack +// layout where regs is lower on the stack than pointers which have +// just been allocated but not yet marked, and get incorrectly sweeped. +MP_NOINLINE void gc_helper_collect_regs_and_stack(void) { + gc_helper_regs_t regs; + gc_helper_get_regs(regs); + // GC stack (and regs because we captured them) + void **regs_ptr = (void **)(void *)®s; + gc_collect_root(regs_ptr, ((uintptr_t)MP_STATE_THREAD(stack_top) - (uintptr_t)®s) / sizeof(uintptr_t)); +} + +#endif // MICROPY_ENABLE_GC diff --git a/lib/utils/gchelper_m3.s b/lib/utils/gchelper_m3.s index 3aac0dedfd..5220fa0883 100644 --- a/lib/utils/gchelper_m3.s +++ b/lib/utils/gchelper_m3.s @@ -31,18 +31,6 @@ .section .text .align 2 - .global gc_helper_get_sp - .type gc_helper_get_sp, %function - -@ uint gc_helper_get_sp(void) -gc_helper_get_sp: - @ return the sp - mov r0, sp - bx lr - - .size gc_helper_get_sp, .-gc_helper_get_sp - - .global gc_helper_get_regs_and_sp .type gc_helper_get_regs_and_sp, %function diff --git a/lib/utils/gchelper_native.c b/lib/utils/gchelper_native.c new file mode 100644 index 0000000000..6bf386b519 --- /dev/null +++ b/lib/utils/gchelper_native.c @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mpstate.h" +#include "py/gc.h" +#include "lib/utils/gchelper.h" + +#if MICROPY_ENABLE_GC + +// provided by gchelper_*.s +uintptr_t gc_helper_get_regs_and_sp(uintptr_t *regs); + +MP_NOINLINE void gc_helper_collect_regs_and_stack(void) { + // get the registers and the sp + gc_helper_regs_t regs; + uintptr_t sp = gc_helper_get_regs_and_sp(regs); + + // trace the stack, including the registers (since they live on the stack in this function) + gc_collect_root((void **)sp, ((uint32_t)MP_STATE_THREAD(stack_top) - sp) / sizeof(uint32_t)); +} + +#endif diff --git a/lib/utils/interrupt_char.c b/lib/utils/interrupt_char.c index 372479f3a7..ea2901a143 100644 --- a/lib/utils/interrupt_char.c +++ b/lib/utils/interrupt_char.c @@ -32,9 +32,6 @@ int mp_interrupt_char = -1; void mp_hal_set_interrupt_char(int c) { - if (c != -1) { - mp_obj_exception_clear_traceback(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); - } mp_interrupt_char = c; } diff --git a/lib/utils/mpirq.c b/lib/utils/mpirq.c index dd0c220059..663be18224 100644 --- a/lib/utils/mpirq.c +++ b/lib/utils/mpirq.c @@ -31,12 +31,14 @@ #include "py/gc.h" #include "lib/utils/mpirq.h" +#if MICROPY_ENABLE_SCHEDULER + /****************************************************************************** DECLARE PUBLIC DATA ******************************************************************************/ const mp_arg_t mp_irq_init_args[] = { - { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_trigger, MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, }; @@ -62,8 +64,10 @@ mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent) { void mp_irq_handler(mp_irq_obj_t *self) { if (self->handler != mp_const_none) { if (self->ishard) { - // When executing code within a handler we must lock the GC to prevent - // any memory allocations. + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); gc_lock(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -77,6 +81,7 @@ void mp_irq_handler(mp_irq_obj_t *self) { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } gc_unlock(); + mp_sched_unlock(); } else { // Schedule call to user function mp_sched_schedule(self->handler, self->parent); @@ -122,3 +127,5 @@ const mp_obj_type_t mp_irq_type = { .call = mp_irq_call, .locals_dict = (mp_obj_dict_t *)&mp_irq_locals_dict, }; + +#endif // MICROPY_ENABLE_SCHEDULER diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index ff2d7e55a3..eff68dd766 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -46,7 +46,10 @@ 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) #define EXEC_FLAG_ALLOW_DEBUGGING (2) @@ -62,7 +65,9 @@ STATIC bool repl_display_debugging_info = 0; // 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, int exec_flags, pyexec_result_t *result) { int ret = 0; + #if MICROPY_REPL_INFO uint32_t start = 0; + #endif // by default a SystemExit exception returns 0 pyexec_system_exit = 0; @@ -97,7 +102,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input // Clear the parse tree because it has a heap pointer we don't need anymore. *((uint32_t volatile *)&parse_tree.chunk) = 0; #else - mp_raise_msg(&mp_type_RuntimeError, translate("script compilation not supported")); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); #endif } @@ -110,11 +115,12 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input // 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 - // Handle any ctrl-c interrupt that arrived just in time - mp_handle_pending(); + mp_handle_pending(true); // handle any pending exceptions (and any callbacks) nlr_pop(); ret = 0; if (exec_flags & EXEC_FLAG_PRINT_EOF) { @@ -122,8 +128,8 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input } } else { // uncaught exception - // FIXME it could be that an interrupt happens just before we disable it here mp_hal_set_interrupt_char(-1); // disable interrupt + mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) // print EOF after normal output if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); @@ -160,6 +166,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input } } + #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 @@ -179,6 +186,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input gc_dump_info(); #endif } + #endif if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); @@ -362,7 +370,7 @@ STATIC int pyexec_friendly_repl_process_char(int c) { } 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, NULL); + 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; } @@ -612,9 +620,10 @@ int pyexec_frozen_module(const char *name, pyexec_result_t *result) { } #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 diff --git a/lib/utils/pyexec.h b/lib/utils/pyexec.h index 9baef2b9fe..6ec2e629f0 100644 --- a/lib/utils/pyexec.h +++ b/lib/utils/pyexec.h @@ -29,8 +29,8 @@ #include "py/obj.h" typedef enum { - PYEXEC_MODE_RAW_REPL, PYEXEC_MODE_FRIENDLY_REPL, + PYEXEC_MODE_RAW_REPL, } pyexec_mode_kind_t; typedef struct { @@ -59,8 +59,10 @@ int pyexec_frozen_module(const char *name, pyexec_result_t *result); void pyexec_event_repl_init(void); int pyexec_event_repl_process_char(int c); extern uint8_t pyexec_repl_active; -mp_obj_t pyb_set_repl_info(mp_obj_t o_value); +#if MICROPY_REPL_INFO +mp_obj_t pyb_set_repl_info(mp_obj_t o_value); MP_DECLARE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj); +#endif #endif // MICROPY_INCLUDED_LIB_UTILS_PYEXEC_H diff --git a/lib/utils/sys_stdio_mphal.c b/lib/utils/sys_stdio_mphal.c index 376e258825..f41c0a9051 100644 --- a/lib/utils/sys_stdio_mphal.c +++ b/lib/utils/sys_stdio_mphal.c @@ -86,13 +86,8 @@ STATIC mp_uint_t stdio_write(mp_obj_t self_in, const void *buf, mp_uint_t size, } STATIC mp_uint_t stdio_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in); - (void)self; - - // For now, pretend we actually flush the stdio stream. - if (request == MP_STREAM_FLUSH) { - return 0; - } else if (request == MP_STREAM_POLL) { + (void)self_in; + if (request == MP_STREAM_POLL) { return mp_hal_stdio_poll(arg); } else { *errcode = MP_EINVAL; diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 8c5a70bf08..2db71c8c47 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -70,7 +70,7 @@ msgstr "" msgid "%q in use" msgstr "" -#: extmod/moductypes.c ports/atmel-samd/common-hal/pulseio/PulseIn.c +#: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nrf/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/pulseio/PulseIn.c @@ -80,7 +80,7 @@ msgid "%q index out of range" msgstr "" #: py/obj.c -msgid "%q indices must be integers, not %q" +msgid "%q indices must be integers, not %s" msgstr "" #: shared-bindings/vectorio/Polygon.c @@ -129,26 +129,10 @@ msgstr "" msgid "'%q' argument required" msgstr "" -#: py/runtime.c -msgid "'%q' object cannot assign attribute '%q'" -msgstr "" - #: py/proto.c msgid "'%q' object does not support '%q'" msgstr "" -#: py/obj.c -msgid "'%q' object does not support item assignment" -msgstr "" - -#: py/obj.c -msgid "'%q' object does not support item deletion" -msgstr "" - -#: py/runtime.c -msgid "'%q' object has no attribute '%q'" -msgstr "" - #: py/runtime.c msgid "'%q' object is not an iterator" msgstr "" @@ -161,10 +145,6 @@ msgstr "" msgid "'%q' object is not iterable" msgstr "" -#: py/obj.c -msgid "'%q' object is not subscriptable" -msgstr "" - #: py/emitinlinethumb.c py/emitinlinextensa.c #, c-format msgid "'%s' expects a label" @@ -207,12 +187,31 @@ msgstr "" #: py/emitinlinextensa.c #, c-format -msgid "'%s' integer %d is not within range %d..%d" +msgid "'%s' integer %d isn't within range %d..%d" msgstr "" #: py/emitinlinethumb.c #, c-format -msgid "'%s' integer 0x%x does not fit in mask 0x%x" +msgid "'%s' integer 0x%x doesn't fit in mask 0x%x" +msgstr "" + +#: py/obj.c +#, c-format +msgid "'%s' object doesn't support item assignment" +msgstr "" + +#: py/obj.c +#, c-format +msgid "'%s' object doesn't support item deletion" +msgstr "" + +#: py/runtime.c +msgid "'%s' object has no attribute '%q'" +msgstr "" + +#: py/obj.c +#, c-format +msgid "'%s' object isn't subscriptable" msgstr "" #: py/objstr.c @@ -664,18 +663,10 @@ msgstr "" msgid "Cannot specify RTS or CTS in RS485 mode" msgstr "" -#: py/objslice.c -msgid "Cannot subclass slice" -msgstr "" - #: shared-module/bitbangio/SPI.c msgid "Cannot transfer without MOSI and MISO pins." msgstr "" -#: extmod/moductypes.c -msgid "Cannot unambiguously get sizeof scalar" -msgstr "" - #: shared-bindings/pwmio/PWMOut.c msgid "Cannot vary frequency on a timer that is already in use" msgstr "" @@ -1382,14 +1373,6 @@ msgstr "" msgid "Layer must be a Group or TileGrid subclass." msgstr "" -#: py/objslice.c -msgid "Length must be an int" -msgstr "" - -#: py/objslice.c -msgid "Length must be non-negative" -msgstr "" - #: ports/esp32s2/bindings/espidf/__init__.c ports/esp32s2/esp_error.c msgid "MAC address was invalid" msgstr "" @@ -2404,6 +2387,10 @@ msgstr "" msgid "arg is an empty sequence" msgstr "" +#: py/objobject.c +msgid "arg must be user-type" +msgstr "" + #: extmod/ulab/code/numpy/numerical/numerical.c msgid "argsort argument must be an ndarray" msgstr "" @@ -2416,6 +2403,10 @@ msgstr "" msgid "argument has wrong type" msgstr "" +#: py/compile.c +msgid "argument name reused" +msgstr "" + #: py/argcheck.c shared-bindings/_stage/__init__.c #: shared-bindings/digitalio/DigitalInOut.c shared-bindings/gamepad/GamePad.c msgid "argument num/types mismatch" @@ -2569,6 +2560,10 @@ msgstr "" msgid "can't assign to expression" msgstr "" +#: extmod/moduasyncio.c +msgid "can't cancel self" +msgstr "" + #: py/obj.c py/objint.c shared-bindings/i2cperipheral/I2CPeripheral.c #: shared-module/_pixelbuf/PixelBuf.c msgid "can't convert %q to %q" @@ -2578,6 +2573,11 @@ msgstr "" msgid "can't convert %q to int" msgstr "" +#: py/obj.c +#, c-format +msgid "can't convert %s to complex" +msgstr "" + #: py/objstr.c msgid "can't convert '%q' object to %q implicitly" msgstr "" @@ -2586,6 +2586,10 @@ msgstr "" msgid "can't convert to %q" msgstr "" +#: py/obj.c +msgid "can't convert to complex" +msgstr "" + #: py/runtime.c msgid "can't convert to int" msgstr "" @@ -2684,6 +2688,10 @@ msgstr "" msgid "cannot perform relative import" msgstr "" +#: extmod/moductypes.c +msgid "cannot unambiguously get sizeof scalar" +msgstr "" + #: py/emitnative.c msgid "casting" msgstr "" @@ -2732,6 +2740,10 @@ msgstr "" msgid "color should be an int" msgstr "" +#: py/emitnative.c +msgid "comparison of int and uint" +msgstr "" + #: py/objcomplex.c msgid "complex division by zero" msgstr "" @@ -2834,7 +2846,15 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: py/modmath.c py/objfloat.c py/objint_longlong.c py/objint_mpz.c py/runtime.c +#: py/emitnative.c +msgid "div/mod not implemented for uint" +msgstr "" + +#: py/objfloat.c py/objint_mpz.c +msgid "divide by zero" +msgstr "" + +#: py/modmath.c py/objint_longlong.c py/runtime.c #: shared-bindings/math/__init__.c msgid "division by zero" msgstr "" @@ -2843,7 +2863,7 @@ msgstr "" msgid "empty" msgstr "" -#: extmod/moduheapq.c extmod/modutimeq.c +#: extmod/moduasyncio.c extmod/moduheapq.c extmod/modutimeq.c msgid "empty heap" msgstr "" @@ -3077,10 +3097,6 @@ msgstr "" msgid "import * not at module level" msgstr "" -#: py/persistentcode.c -msgid "incompatible .mpy file" -msgstr "" - #: py/persistentcode.c msgid "incompatible native .mpy architecture" msgstr "" @@ -3217,10 +3233,6 @@ msgstr "" msgid "invalid bits_per_pixel %d, must be, 1, 4, 8, 16, 24, or 32" msgstr "" -#: py/compile.c -msgid "invalid decorator" -msgstr "" - #: shared-bindings/bitmaptools/__init__.c #, c-format msgid "invalid element size %d for bits_per_pixel %d\n" @@ -3427,10 +3439,6 @@ msgstr "" msgid "name not defined" msgstr "" -#: py/compile.c -msgid "name reused for argument" -msgstr "" - #: py/emitnative.c msgid "native yield" msgstr "" @@ -3485,7 +3493,7 @@ msgstr "" msgid "no response from SD card" msgstr "" -#: py/runtime.c +#: py/objobject.c py/runtime.c msgid "no such attribute" msgstr "" @@ -3538,15 +3546,16 @@ msgid "object " msgstr "" #: py/obj.c -msgid "object '%q' is not a tuple or list" +#, c-format +msgid "object '%s' isn't a tuple or list" msgstr "" #: py/obj.c -msgid "object does not support item assignment" +msgid "object doesn't support item assignment" msgstr "" #: py/obj.c -msgid "object does not support item deletion" +msgid "object doesn't support item deletion" msgstr "" #: py/obj.c @@ -3554,7 +3563,7 @@ msgid "object has no len" msgstr "" #: py/obj.c -msgid "object is not subscriptable" +msgid "object isn't subscriptable" msgstr "" #: py/runtime.c @@ -3574,7 +3583,8 @@ msgid "object not iterable" msgstr "" #: py/obj.c -msgid "object of type '%q' has no len()" +#, c-format +msgid "object of type '%s' has no len()" msgstr "" #: py/obj.c @@ -3844,6 +3854,10 @@ msgstr "" msgid "script compilation not supported" msgstr "" +#: py/nativeglue.c +msgid "set unsupported" +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "shape must be a tuple" msgstr "" @@ -3872,12 +3886,12 @@ msgstr "" msgid "sleep length must be non-negative" msgstr "" -#: extmod/ulab/code/ndarray.c +#: extmod/ulab/code/ndarray.c py/objslice.c msgid "slice step can't be zero" msgstr "" -#: py/objslice.c py/sequence.c -msgid "slice step cannot be zero" +#: py/nativeglue.c +msgid "slice unsupported" msgstr "" #: py/objint.c py/sequence.c @@ -3941,7 +3955,11 @@ msgid "string not supported; use bytes or bytearray" msgstr "" #: extmod/moductypes.c -msgid "struct: cannot index" +msgid "struct: can't index" +msgstr "" + +#: extmod/moductypes.c +msgid "struct: index out of range" msgstr "" #: extmod/moductypes.c diff --git a/mpy-cross/gccollect.c b/mpy-cross/gccollect.c index 117a903869..2172bb36b7 100644 --- a/mpy-cross/gccollect.c +++ b/mpy-cross/gccollect.c @@ -8,120 +8,13 @@ #include "py/mpstate.h" #include "py/gc.h" +#include "lib/utils/gchelper.h" + #if MICROPY_ENABLE_GC -// Even if we have specific support for an architecture, it is -// possible to force use of setjmp-based implementation. -#if !MICROPY_GCREGS_SETJMP - -// We capture here callee-save registers, i.e. ones which may contain -// interesting values held there by our callers. It doesn't make sense -// to capture caller-saved registers, because they, well, put on the -// stack already by the caller. -#if defined(__x86_64__) -typedef mp_uint_t regs_t[6]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long rbx asm ("rbx"); - register long rbp asm ("rbp"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); - register long r14 asm ("r14"); - register long r15 asm ("r15"); - #ifdef __clang__ - // TODO: - // This is dirty workaround for Clang. It tries to get around - // uncompliant (wrt to GCC) behavior of handling register variables. - // Application of this patch here is random, and done only to unbreak - // MacOS build. Better, cross-arch ways to deal with Clang issues should - // be found. - asm ("" : "=r" (rbx)); - asm ("" : "=r" (rbp)); - asm ("" : "=r" (r12)); - asm ("" : "=r" (r13)); - asm ("" : "=r" (r14)); - asm ("" : "=r" (r15)); - #endif - arr[0] = rbx; - arr[1] = rbp; - arr[2] = r12; - arr[3] = r13; - arr[4] = r14; - arr[5] = r15; -} - -#elif defined(__i386__) - -typedef mp_uint_t regs_t[4]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long ebx asm ("ebx"); - register long esi asm ("esi"); - register long edi asm ("edi"); - register long ebp asm ("ebp"); - arr[0] = ebx; - arr[1] = esi; - arr[2] = edi; - arr[3] = ebp; -} - -#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) - -typedef mp_uint_t regs_t[10]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long r4 asm ("r4"); - register long r5 asm ("r5"); - register long r6 asm ("r6"); - register long r7 asm ("r7"); - register long r8 asm ("r8"); - register long r9 asm ("r9"); - register long r10 asm ("r10"); - register long r11 asm ("r11"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); - arr[0] = r4; - arr[1] = r5; - arr[2] = r6; - arr[3] = r7; - arr[4] = r8; - arr[5] = r9; - arr[6] = r10; - arr[7] = r11; - arr[8] = r12; - arr[9] = r13; -} - -#else - -// If we don't have architecture-specific optimized support, -// just fall back to setjmp-based implementation. -#undef MICROPY_GCREGS_SETJMP -#define MICROPY_GCREGS_SETJMP (1) - -#endif // Arch-specific selection -#endif // !MICROPY_GCREGS_SETJMP - -// If MICROPY_GCREGS_SETJMP was requested explicitly, or if -// we enabled it as a fallback above. -#if MICROPY_GCREGS_SETJMP -#include - -typedef jmp_buf regs_t; - -STATIC void gc_helper_get_regs(regs_t arr) { - setjmp(arr); -} - -#endif // MICROPY_GCREGS_SETJMP - void gc_collect(void) { gc_collect_start(); - regs_t regs; - gc_helper_get_regs(regs); - // GC stack (and regs because we captured them) - void **regs_ptr = (void **)(void *)®s; - gc_collect_root(regs_ptr, ((mp_uint_t)MP_STATE_THREAD(stack_top) - (mp_uint_t)®s) / sizeof(mp_uint_t)); + gc_helper_collect_regs_and_stack(); gc_collect_end(); } diff --git a/mpy-cross/main.c b/mpy-cross/main.c index d16ebc4a05..815bd249be 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -26,7 +26,7 @@ mp_uint_t mp_verbose_flag = 0; // Make it larger on a 64 bit machine, because pointers are larger. long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4); -STATIC void stderr_print_strn(void *env, const char *str, mp_uint_t len) { +STATIC void stderr_print_strn(void *env, const char *str, size_t len) { (void)env; ssize_t dummy = write(STDERR_FILENO, str, len); (void)dummy; @@ -324,7 +324,7 @@ uint mp_import_stat(const char *path) { } void nlr_jump_fail(void *val) { - printf("FATAL: uncaught NLR %p\n", val); + fprintf(stderr, "FATAL: uncaught NLR %p\n", val); exit(1); } diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 724c0e001e..d5f77494e4 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -48,14 +48,9 @@ #define MICROPY_PY_BUILTINS_STR_UNICODE (1) -// Define to 1 to use undertested inefficient GC helper implementation -// (if more efficient arch-specific one is not available). -#ifndef MICROPY_GCREGS_SETJMP - #ifdef __mips__ - #define MICROPY_GCREGS_SETJMP (1) - #else - #define MICROPY_GCREGS_SETJMP (0) - #endif +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +// Fall back to setjmp() implementation for discovery of GC pointers in registers. +#define MICROPY_GCREGS_SETJMP (1) #endif #define MICROPY_PY___FILE__ (0) diff --git a/mpy-cross/mpy-cross.mk b/mpy-cross/mpy-cross.mk index c6ef9d8165..f84afef1da 100644 --- a/mpy-cross/mpy-cross.mk +++ b/mpy-cross/mpy-cross.mk @@ -69,6 +69,7 @@ endif SRC_C += \ main.c \ gccollect.c \ + lib/utils/gchelper_generic.c \ supervisor/stub/safe_mode.c \ supervisor/stub/stack.c \ supervisor/shared/translate.c diff --git a/mpy-cross/mpy-cross.vcxproj b/mpy-cross/mpy-cross.vcxproj index 537714a1df..7151ab5e99 100644 --- a/mpy-cross/mpy-cross.vcxproj +++ b/mpy-cross/mpy-cross.vcxproj @@ -89,6 +89,9 @@ + + MICROPY_GCREGS_SETJMP + diff --git a/ports/atmel-samd/qstrdefsport.h b/ports/atmel-samd/qstrdefsport.h index 3ba897069b..00d3e2ae3c 100644 --- a/ports/atmel-samd/qstrdefsport.h +++ b/ports/atmel-samd/qstrdefsport.h @@ -1 +1,2 @@ // qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/cxd56/qstrdefsport.h b/ports/cxd56/qstrdefsport.h index 3ba897069b..00d3e2ae3c 100644 --- a/ports/cxd56/qstrdefsport.h +++ b/ports/cxd56/qstrdefsport.h @@ -1 +1,2 @@ // qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/esp32s2/qstrdefsport.h b/ports/esp32s2/qstrdefsport.h index 3ba897069b..00d3e2ae3c 100644 --- a/ports/esp32s2/qstrdefsport.h +++ b/ports/esp32s2/qstrdefsport.h @@ -1 +1,2 @@ // qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/litex/qstrdefsport.h b/ports/litex/qstrdefsport.h index 3ba897069b..00d3e2ae3c 100644 --- a/ports/litex/qstrdefsport.h +++ b/ports/litex/qstrdefsport.h @@ -1 +1,2 @@ // qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/mimxrt10xx/qstrdefsport.h b/ports/mimxrt10xx/qstrdefsport.h index 3ba897069b..00d3e2ae3c 100644 --- a/ports/mimxrt10xx/qstrdefsport.h +++ b/ports/mimxrt10xx/qstrdefsport.h @@ -1 +1,2 @@ // qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/unix/mpconfigport_nanbox.h b/ports/stm/mpconfigport_nanbox.h similarity index 100% rename from ports/unix/mpconfigport_nanbox.h rename to ports/stm/mpconfigport_nanbox.h diff --git a/ports/unix/.gitignore b/ports/unix/.gitignore index 7179e7bde4..6745218688 100644 --- a/ports/unix/.gitignore +++ b/ports/unix/.gitignore @@ -1,8 +1,4 @@ micropython -micropython_fast -micropython_minimal -micropython_coverage -micropython_nanbox -micropython_freedos* +micropython-* *.py *.gcov diff --git a/ports/unix/Makefile b/ports/unix/Makefile index f34472bf4c..b414377fa3 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -1,11 +1,24 @@ --include mpconfigport.mk +# Select the variant to build for. +VARIANT ?= standard + +# If the build directory is not given, make it reflect the variant name. +BUILD ?= build-$(VARIANT) + +VARIANT_DIR ?= variants/$(VARIANT) +ifeq ($(wildcard $(VARIANT_DIR)/.),) +$(error Invalid VARIANT specified: $(VARIANT_DIR)) +endif + include ../../py/mkenv.mk +-include mpconfigport.mk +include $(VARIANT_DIR)/mpconfigvariant.mk # define main target PROG = micropython # qstr definitions (must come before including py.mk) QSTR_DEFS = qstrdefsport.h +QSTR_GLOBAL_DEPENDENCIES = $(VARIANT_DIR)/mpconfigvariant.h # OS name, for simple autoconfig UNAME_S := $(shell uname -s) @@ -21,15 +34,23 @@ INC += -I$(BUILD) # compiler settings CWARN = -Wall -Werror -CWARN += -Wpointer-arith -Wuninitialized -CFLAGS = $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) $(CFLAGS_EXTRA) +CWARN += -Wpointer-arith -Wuninitialized -Wdouble-promotion -Wsign-compare -Wfloat-conversion +CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) # Debugging/Optimization ifdef DEBUG -CFLAGS += -g -COPT = -O0 +COPT ?= -O0 else -COPT = -Os -fdata-sections -ffunction-sections -DNDEBUG +COPT ?= -Os +COPT += -fdata-sections -ffunction-sections +COPT += -DNDEBUG +endif + +# Always enable symbols -- They're occasionally useful, and don't make it into the +# final .bin/.hex/.dfu so the extra size doesn't matter. +CFLAGS += -g + +ifndef DEBUG # _FORTIFY_SOURCE is a feature in gcc/glibc which is intended to provide extra # security for detecting buffer overflows. Some distros (Ubuntu at the very least) # have it enabled by default. @@ -73,7 +94,7 @@ else # Use gcc syntax for map file LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections endif -LDFLAGS = -Lbuild $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) +LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) # Flags to link with pthread library LIBPTHREAD = -lpthread @@ -142,7 +163,6 @@ SRC_C += \ unix_mphal.c \ mpthreadport.c \ input.c \ - file.c \ modos.c \ moduos_vfs.c \ modtime.c \ @@ -155,25 +175,22 @@ SRC_C += \ supervisor/stub/serial.c \ supervisor/stub/stack.c \ supervisor/shared/translate.c \ - $(SRC_MOD) + $(SRC_MOD) \ + $(wildcard $(VARIANT_DIR)/*.c) -LIB_SRC_C = $(addprefix lib/,\ +LIB_SRC_C += $(addprefix lib/,\ $(LIB_SRC_C_EXTRA) \ timeutils/timeutils.c \ - ) - -# FatFS VFS support -LIB_SRC_C += $(addprefix lib/,\ - oofatfs/ff.c \ - oofatfs/ffunicode.c \ + utils/gchelper_generic.c \ ) OBJ = $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(LIB_SRC_C) +SRC_QSTR += $(SRC_C) $(LIB_SRC_C) $(EXTMOD_SRC_C) # Append any auto-generated sources that are needed by sources listed in # SRC_QSTR SRC_QSTR_AUTO_DEPS += @@ -186,85 +203,32 @@ CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs MPY_CROSS_FLAGS += -mcache-lookup-bc endif +ifeq ($(MICROPY_FORCE_32BIT),1) +RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-mcache-lookup-bc -march=x86' +else +RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-mcache-lookup-bc' +endif + include $(TOP)/py/mkrules.mk -.PHONY: test +.PHONY: test test_full test: $(PROG) $(TOP)/tests/run-tests $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --auto-jobs -# install micropython in /usr/local/bin -TARGET = micropython -PREFIX = $(DESTDIR)/usr/local -BINDIR = $(PREFIX)/bin - -install: micropython - install -d $(BINDIR) - install $(TARGET) $(BINDIR)/$(TARGET) - -# uninstall micropython -uninstall: - -rm $(BINDIR)/$(TARGET) - -# build synthetically fast interpreter for benchmarking -fast: - $(MAKE) COPT="-O2 -DNDEBUG -fno-crossjumping" CFLAGS_EXTRA='-DMP_CONFIGFILE=""' BUILD=build-fast PROG=micropython_fast - -# build a minimal interpreter -minimal: - $(MAKE) COPT="-Os -DNDEBUG" CFLAGS_EXTRA='-DMP_CONFIGFILE=""' \ - BUILD=build-minimal PROG=micropython_minimal \ - MICROPY_PY_BTREE=0 MICROPY_PY_FFI=0 MICROPY_PY_SOCKET=0 MICROPY_PY_THREAD=0 \ - MICROPY_PY_TERMIOS=0 MICROPY_PY_USSL=0 \ - MICROPY_USE_READLINE=0 - -# build interpreter with nan-boxing as object model -nanbox: - $(MAKE) \ - CFLAGS_EXTRA='-DMP_CONFIGFILE=""' \ - BUILD=build-nanbox \ - PROG=micropython_nanbox \ - MICROPY_FORCE_32BIT=1 - -freedos: - $(MAKE) \ - CC=i586-pc-msdosdjgpp-gcc \ - STRIP=i586-pc-msdosdjgpp-strip \ - SIZE=i586-pc-msdosdjgpp-size \ - CFLAGS_EXTRA='-DMP_CONFIGFILE="" -DMICROPY_NLR_SETJMP -Dtgamma=gamma -DMICROPY_EMIT_X86=0 -DMICROPY_NO_ALLOCA=1 -DMICROPY_PY_USELECT_POSIX=0' \ - BUILD=build-freedos \ - PROG=micropython_freedos \ - MICROPY_PY_SOCKET=0 \ - MICROPY_PY_FFI=0 \ - MICROPY_PY_JNI=0 \ - MICROPY_PY_BTREE=0 \ - MICROPY_PY_THREAD=0 \ - MICROPY_PY_USSL=0 - -# build an interpreter for coverage testing and do the testing -coverage: - $(MAKE) \ - COPT="-O0" CFLAGS_EXTRA='$(CFLAGS_EXTRA) -DMP_CONFIGFILE="" \ - -fprofile-arcs -ftest-coverage \ - -Wformat -Wmissing-declarations -Wmissing-prototypes -Wsign-compare \ - -Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \ - -DMICROPY_UNIX_COVERAGE' \ - LDFLAGS_EXTRA='-fprofile-arcs -ftest-coverage' \ - MICROPY_VFS_FAT=1 MICROPY_VFS_LFS1=1 MICROPY_VFS_LFS2=1 \ - FROZEN_DIR=coverage-frzstr FROZEN_MPY_DIR=coverage-frzmpy \ - BUILD=build-coverage PROG=micropython_coverage - -coverage_test: coverage +test_full: $(PROG) $(TOP)/tests/run-tests $(eval DIRNAME=ports/$(notdir $(CURDIR))) - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/micropython_coverage ./run-tests - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/micropython_coverage ./run-tests -d thread - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/micropython_coverage ./run-tests --emit native - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/micropython_coverage ./run-tests --via-mpy -d basics float micropython - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/micropython_coverage ./run-tests --via-mpy --emit native -d basics float micropython - cat $(TOP)/tests/basics/0prelim.py | ./micropython_coverage | grep -q 'abc' - gcov -o build-coverage/py $(TOP)/py/*.c - gcov -o build-coverage/extmod $(TOP)/extmod/*.c + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests -d thread + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --emit native + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + cat $(TOP)/tests/basics/0prelim.py | ./$(PROG) | grep -q 'abc' + +test_gcov: test_full + gcov -o $(BUILD)/py $(TOP)/py/*.c + gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c coverage_clean: $(MAKE) V=2 BUILD=build-coverage PROG=micropython_coverage clean @@ -309,4 +273,14 @@ $(TOP)/lib/axtls/README: @echo "You cloned without --recursive, fetching submodules for you." (cd $(TOP); git submodule update --init --recursive) +PREFIX = /usr/local +BINDIR = $(DESTDIR)$(PREFIX)/bin + +install: $(PROG) + install -d $(BINDIR) + install $(PROG) $(BINDIR)/$(PROG) + +uninstall: + -rm $(BINDIR)/$(PROG) + $(BUILD)/supervisor/shared/translate.o: $(HEADER_BUILD)/qstrdefs.generated.h diff --git a/ports/unix/btstack_usb.c b/ports/unix/btstack_usb.c new file mode 100644 index 0000000000..ab6a49f39a --- /dev/null +++ b/ports/unix/btstack_usb.c @@ -0,0 +1,191 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/btstack/modbluetooth_btstack.h" + +#if !MICROPY_PY_THREAD +#error Unix btstack requires MICROPY_PY_THREAD +#endif + +STATIC const useconds_t USB_POLL_INTERVAL_US = 1000; + +STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; + +STATIC uint8_t local_addr[6] = {0}; +STATIC uint8_t static_address[6] = {0}; +STATIC volatile bool have_addr = false; +STATIC bool using_static_address = false; + +STATIC btstack_packet_callback_registration_t hci_event_callback_registration; + +STATIC void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + if (packet_type != HCI_EVENT_PACKET) { + return; + } + switch (hci_event_packet_get_type(packet)) { + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) { + return; + } + gap_local_bd_addr(local_addr); + if (using_static_address) { + memcpy(local_addr, static_address, sizeof(local_addr)); + } + have_addr = true; + break; + case HCI_EVENT_COMMAND_COMPLETE: + if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { + reverse_48(&packet[7], static_address); + gap_random_address_set(static_address); + using_static_address = true; + have_addr = true; + } + break; + default: + break; + } +} + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // MICROPYBTUSB can be a ':'' or '-' separated port list. + char *path = getenv("MICROPYBTUSB"); + if (path != NULL) { + uint8_t usb_path[7] = {0}; + size_t usb_path_len = 0; + + while (usb_path_len < MP_ARRAY_SIZE(usb_path)) { + char *delimiter; + usb_path[usb_path_len++] = strtol(path, &delimiter, 16); + if (!delimiter || (*delimiter != ':' && *delimiter != '-')) { + break; + } + path = delimiter + 1; + } + + hci_transport_usb_set_path(usb_path_len, usb_path); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + hci_init(hci_transport_usb_instance(), NULL); + + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); +} + +STATIC pthread_t bstack_thread_id; + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + + // Wait for the poll loop to terminate when the state is set to OFF. + pthread_join(bstack_thread_id, NULL); + have_addr = false; +} + +STATIC void *btstack_thread(void *arg) { + (void)arg; + hci_power_control(HCI_POWER_ON); + + // modbluetooth_btstack.c will have set the state to STARTING before + // calling mp_bluetooth_btstack_port_start. + // This loop will terminate when the HCI_POWER_OFF above results + // in modbluetooth_btstack.c setting the state back to OFF. + // Or, if a timeout results in it being set to TIMEOUT. + + while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + btstack_run_loop_embedded_execute_once(); + MICROPY_END_ATOMIC_SECTION(atomic_state); + + // The USB transport schedules events to the run loop at 1ms intervals, + // and the implementation currently polls rather than selects. + usleep(USB_POLL_INTERVAL_US); + } + + hci_close(); + + return NULL; +} + +void mp_bluetooth_btstack_port_start(void) { + // Create a thread to run the btstack loop. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&bstack_thread_id, &attr, &btstack_thread, NULL); +} + +void mp_hal_get_mac(int idx, uint8_t buf[6]) { + if (idx == MP_HAL_MAC_BDADDR) { + if (!have_addr) { + mp_raise_OSError(MP_ENODEV); + } + memcpy(buf, local_addr, sizeof(local_addr)); + } +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/unix/coverage-frzmpy/frzqstr.py b/ports/unix/coverage-frzmpy/frzqstr.py deleted file mode 100644 index 051f2a9c16..0000000000 --- a/ports/unix/coverage-frzmpy/frzqstr.py +++ /dev/null @@ -1,3 +0,0 @@ -# Checks for regression on MP_QSTR_NULL -def returns_NULL(): - return "NULL" diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index f67accd384..372943b894 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -11,10 +11,13 @@ #include "py/emit.h" #include "py/formatfloat.h" #include "py/ringbuf.h" +#include "py/pairheap.h" #include "py/stream.h" #include "py/binary.h" #include "py/bc.h" +// expected output of this file is found in extra_coverage.py.exp + #if defined(MICROPY_UNIX_COVERAGE) // stream testing object @@ -141,6 +144,40 @@ STATIC const mp_obj_type_t mp_type_stest_textio2 = { STATIC const mp_obj_str_t str_no_hash_obj = {{&mp_type_str}, 0, 10, (const byte *)"0123456789"}; STATIC const mp_obj_str_t bytes_no_hash_obj = {{&mp_type_bytes}, 0, 10, (const byte *)"0123456789"}; +STATIC int pairheap_lt(mp_pairheap_t *a, mp_pairheap_t *b) { + return (uintptr_t)a < (uintptr_t)b; +} + +// ops array contain operations: x>=0 means push(x), x<0 means delete(-x) +STATIC void pairheap_test(size_t nops, int *ops) { + mp_pairheap_t node[8]; + for (size_t i = 0; i < MP_ARRAY_SIZE(node); ++i) { + mp_pairheap_init_node(pairheap_lt, &node[i]); + } + mp_pairheap_t *heap = mp_pairheap_new(pairheap_lt); + printf("create:"); + for (size_t i = 0; i < nops; ++i) { + if (ops[i] >= 0) { + heap = mp_pairheap_push(pairheap_lt, heap, &node[ops[i]]); + } else { + heap = mp_pairheap_delete(pairheap_lt, heap, &node[-ops[i]]); + } + if (mp_pairheap_is_empty(pairheap_lt, heap)) { + mp_printf(&mp_plat_print, " -"); + } else { + mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + ; + } + } + printf("\npop all:"); + while (!mp_pairheap_is_empty(pairheap_lt, heap)) { + mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + ; + heap = mp_pairheap_pop(pairheap_lt, heap); + } + printf("\n"); +} + // function to run extra tests for things that can't be checked by scripts STATIC mp_obj_t extra_coverage(void) { // mp_printf (used by ports that don't have a native printf) @@ -336,6 +373,32 @@ STATIC mp_obj_t extra_coverage(void) { mp_call_function_2_protected(MP_OBJ_FROM_PTR(&mp_builtin_divmod_obj), MP_OBJ_NEW_SMALL_INT(1), MP_OBJ_NEW_SMALL_INT(1)); // call mp_call_function_2_protected with invalid args mp_call_function_2_protected(MP_OBJ_FROM_PTR(&mp_builtin_divmod_obj), mp_obj_new_str("abc", 3), mp_obj_new_str("abc", 3)); + + // mp_obj_int_get_uint_checked with non-negative small-int + mp_printf(&mp_plat_print, "%d\n", (int)mp_obj_int_get_uint_checked(MP_OBJ_NEW_SMALL_INT(1))); + + // mp_obj_int_get_uint_checked with non-negative big-int + mp_printf(&mp_plat_print, "%d\n", (int)mp_obj_int_get_uint_checked(mp_obj_new_int_from_ll(2))); + + // mp_obj_int_get_uint_checked with negative small-int (should raise exception) + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_int_get_uint_checked(MP_OBJ_NEW_SMALL_INT(-1)); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + // mp_obj_int_get_uint_checked with negative big-int (should raise exception) + if (nlr_push(&nlr) == 0) { + mp_obj_int_get_uint_checked(mp_obj_new_int_from_ll(-2)); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + // call mp_obj_new_exception_args (it's a part of the public C API and not used in the core) + mp_obj_print_exception(&mp_plat_print, mp_obj_new_exception_args(&mp_type_ValueError, 0, NULL)); } // warning @@ -410,7 +473,7 @@ STATIC mp_obj_t extra_coverage(void) { mp_sched_unlock(); // shouldn't do anything while scheduler is locked - mp_handle_pending(); + mp_handle_pending(true); // unlock scheduler mp_sched_unlock(); @@ -418,8 +481,34 @@ STATIC mp_obj_t extra_coverage(void) { // drain pending callbacks while (mp_sched_num_pending()) { - mp_handle_pending(); + mp_handle_pending(true); } + + // setting the keyboard interrupt and raising it during mp_handle_pending + mp_keyboard_interrupt(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_handle_pending(true); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + // setting the keyboard interrupt (twice) and cancelling it during mp_handle_pending + mp_keyboard_interrupt(); + mp_keyboard_interrupt(); + mp_handle_pending(false); + + // setting keyboard interrupt and a pending event (intr should be handled first) + mp_sched_schedule(MP_OBJ_FROM_PTR(&mp_builtin_print_obj), MP_OBJ_NEW_SMALL_INT(10)); + mp_keyboard_interrupt(); + if (nlr_push(&nlr) == 0) { + mp_handle_pending(true); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + mp_handle_pending(true); } // ringbuf @@ -493,6 +582,54 @@ STATIC mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%d\n", ringbuf_get16(&ringbuf)); } + // pairheap + { + mp_printf(&mp_plat_print, "# pairheap\n"); + + // Basic case. + int t0[] = {0, 2, 1, 3}; + pairheap_test(MP_ARRAY_SIZE(t0), t0); + + // All pushed in reverse order. + int t1[] = {7, 6, 5, 4, 3, 2, 1, 0}; + pairheap_test(MP_ARRAY_SIZE(t1), t1); + + // Basic deletion. + int t2[] = {1, -1, -1, 1, 2, -2, 2, 3, -3}; + pairheap_test(MP_ARRAY_SIZE(t2), t2); + + // Deletion of first child that has next node (the -3). + int t3[] = {1, 2, 3, 4, -1, -3}; + pairheap_test(MP_ARRAY_SIZE(t3), t3); + + // Deletion of node that's not first child (the -2). + int t4[] = {1, 2, 3, 4, -2}; + pairheap_test(MP_ARRAY_SIZE(t4), t4); + + // Deletion of node that's not first child and has children (the -3). + int t5[] = {3, 4, 5, 1, 2, -3}; + pairheap_test(MP_ARRAY_SIZE(t5), t5); + } + + // mp_obj_is_type and derivatives + { + mp_printf(&mp_plat_print, "# mp_obj_is_type\n"); + + // mp_obj_is_bool accepts only booleans + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_bool(mp_const_true), mp_obj_is_bool(mp_const_false)); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_bool(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_bool(mp_const_none)); + + // mp_obj_is_integer accepts ints and booleans + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_integer(mp_obj_new_int_from_ll(1))); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(mp_const_true), mp_obj_is_integer(mp_const_false)); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(mp_obj_new_str("1", 1)), mp_obj_is_integer(mp_const_none)); + + // mp_obj_is_int accepts small int and object ints + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_int(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_int(mp_obj_new_int_from_ll(1))); + } + + mp_printf(&mp_plat_print, "# end coverage.c\n"); + mp_obj_streamtest_t *s = m_new_obj(mp_obj_streamtest_t); s->base.type = &mp_type_stest_fileio; s->buf = NULL; diff --git a/ports/unix/fatfs_port.c b/ports/unix/fatfs_port.c index 30f1959f52..9e0f444ce2 100644 --- a/ports/unix/fatfs_port.c +++ b/ports/unix/fatfs_port.c @@ -1,5 +1,13 @@ +#include #include "lib/oofatfs/ff.h" DWORD get_fattime(void) { - return 0; + time_t now = time(NULL); + struct tm *tm = localtime(&now); + return ((1900 + tm->tm_year - 1980) << 25) + | ((tm->tm_mon + 1) << 21) + | (tm->tm_mday << 16) + | (tm->tm_hour << 11) + | (tm->tm_min << 5) + | (tm->tm_sec / 2); } diff --git a/ports/unix/file.c b/ports/unix/file.c deleted file mode 100644 index 7a412a6739..0000000000 --- a/ports/unix/file.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * SPDX-FileCopyrightText: Copyright (c) 2013, 2014 Damien P. George - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "py/runtime.h" -#include "py/stream.h" -#include "py/builtin.h" -#include "py/mphal.h" -#include "supervisor/shared/translate.h" -#include "fdfile.h" - -#if MICROPY_PY_IO && !MICROPY_VFS - -#ifdef _WIN32 -#define fsync _commit -#endif - -#ifdef MICROPY_CPYTHON_COMPAT -STATIC void check_fd_is_open(const mp_obj_fdfile_t *o) { - if (o->fd < 0) { - mp_raise_ValueError(translate("I/O operation on closed file")); - } -} -#else -#define check_fd_is_open(o) -#endif - -extern const mp_obj_type_t mp_type_fileio; -extern const mp_obj_type_t mp_type_textio; - -STATIC void fdfile_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - (void)kind; - mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", mp_obj_get_type_qstr(self_in), self->fd); -} - -STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { - mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); - check_fd_is_open(o); - mp_int_t r = read(o->fd, buf, size); - if (r == -1) { - *errcode = errno; - return MP_STREAM_ERROR; - } - return r; -} - -STATIC mp_uint_t fdfile_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { - mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); - check_fd_is_open(o); - mp_int_t r = write(o->fd, buf, size); - while (r == -1 && errno == EINTR) { - if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); - } - r = write(o->fd, buf, size); - } - if (r == -1) { - *errcode = errno; - return MP_STREAM_ERROR; - } - return r; -} - -STATIC mp_uint_t fdfile_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { - mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); - check_fd_is_open(o); - switch (request) { - case MP_STREAM_SEEK: { - struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; - off_t off = lseek(o->fd, s->offset, s->whence); - if (off == (off_t)-1) { - *errcode = errno; - return MP_STREAM_ERROR; - } - s->offset = off; - return 0; - } - case MP_STREAM_FLUSH: - if (fsync(o->fd) < 0) { - *errcode = errno; - return MP_STREAM_ERROR; - } - return 0; - case MP_STREAM_CLOSE: - close(o->fd); - #ifdef MICROPY_CPYTHON_COMPAT - o->fd = -1; - #endif - return 0; - case MP_STREAM_GET_FILENO: - return o->fd; - default: - *errcode = EINVAL; - return MP_STREAM_ERROR; - } -} - -STATIC mp_obj_t fdfile___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(fdfile___exit___obj, 4, 4, fdfile___exit__); - -STATIC mp_obj_t fdfile_fileno(mp_obj_t self_in) { - mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); - check_fd_is_open(self); - return MP_OBJ_NEW_SMALL_INT(self->fd); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(fdfile_fileno_obj, fdfile_fileno); - -// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO, -// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor -STATIC const mp_arg_t file_open_args[] = { - { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, - { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} }, - { MP_QSTR_buffering, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, - { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, -}; -#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) - -STATIC mp_obj_t fdfile_open(const mp_obj_type_t *type, mp_arg_val_t *args) { - mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t); - const char *mode_s = mp_obj_str_get_str(args[1].u_obj); - - int mode_rw = 0, mode_x = 0; - while (*mode_s) { - switch (*mode_s++) { - case 'r': - mode_rw = O_RDONLY; - break; - case 'w': - mode_rw = O_WRONLY; - mode_x = O_CREAT | O_TRUNC; - break; - case 'a': - mode_rw = O_WRONLY; - mode_x = O_CREAT | O_APPEND; - break; - case '+': - mode_rw = O_RDWR; - break; - #if MICROPY_PY_IO_FILEIO - // If we don't have io.FileIO, then files are in text mode implicitly - case 'b': - type = &mp_type_fileio; - break; - case 't': - type = &mp_type_textio; - break; - #endif - } - } - - o->base.type = type; - - mp_obj_t fid = args[0].u_obj; - - if (mp_obj_is_small_int(fid)) { - o->fd = MP_OBJ_SMALL_INT_VALUE(fid); - return MP_OBJ_FROM_PTR(o); - } - - const char *fname = mp_obj_str_get_str(fid); - int fd = open(fname, mode_x | mode_rw, 0644); - if (fd == -1) { - mp_raise_OSError(errno); - } - o->fd = fd; - return MP_OBJ_FROM_PTR(o); -} - -STATIC mp_obj_t fdfile_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; - mp_arg_parse_all(n_args, args, kw_args, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); - return fdfile_open(type, arg_vals); -} - -STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&fdfile_fileno_obj) }, - { 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_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_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___enter__), MP_ROM_PTR(&mp_identity_obj) }, - { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&fdfile___exit___obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); - -#if MICROPY_PY_IO_FILEIO -STATIC const mp_stream_p_t fileio_stream_p = { - MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) - .read = fdfile_read, - .write = fdfile_write, - .ioctl = fdfile_ioctl, -}; - -const mp_obj_type_t mp_type_fileio = { - { &mp_type_type }, - .name = MP_QSTR_FileIO, - .print = fdfile_print, - .make_new = fdfile_make_new, - .getiter = mp_identity_getiter, - .iternext = mp_stream_unbuffered_iter, - .protocol = &fileio_stream_p, - .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, -}; -#endif - -STATIC const mp_stream_p_t textio_stream_p = { - MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) - .read = fdfile_read, - .write = fdfile_write, - .ioctl = fdfile_ioctl, - .is_text = true, -}; - -const mp_obj_type_t mp_type_textio = { - { &mp_type_type }, - .name = MP_QSTR_TextIOWrapper, - .print = fdfile_print, - .make_new = fdfile_make_new, - .getiter = mp_identity_getiter, - .iternext = mp_stream_unbuffered_iter, - .protocol = &textio_stream_p, - .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, -}; - -// Factory function for I/O stream classes -mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - // TODO: analyze buffering args and instantiate appropriate type - mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; - mp_arg_parse_all(n_args, args, kwargs, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); - return fdfile_open(&mp_type_textio, arg_vals); -} -MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); - -const mp_obj_fdfile_t mp_sys_stdin_obj = { .base = {&mp_type_textio}, .fd = STDIN_FILENO }; -const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&mp_type_textio}, .fd = STDOUT_FILENO }; -const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&mp_type_textio}, .fd = STDERR_FILENO }; - -#endif // MICROPY_PY_IO && !MICROPY_VFS diff --git a/ports/unix/gccollect.c b/ports/unix/gccollect.c index efe6205a68..5e438b258b 100644 --- a/ports/unix/gccollect.c +++ b/ports/unix/gccollect.c @@ -29,146 +29,15 @@ #include "py/mpstate.h" #include "py/gc.h" +#include "lib/utils/gchelper.h" + #if MICROPY_ENABLE_GC -// Even if we have specific support for an architecture, it is -// possible to force use of setjmp-based implementation. -#if !MICROPY_GCREGS_SETJMP - -// We capture here callee-save registers, i.e. ones which may contain -// interesting values held there by our callers. It doesn't make sense -// to capture caller-saved registers, because they, well, put on the -// stack already by the caller. -#if defined(__x86_64__) -typedef mp_uint_t regs_t[6]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long rbx asm ("rbx"); - register long rbp asm ("rbp"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); - register long r14 asm ("r14"); - register long r15 asm ("r15"); - #ifdef __clang__ - // TODO: - // This is dirty workaround for Clang. It tries to get around - // uncompliant (wrt to GCC) behavior of handling register variables. - // Application of this patch here is random, and done only to unbreak - // MacOS build. Better, cross-arch ways to deal with Clang issues should - // be found. - asm ("" : "=r" (rbx)); - asm ("" : "=r" (rbp)); - asm ("" : "=r" (r12)); - asm ("" : "=r" (r13)); - asm ("" : "=r" (r14)); - asm ("" : "=r" (r15)); - #endif - arr[0] = rbx; - arr[1] = rbp; - arr[2] = r12; - arr[3] = r13; - arr[4] = r14; - arr[5] = r15; -} - -#elif defined(__i386__) - -typedef mp_uint_t regs_t[4]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long ebx asm ("ebx"); - register long esi asm ("esi"); - register long edi asm ("edi"); - register long ebp asm ("ebp"); - #ifdef __clang__ - // TODO: - // This is dirty workaround for Clang. It tries to get around - // uncompliant (wrt to GCC) behavior of handling register variables. - // Application of this patch here is random, and done only to unbreak - // MacOS build. Better, cross-arch ways to deal with Clang issues should - // be found. - asm ("" : "=r" (ebx)); - asm ("" : "=r" (esi)); - asm ("" : "=r" (edi)); - asm ("" : "=r" (ebp)); - #endif - arr[0] = ebx; - arr[1] = esi; - arr[2] = edi; - arr[3] = ebp; -} - -#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) - -typedef mp_uint_t regs_t[10]; - -STATIC void gc_helper_get_regs(regs_t arr) { - register long r4 asm ("r4"); - register long r5 asm ("r5"); - register long r6 asm ("r6"); - register long r7 asm ("r7"); - register long r8 asm ("r8"); - register long r9 asm ("r9"); - register long r10 asm ("r10"); - register long r11 asm ("r11"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); - arr[0] = r4; - arr[1] = r5; - arr[2] = r6; - arr[3] = r7; - arr[4] = r8; - arr[5] = r9; - arr[6] = r10; - arr[7] = r11; - arr[8] = r12; - arr[9] = r13; -} - -#else - -// If we don't have architecture-specific optimized support, -// just fall back to setjmp-based implementation. -#undef MICROPY_GCREGS_SETJMP -#define MICROPY_GCREGS_SETJMP (1) - -#endif // Arch-specific selection -#endif // !MICROPY_GCREGS_SETJMP - -// If MICROPY_GCREGS_SETJMP was requested explicitly, or if -// we enabled it as a fallback above. -#if MICROPY_GCREGS_SETJMP -#include - -typedef jmp_buf regs_t; - -STATIC void gc_helper_get_regs(regs_t arr) { - setjmp(arr); -} - -#endif // MICROPY_GCREGS_SETJMP - -// this function is used by mpthreadport.c -MP_NOINLINE void gc_collect_regs_and_stack(void); - -// Explicitly mark this as noinline to make sure the regs variable -// is effectively at the top of the stack: otherwise, in builds where -// LTO is enabled and a lot of inlining takes place we risk a stack -// layout where regs is lower on the stack than pointers which have -// just been allocated but not yet marked, and get incorrectly sweeped. -MP_NOINLINE void gc_collect_regs_and_stack(void) { - regs_t regs; - gc_helper_get_regs(regs); - // GC stack (and regs because we captured them) - void **regs_ptr = (void **)(void *)®s; - gc_collect_root(regs_ptr, ((uintptr_t)MP_STATE_THREAD(stack_top) - (uintptr_t)®s) / sizeof(uintptr_t)); -} - void gc_collect(void) { // gc_dump_info(); gc_collect_start(); - gc_collect_regs_and_stack(); + gc_helper_collect_regs_and_stack(); #if MICROPY_PY_THREAD mp_thread_gc_others(); #endif diff --git a/ports/unix/input.c b/ports/unix/input.c index 6fbb8513ac..f85bb177f0 100644 --- a/ports/unix/input.c +++ b/ports/unix/input.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +#include #include #include #include @@ -74,6 +75,9 @@ void prompt_read_history(void) { char c; int sz = read(fd, &c, 1); if (sz < 0) { + if (errno == EINTR) { + continue; + } break; } if (sz == 0 || c == '\n') { @@ -107,10 +111,10 @@ void prompt_write_history(void) { for (int i = MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)) - 1; i >= 0; i--) { const char *line = MP_STATE_PORT(readline_hist)[i]; if (line != NULL) { - int res; - res = write(fd, line, strlen(line)); - res = write(fd, "\n", 1); - (void)res; + while (write(fd, line, strlen(line)) == -1 && errno == EINTR) { + } + while (write(fd, "\n", 1) == -1 && errno == EINTR) { + } } } close(fd); diff --git a/ports/unix/main.c b/ports/unix/main.c index 0ba3205468..08c50d5641 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -64,8 +64,8 @@ long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4); STATIC void stderr_print_strn(void *env, const char *str, size_t len) { (void)env; - ssize_t dummy = write(STDERR_FILENO, str, len); - (void)dummy; + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); } const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; @@ -142,21 +142,17 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu if (!compile_only) { // execute it mp_call_function_0(module_fun); - // check for pending exception - if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); - } } mp_hal_set_interrupt_char(-1); + mp_handle_pending(true); nlr_pop(); return 0; } else { // uncaught exception mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); return handle_uncaught_exception(nlr.ret_val); } } @@ -300,11 +296,15 @@ STATIC int do_str(const char *str) { return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); } -STATIC int usage(char **argv) { +STATIC void print_help(char **argv) { printf( - "usage: %s [] [-X ] [-c ] []\n" + "usage: %s [] [-X ] [-c | -m | ]\n" "Options:\n" + "-h : print this help message\n" + "-i : enable inspection via REPL after running command/module/file\n" + #if MICROPY_DEBUG_PRINTERS "-v : verbose (trace various operations); can be multiple\n" + #endif "-O[N] : apply bytecode optimizations of level N\n" "\n" "Implementation specific options (-X):\n", argv[0] @@ -329,7 +329,10 @@ STATIC int usage(char **argv) { if (impl_opts_cnt == 0) { printf(" (none)\n"); } +} +STATIC int invalid_args(void) { + fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n"); return 1; } @@ -337,9 +340,13 @@ STATIC int usage(char **argv) { STATIC void pre_process_options(int argc, char **argv) { for (int a = 1; a < argc; a++) { if (argv[a][0] == '-') { + if (strcmp(argv[a], "-h") == 0) { + print_help(argv); + exit(0); + } if (strcmp(argv[a], "-X") == 0) { if (a + 1 >= argc) { - exit(usage(argv)); + exit(invalid_args()); } if (0) { } else if (strcmp(argv[a + 1], "compile-only") == 0) { @@ -388,8 +395,7 @@ STATIC void pre_process_options(int argc, char **argv) { #endif } else { invalid_arg: - printf("Invalid option\n"); - exit(usage(argv)); + exit(invalid_args()); } a++; } @@ -561,7 +567,7 @@ MP_NOINLINE int main_(int argc, char **argv) { inspect = true; } else if (strcmp(argv[a], "-c") == 0) { if (a + 1 >= argc) { - return usage(argv); + return invalid_args(); } ret = do_str(argv[a + 1]); if (ret & FORCED_EXIT) { @@ -570,7 +576,7 @@ MP_NOINLINE int main_(int argc, char **argv) { a += 1; } else if (strcmp(argv[a], "-m") == 0) { if (a + 1 >= argc) { - return usage(argv); + return invalid_args(); } mp_obj_t import_args[4]; import_args[0] = mp_obj_new_str(argv[a + 1], strlen(argv[a + 1])); @@ -626,7 +632,7 @@ MP_NOINLINE int main_(int argc, char **argv) { } } } else { - return usage(argv); + return invalid_args(); } } else { char *pathbuf = malloc(PATH_MAX); @@ -649,8 +655,12 @@ MP_NOINLINE int main_(int argc, char **argv) { } } + const char *inspect_env = getenv("MICROPYINSPECT"); + if (inspect_env && inspect_env[0] != '\0') { + inspect = true; + } if (ret == NOTHING_EXECUTED || inspect) { - if (isatty(0)) { + if (isatty(0) || inspect) { prompt_read_history(); ret = do_repl(); prompt_write_history(); @@ -708,9 +718,27 @@ uint mp_import_stat(const char *path) { } return MP_IMPORT_STAT_NO_EXIST; } + +#if MICROPY_PY_IO +// Factory function for I/O stream classes, only needed if generic VFS subsystem isn't used. +// Note: buffering and encoding are currently ignored. +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kwargs) { + enum { ARG_file, ARG_mode }; + 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_obj = MP_OBJ_NEW_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} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kwargs, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + return mp_vfs_posix_file_open(&mp_type_textio, args[ARG_file].u_obj, args[ARG_mode].u_obj); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); +#endif #endif void nlr_jump_fail(void *val) { - printf("FATAL: uncaught NLR %p\n", val); + fprintf(stderr, "FATAL: uncaught NLR %p\n", val); exit(1); } diff --git a/ports/unix/modffi.c b/ports/unix/modffi.c index 9e953ca81d..d18b82dbc6 100644 --- a/ports/unix/modffi.c +++ b/ports/unix/modffi.c @@ -150,7 +150,7 @@ STATIC ffi_type *get_ffi_type(mp_obj_t o_in) { } // TODO: Support actual libffi type objects - mp_raise_TypeError(translate("Unknown type")); + mp_raise_TypeError(MP_ERROR_TEXT("unknown type")); } STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { @@ -169,11 +169,11 @@ STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { union { ffi_arg ffi; float flt; } val_union = { .ffi = val }; - return mp_obj_new_float((mp_float_t)val_union.flt); + return mp_obj_new_float_from_f(val_union.flt); } case 'd': { double *p = (double *)&val; - return mp_obj_new_float(*p); + return mp_obj_new_float_from_d(*p); } #endif case 'O': @@ -220,7 +220,7 @@ STATIC mp_obj_t make_func(mp_obj_t rettype_in, void *func, mp_obj_t argtypes_in) int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); if (res != FFI_OK) { - mp_raise_ValueError(translate("Error in ffi_prep_cif")); + mp_raise_ValueError(MP_ERROR_TEXT("Error in ffi_prep_cif")); } return MP_OBJ_FROM_PTR(o); @@ -278,12 +278,12 @@ STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); if (res != FFI_OK) { - mp_raise_ValueError(translate("Error in ffi_prep_cif")); + mp_raise_ValueError(MP_ERROR_TEXT("Error in ffi_prep_cif")); } res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, MP_OBJ_TO_PTR(func_in), o->func); if (res != FFI_OK) { - mp_raise_ValueError(translate("ffi_prep_closure_loc")); + mp_raise_ValueError(MP_ERROR_TEXT("ffi_prep_closure_loc")); } return MP_OBJ_FROM_PTR(o); @@ -380,10 +380,10 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const #if MICROPY_PY_BUILTINS_FLOAT } else if (*argtype == 'f') { float *p = (float *)&values[i]; - *p = mp_obj_get_float(a); + *p = mp_obj_get_float_to_f(a); } else if (*argtype == 'd') { double *p = (double *)&values[i]; - *p = mp_obj_get_float(a); + *p = mp_obj_get_float_to_d(a); #endif } else if (a == mp_const_none) { values[i] = 0; @@ -417,7 +417,7 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { double retval; ffi_call(&self->cif, self->func, &retval, valueptrs); - return mp_obj_new_float(retval); + return mp_obj_new_float_from_d(retval); } else #endif { @@ -427,7 +427,7 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const } error: - mp_raise_TypeError(translate("Don't know how to pass object to native function")); + mp_raise_TypeError(MP_ERROR_TEXT("Don't know how to pass object to native function")); } STATIC const mp_obj_type_t ffifunc_type = { diff --git a/ports/unix/modjni.c b/ports/unix/modjni.c index 4a9fdb4a58..bf039c0b71 100644 --- a/ports/unix/modjni.c +++ b/ports/unix/modjni.c @@ -157,7 +157,7 @@ STATIC void jclass_attr(mp_obj_t self_in, qstr attr_in, mp_obj_t *dest) { STATIC mp_obj_t jclass_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { if (n_kw != 0) { - mp_raise_TypeError("kwargs not supported"); + mp_raise_TypeError(MP_ERROR_TEXT("kwargs not supported")); } mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); @@ -431,7 +431,7 @@ STATIC bool py2jvalue(const char **jtypesig, mp_obj_t arg, jvalue *out) { } out->l = NULL; } else { - mp_raise_TypeError("arg type not supported"); + mp_raise_TypeError(MP_ERROR_TEXT("arg type not supported")); } *jtypesig = arg_type; @@ -531,7 +531,7 @@ STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool ret = new_jobject(res); } else { JJ(ReleaseStringUTFChars, name_o, decl); - mp_raise_TypeError("cannot handle return type"); + mp_raise_TypeError(MP_ERROR_TEXT("can't handle return type")); } JJ(ReleaseStringUTFChars, name_o, decl); @@ -547,13 +547,13 @@ STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool JJ(DeleteLocalRef, meth); } - mp_raise_TypeError("method not found"); + mp_raise_TypeError(MP_ERROR_TEXT("method not found")); } STATIC mp_obj_t jmethod_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { if (n_kw != 0) { - mp_raise_TypeError("kwargs not supported"); + mp_raise_TypeError(MP_ERROR_TEXT("kwargs not supported")); } mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); @@ -599,13 +599,13 @@ STATIC void create_jvm(void) { void *libjvm = dlopen(LIBJVM_SO, RTLD_NOW | RTLD_GLOBAL); if (!libjvm) { - mp_raise_msg(&mp_type_OSError, "unable to load libjvm.so, use LD_LIBRARY_PATH"); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("unable to load libjvm.so, use LD_LIBRARY_PATH")); } int (*_JNI_CreateJavaVM)(void *, void **, void *) = dlsym(libjvm, "JNI_CreateJavaVM"); int st = _JNI_CreateJavaVM(&jvm, (void **)&env, &args); if (st < 0 || !env) { - mp_raise_msg(&mp_type_OSError, "unable to create JVM"); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("unable to create JVM")); } Class_class = JJ(FindClass, "java/lang/Class"); @@ -614,26 +614,26 @@ STATIC void create_jvm(void) { jclass Object_class = JJ(FindClass, "java/lang/Object"); Object_toString_mid = JJ(GetMethodID, Object_class, "toString", - "()Ljava/lang/String;"); + MP_ERROR_TEXT("()Ljava/lang/String;")); Class_getName_mid = (*env)->GetMethodID(env, Class_class, "getName", - "()Ljava/lang/String;"); + MP_ERROR_TEXT("()Ljava/lang/String;")); Class_getField_mid = (*env)->GetMethodID(env, Class_class, "getField", - "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); + MP_ERROR_TEXT("(Ljava/lang/String;)Ljava/lang/reflect/Field;")); Class_getMethods_mid = (*env)->GetMethodID(env, Class_class, "getMethods", - "()[Ljava/lang/reflect/Method;"); + MP_ERROR_TEXT("()[Ljava/lang/reflect/Method;")); Class_getConstructors_mid = (*env)->GetMethodID(env, Class_class, "getConstructors", - "()[Ljava/lang/reflect/Constructor;"); + MP_ERROR_TEXT("()[Ljava/lang/reflect/Constructor;")); Method_getName_mid = (*env)->GetMethodID(env, method_class, "getName", - "()Ljava/lang/String;"); + MP_ERROR_TEXT("()Ljava/lang/String;")); List_class = JJ(FindClass, "java/util/List"); List_get_mid = JJ(GetMethodID, List_class, "get", - "(I)Ljava/lang/Object;"); + MP_ERROR_TEXT("(I)Ljava/lang/Object;")); List_set_mid = JJ(GetMethodID, List_class, "set", - "(ILjava/lang/Object;)Ljava/lang/Object;"); + MP_ERROR_TEXT("(ILjava/lang/Object;)Ljava/lang/Object;")); List_size_mid = JJ(GetMethodID, List_class, "size", - "()I"); + MP_ERROR_TEXT("()I")); IndexException_class = JJ(FindClass, "java/lang/IndexOutOfBoundsException"); } diff --git a/ports/unix/modmachine.c b/ports/unix/modmachine.c index 0871d84d02..e139605b29 100644 --- a/ports/unix/modmachine.c +++ b/ports/unix/modmachine.c @@ -51,7 +51,7 @@ uintptr_t mod_machine_mem_get_addr(mp_obj_t addr_o, uint align) { uintptr_t addr = mp_obj_int_get_truncated(addr_o); if ((addr & (align - 1)) != 0) { - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, translate("address %08x is not aligned to %d bytes"), addr, align)); + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align); } #if MICROPY_PLAT_DEV_MEM { @@ -79,6 +79,14 @@ uintptr_t mod_machine_mem_get_addr(mp_obj_t addr_o, uint align) { return addr; } +#ifdef MICROPY_UNIX_MACHINE_IDLE +STATIC mp_obj_t machine_idle(void) { + MICROPY_UNIX_MACHINE_IDLE + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); +#endif + STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, @@ -86,6 +94,10 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + #ifdef MICROPY_UNIX_MACHINE_IDLE + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_PinBase), MP_ROM_PTR(&machine_pinbase_type) }, { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, #if MICROPY_PY_MACHINE_PULSE diff --git a/ports/unix/modos.c b/ports/unix/modos.c index 3d547bf56c..c7386180ae 100644 --- a/ports/unix/modos.c +++ b/ports/unix/modos.c @@ -29,14 +29,19 @@ #include #include #include +#include #include #include #include +#ifdef _MSC_VER +#include // For mkdir +#endif #include "py/mpconfig.h" #include "py/runtime.h" #include "py/objtuple.h" #include "py/mphal.h" +#include "py/mpthread.h" #include "extmod/vfs.h" #ifdef __ANDROID__ @@ -47,20 +52,20 @@ STATIC mp_obj_t mod_os_stat(mp_obj_t path_in) { struct stat sb; const char *path = mp_obj_str_get_str(path_in); - int res = stat(path, &sb); - RAISE_ERRNO(res, errno); + int res; + MP_HAL_RETRY_SYSCALL(res, stat(path, &sb), mp_raise_OSError(err)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); - t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.st_ino); - t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.st_dev); - t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.st_nlink); - t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.st_uid); - t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.st_gid); + t->items[1] = mp_obj_new_int_from_uint(sb.st_ino); + t->items[2] = mp_obj_new_int_from_uint(sb.st_dev); + t->items[3] = mp_obj_new_int_from_uint(sb.st_nlink); + t->items[4] = mp_obj_new_int_from_uint(sb.st_uid); + t->items[5] = mp_obj_new_int_from_uint(sb.st_gid); t->items[6] = mp_obj_new_int_from_uint(sb.st_size); - t->items[7] = MP_OBJ_NEW_SMALL_INT(sb.st_atime); - t->items[8] = MP_OBJ_NEW_SMALL_INT(sb.st_mtime); - t->items[9] = MP_OBJ_NEW_SMALL_INT(sb.st_ctime); + t->items[7] = mp_obj_new_int_from_uint(sb.st_atime); + t->items[8] = mp_obj_new_int_from_uint(sb.st_mtime); + t->items[9] = mp_obj_new_int_from_uint(sb.st_ctime); return MP_OBJ_FROM_PTR(t); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); @@ -87,8 +92,8 @@ STATIC mp_obj_t mod_os_statvfs(mp_obj_t path_in) { STRUCT_STATVFS sb; const char *path = mp_obj_str_get_str(path_in); - int res = STATVFS(path, &sb); - RAISE_ERRNO(res, errno); + int res; + MP_HAL_RETRY_SYSCALL(res, STATVFS(path, &sb), mp_raise_OSError(err)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.f_bsize); @@ -114,7 +119,9 @@ STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { // of that function. But Python remove() follows ANSI C, and explicitly // required to raise exception on attempt to remove a directory. Thus, // call POSIX unlink() here. + MP_THREAD_GIL_EXIT(); int r = unlink(path); + MP_THREAD_GIL_ENTER(); RAISE_ERRNO(r, errno); @@ -122,10 +129,39 @@ STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_remove_obj, mod_os_remove); +STATIC mp_obj_t mod_os_rename(mp_obj_t old_path_in, mp_obj_t new_path_in) { + const char *old_path = mp_obj_str_get_str(old_path_in); + const char *new_path = mp_obj_str_get_str(new_path_in); + + MP_THREAD_GIL_EXIT(); + int r = rename(old_path, new_path); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_os_rename_obj, mod_os_rename); + +STATIC mp_obj_t mod_os_rmdir(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + MP_THREAD_GIL_EXIT(); + int r = rmdir(path); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_rmdir_obj, mod_os_rmdir); + STATIC mp_obj_t mod_os_system(mp_obj_t cmd_in) { const char *cmd = mp_obj_str_get_str(cmd_in); + MP_THREAD_GIL_EXIT(); int r = system(cmd); + MP_THREAD_GIL_ENTER(); RAISE_ERRNO(r, errno); @@ -142,14 +178,51 @@ STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) { } MP_DEFINE_CONST_FUN_OBJ_1(mod_os_getenv_obj, mod_os_getenv); +STATIC mp_obj_t mod_os_putenv(mp_obj_t key_in, mp_obj_t value_in) { + const char *key = mp_obj_str_get_str(key_in); + const char *value = mp_obj_str_get_str(value_in); + int ret; + + #if _WIN32 + ret = _putenv_s(key, value); + #else + ret = setenv(key, value, 1); + #endif + + if (ret == -1) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_os_putenv_obj, mod_os_putenv); + +STATIC mp_obj_t mod_os_unsetenv(mp_obj_t key_in) { + const char *key = mp_obj_str_get_str(key_in); + int ret; + + #if _WIN32 + ret = _putenv_s(key, ""); + #else + ret = unsetenv(key); + #endif + + if (ret == -1) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_unsetenv_obj, mod_os_unsetenv); + STATIC mp_obj_t mod_os_mkdir(mp_obj_t path_in) { // TODO: Accept mode param const char *path = mp_obj_str_get_str(path_in); + MP_THREAD_GIL_EXIT(); #ifdef _WIN32 int r = mkdir(path); #else int r = mkdir(path, 0777); #endif + MP_THREAD_GIL_ENTER(); RAISE_ERRNO(r, errno); return mp_const_none; } @@ -167,13 +240,16 @@ STATIC mp_obj_t listdir_next(mp_obj_t self_in) { if (self->dir == NULL) { goto done; } + MP_THREAD_GIL_EXIT(); struct dirent *dirent = readdir(self->dir); if (dirent == NULL) { closedir(self->dir); + MP_THREAD_GIL_ENTER(); self->dir = NULL; done: return MP_OBJ_STOP_ITERATION; } + MP_THREAD_GIL_ENTER(); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); t->items[0] = mp_obj_new_str(dirent->d_name, strlen(dirent->d_name)); @@ -210,7 +286,9 @@ STATIC mp_obj_t mod_os_ilistdir(size_t n_args, const mp_obj_t *args) { } mp_obj_listdir_t *o = m_new_obj(mp_obj_listdir_t); o->base.type = &mp_type_polymorph_iter; + MP_THREAD_GIL_EXIT(); o->dir = opendir(path); + MP_THREAD_GIL_ENTER(); o->iternext = listdir_next; return MP_OBJ_FROM_PTR(o); } @@ -235,7 +313,11 @@ STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_obj) }, { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mod_os_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mod_os_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mod_os_rmdir_obj) }, { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_putenv), MP_ROM_PTR(&mod_os_putenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_unsetenv), MP_ROM_PTR(&mod_os_unsetenv_obj) }, { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mod_os_mkdir_obj) }, { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mod_os_ilistdir_obj) }, }; diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 6ea85359da..188f24db83 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -61,7 +61,7 @@ static inline int msec_sleep_tv(struct timeval *tv) { #endif #if defined(MP_CLOCKS_PER_SEC) -#define CLOCK_DIV (mp_float_t)(MP_CLOCKS_PER_SEC / 1000.0F) +#define CLOCK_DIV (MP_CLOCKS_PER_SEC / MICROPY_FLOAT_CONST(1000.0)) #else #error Unsupported clock() implementation #endif @@ -84,7 +84,7 @@ STATIC mp_obj_t mod_time_clock(void) { // float cannot represent full range of int32 precisely, so we pre-divide // int to reduce resolution, and then actually do float division hoping // to preserve integer part resolution. - return mp_obj_new_float((mp_float_t)(clock() / 1000) / CLOCK_DIV); + return mp_obj_new_float((clock() / 1000) / CLOCK_DIV); #else return mp_obj_new_int((mp_int_t)clock()); #endif @@ -95,9 +95,9 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { #if MICROPY_PY_BUILTINS_FLOAT struct timeval tv; mp_float_t val = mp_obj_get_float(arg); - double ipart; - tv.tv_usec = round(modf(val, &ipart) * 1000000); - tv.tv_sec = ipart; + mp_float_t ipart; + tv.tv_usec = (time_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(modf)(val, &ipart) * MICROPY_FLOAT_CONST(1000000.)); + tv.tv_sec = (suseconds_t)ipart; int res; while (1) { MP_THREAD_GIL_EXIT(); @@ -109,7 +109,7 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { if (res != -1 || errno != EINTR) { break; } - mp_handle_pending(); + mp_handle_pending(true); // printf("select: EINTR: %ld:%ld\n", tv.tv_sec, tv.tv_usec); #else break; @@ -117,10 +117,16 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { } RAISE_ERRNO(res, errno); #else - // TODO: Handle EINTR - MP_THREAD_GIL_EXIT(); - sleep(mp_obj_get_int(arg)); - MP_THREAD_GIL_ENTER(); + int seconds = mp_obj_get_int(arg); + for (;;) { + MP_THREAD_GIL_EXIT(); + seconds = sleep(seconds); + MP_THREAD_GIL_ENTER(); + if (seconds == 0) { + break; + } + mp_handle_pending(true); + } #endif return mp_const_none; } @@ -161,6 +167,37 @@ STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_localtime_obj, 0, 1, mod_time_localtime); +STATIC mp_obj_t mod_time_mktime(mp_obj_t tuple) { + size_t len; + mp_obj_t *elem; + mp_obj_get_array(tuple, &len, &elem); + + // localtime generates a tuple of len 8. CPython uses 9, so we accept both. + if (len < 8 || len > 9) { + mp_raise_TypeError(MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9")); + } + + struct tm time = { + .tm_year = mp_obj_get_int(elem[0]) - 1900, + .tm_mon = mp_obj_get_int(elem[1]) - 1, + .tm_mday = mp_obj_get_int(elem[2]), + .tm_hour = mp_obj_get_int(elem[3]), + .tm_min = mp_obj_get_int(elem[4]), + .tm_sec = mp_obj_get_int(elem[5]), + }; + if (len == 9) { + time.tm_isdst = mp_obj_get_int(elem[8]); + } else { + time.tm_isdst = -1; // auto-detect + } + time_t ret = mktime(&time); + if (ret == -1) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("invalid mktime usage")); + } + return mp_obj_new_int(ret); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_time_mktime_obj, mod_time_mktime); + STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, { MP_ROM_QSTR(MP_QSTR_clock), MP_ROM_PTR(&mod_time_clock_obj) }, @@ -174,6 +211,7 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&mod_time_mktime_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_time_globals, mp_module_time_globals_table); diff --git a/ports/unix/moduos_vfs.c b/ports/unix/moduos_vfs.c index 457c9bb879..41273bc7e2 100644 --- a/ports/unix/moduos_vfs.c +++ b/ports/unix/moduos_vfs.c @@ -37,6 +37,8 @@ // These are defined in modos.c MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj); MP_DECLARE_CONST_FUN_OBJ_1(mod_os_getenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_putenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_unsetenv_obj); MP_DECLARE_CONST_FUN_OBJ_1(mod_os_system_obj); STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { @@ -45,6 +47,8 @@ STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_putenv), MP_ROM_PTR(&mod_os_putenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_unsetenv), MP_ROM_PTR(&mod_os_unsetenv_obj) }, { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_obj) }, { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, diff --git a/ports/unix/moduselect.c b/ports/unix/moduselect.c index 299fa20039..dfe32f18fd 100644 --- a/ports/unix/moduselect.c +++ b/ports/unix/moduselect.c @@ -39,7 +39,7 @@ #include "py/objlist.h" #include "py/objtuple.h" #include "py/mphal.h" -#include "fdfile.h" +#include "py/mpthread.h" #define DEBUG 0 @@ -188,8 +188,8 @@ STATIC int poll_poll_internal(size_t n_args, const mp_obj_t *args) { self->flags = flags; - int n_ready = poll(self->entries, self->len, timeout); - RAISE_ERRNO(n_ready, errno); + int n_ready; + MP_HAL_RETRY_SYSCALL(n_ready, poll(self->entries, self->len, timeout), mp_raise_OSError(err)); return n_ready; } diff --git a/ports/unix/modusocket.c b/ports/unix/modusocket.c deleted file mode 100644 index 984c6d914d..0000000000 --- a/ports/unix/modusocket.c +++ /dev/null @@ -1,639 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * SPDX-FileCopyrightText: Copyright (c) 2014-2018 Paul Sokolovsky - * SPDX-FileCopyrightText: Copyright (c) 2014-2019 Damien P. George - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "py/objtuple.h" -#include "py/objstr.h" -#include "py/runtime.h" -#include "py/stream.h" -#include "py/builtin.h" -#include "py/mphal.h" - -#include "supervisor/shared/translate.h" - -/* - The idea of this module is to implement reasonable minimum of - socket-related functions to write typical clients and servers. - The module named "usocket" on purpose, to allow to make - Python-level module more (or fully) compatible with CPython - "socket", e.g.: - ---- socket.py ---- - from usocket import * - from socket_more_funcs import * - from socket_more_funcs2 import * - ------------------- - I.e. this module should stay lean, and more functions (if needed) - should be add to separate modules (C or Python level). - */ - -// This type must "inherit" from mp_obj_fdfile_t, i.e. matching subset of -// fields should have the same layout. -typedef struct _mp_obj_socket_t { - mp_obj_base_t base; - int fd; - bool blocking; -} mp_obj_socket_t; - -const mp_obj_type_t mp_type_socket; - -// Helper functions -static inline mp_obj_t mp_obj_from_sockaddr(const struct sockaddr *addr, socklen_t len) { - return mp_obj_new_bytes((const byte *)addr, len); -} - -STATIC mp_obj_socket_t *socket_new(int fd) { - mp_obj_socket_t *o = m_new_obj(mp_obj_socket_t); - o->base.type = &mp_type_socket; - o->fd = fd; - o->blocking = true; - return o; -} - - -STATIC void socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - (void)kind; - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "<_socket %d>", self->fd); -} - -STATIC mp_uint_t socket_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { - mp_obj_socket_t *o = MP_OBJ_TO_PTR(o_in); - mp_int_t r = read(o->fd, buf, size); - if (r == -1) { - int err = errno; - // On blocking socket, we get EAGAIN in case SO_RCVTIMEO/SO_SNDTIMEO - // timed out, and need to convert that to ETIMEDOUT. - if (err == EAGAIN && o->blocking) { - err = MP_ETIMEDOUT; - } - - *errcode = err; - return MP_STREAM_ERROR; - } - return r; -} - -STATIC mp_uint_t socket_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { - mp_obj_socket_t *o = MP_OBJ_TO_PTR(o_in); - mp_int_t r = write(o->fd, buf, size); - if (r == -1) { - int err = errno; - // On blocking socket, we get EAGAIN in case SO_RCVTIMEO/SO_SNDTIMEO - // timed out, and need to convert that to ETIMEDOUT. - if (err == EAGAIN && o->blocking) { - err = MP_ETIMEDOUT; - } - - *errcode = err; - return MP_STREAM_ERROR; - } - return r; -} - -STATIC mp_uint_t socket_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(o_in); - (void)arg; - switch (request) { - case MP_STREAM_CLOSE: - // There's a POSIX drama regarding return value of close in general, - // and EINTR error in particular. See e.g. - // http://lwn.net/Articles/576478/ - // http://austingroupbugs.net/view.php?id=529 - // The rationale MicroPython follows is that close() just releases - // file descriptor. If you're interested to catch I/O errors before - // closing fd, fsync() it. - close(self->fd); - return 0; - - case MP_STREAM_GET_FILENO: - return self->fd; - - default: - *errcode = MP_EINVAL; - return MP_STREAM_ERROR; - } -} - -STATIC mp_obj_t socket_fileno(mp_obj_t self_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(self->fd); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_fileno_obj, socket_fileno); - -STATIC mp_obj_t socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(addr_in, &bufinfo, MP_BUFFER_READ); - int r = connect(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); - int err = errno; - if (r == -1 && self->blocking && err == EINPROGRESS) { - // EINPROGRESS on a blocking socket means the operation timed out - err = MP_ETIMEDOUT; - } - RAISE_ERRNO(r, err); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_connect_obj, socket_connect); - -STATIC mp_obj_t socket_bind(mp_obj_t self_in, mp_obj_t addr_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(addr_in, &bufinfo, MP_BUFFER_READ); - int r = bind(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); - RAISE_ERRNO(r, errno); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_bind_obj, socket_bind); - -STATIC mp_obj_t socket_listen(mp_obj_t self_in, mp_obj_t backlog_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - int r = listen(self->fd, MP_OBJ_SMALL_INT_VALUE(backlog_in)); - RAISE_ERRNO(r, errno); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_listen_obj, socket_listen); - -STATIC mp_obj_t socket_accept(mp_obj_t self_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - // sockaddr_storage isn't stack-friendly (129 bytes or so) - // struct sockaddr_storage addr; - byte addr[32]; - socklen_t addr_len = sizeof(addr); - int fd = accept(self->fd, (struct sockaddr *)&addr, &addr_len); - int err = errno; - if (fd == -1 && self->blocking && err == EAGAIN) { - // EAGAIN on a blocking socket means the operation timed out - err = MP_ETIMEDOUT; - } - RAISE_ERRNO(fd, err); - - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); - t->items[0] = MP_OBJ_FROM_PTR(socket_new(fd)); - t->items[1] = mp_obj_new_bytearray(addr_len, &addr); - - return MP_OBJ_FROM_PTR(t); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); - -// Note: besides flag param, this differs from read() in that -// this does not swallow blocking errors (EAGAIN, EWOULDBLOCK) - -// these would be thrown as exceptions. -STATIC mp_obj_t socket_recv(size_t n_args, const mp_obj_t *args) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); - int flags = 0; - - if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); - } - - byte *buf = m_new(byte, sz); - int out_sz = recv(self->fd, buf, sz, flags); - RAISE_ERRNO(out_sz, errno); - - mp_obj_t ret = mp_obj_new_str_of_type(&mp_type_bytes, buf, out_sz); - m_del(char, buf, sz); - return ret; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recv_obj, 2, 3, socket_recv); - -STATIC mp_obj_t socket_recvfrom(size_t n_args, const mp_obj_t *args) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); - int flags = 0; - - if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); - } - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - - byte *buf = m_new(byte, sz); - int out_sz = recvfrom(self->fd, buf, sz, flags, (struct sockaddr *)&addr, &addr_len); - RAISE_ERRNO(out_sz, errno); - - mp_obj_t buf_o = mp_obj_new_str_of_type(&mp_type_bytes, buf, out_sz); - m_del(char, buf, sz); - - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); - t->items[0] = buf_o; - t->items[1] = mp_obj_from_sockaddr((struct sockaddr *)&addr, addr_len); - - return MP_OBJ_FROM_PTR(t); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recvfrom_obj, 2, 3, socket_recvfrom); - -// Note: besides flag param, this differs from write() in that -// this does not swallow blocking errors (EAGAIN, EWOULDBLOCK) - -// these would be thrown as exceptions. -STATIC mp_obj_t socket_send(size_t n_args, const mp_obj_t *args) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int flags = 0; - - if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); - } - - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); - int out_sz = send(self->fd, bufinfo.buf, bufinfo.len, flags); - RAISE_ERRNO(out_sz, errno); - - return MP_OBJ_NEW_SMALL_INT(out_sz); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_send_obj, 2, 3, socket_send); - -STATIC mp_obj_t socket_sendto(size_t n_args, const mp_obj_t *args) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int flags = 0; - - mp_obj_t dst_addr = args[2]; - if (n_args > 3) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); - dst_addr = args[3]; - } - - mp_buffer_info_t bufinfo, addr_bi; - mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); - mp_get_buffer_raise(dst_addr, &addr_bi, MP_BUFFER_READ); - int out_sz = sendto(self->fd, bufinfo.buf, bufinfo.len, flags, - (struct sockaddr *)addr_bi.buf, addr_bi.len); - RAISE_ERRNO(out_sz, errno); - - return MP_OBJ_NEW_SMALL_INT(out_sz); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_sendto_obj, 3, 4, socket_sendto); - -STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { - (void)n_args; // always 4 - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int level = MP_OBJ_SMALL_INT_VALUE(args[1]); - int option = mp_obj_get_int(args[2]); - - const void *optval; - socklen_t optlen; - int val; - if (mp_obj_is_int(args[3])) { - val = mp_obj_int_get_truncated(args[3]); - optval = &val; - optlen = sizeof(val); - } else { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); - optval = bufinfo.buf; - optlen = bufinfo.len; - } - int r = setsockopt(self->fd, level, option, optval, optlen); - RAISE_ERRNO(r, errno); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_setsockopt_obj, 4, 4, socket_setsockopt); - -STATIC mp_obj_t socket_setblocking(mp_obj_t self_in, mp_obj_t flag_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - int val = mp_obj_is_true(flag_in); - int flags = fcntl(self->fd, F_GETFL, 0); - RAISE_ERRNO(flags, errno); - if (val) { - flags &= ~O_NONBLOCK; - } else { - flags |= O_NONBLOCK; - } - flags = fcntl(self->fd, F_SETFL, flags); - RAISE_ERRNO(flags, errno); - self->blocking = val; - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); - -STATIC mp_obj_t socket_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) { - mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); - struct timeval tv = {0,}; - bool new_blocking = true; - - // Timeout of None means no timeout, which in POSIX is signified with 0 timeout, - // and that's how 'tv' is initialized above - if (timeout_in != mp_const_none) { - #if MICROPY_PY_BUILTINS_FLOAT - mp_float_t val = mp_obj_get_float(timeout_in); - double ipart; - tv.tv_usec = round(modf(val, &ipart) * 1000000); - tv.tv_sec = ipart; - #else - tv.tv_sec = mp_obj_get_int(timeout_in); - #endif - - // For SO_RCVTIMEO/SO_SNDTIMEO, zero timeout means infinity, but - // for Python API it means non-blocking. - if (tv.tv_sec == 0 && tv.tv_usec == 0) { - new_blocking = false; - } - } - - if (new_blocking) { - int r; - r = setsockopt(self->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); - RAISE_ERRNO(r, errno); - r = setsockopt(self->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval)); - RAISE_ERRNO(r, errno); - } - - if (self->blocking != new_blocking) { - socket_setblocking(self_in, mp_obj_new_bool(new_blocking)); - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_settimeout_obj, socket_settimeout); - -STATIC mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { - // TODO: CPython explicitly says that closing returned object doesn't close - // the original socket (Python2 at all says that fd is dup()ed). But we - // save on the bloat. - mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - mp_obj_t *new_args = alloca(n_args * sizeof(mp_obj_t)); - memcpy(new_args + 1, args + 1, (n_args - 1) * sizeof(mp_obj_t)); - new_args[0] = MP_OBJ_NEW_SMALL_INT(self->fd); - return mp_builtin_open(n_args, new_args, (mp_map_t *)&mp_const_empty_map); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); - -STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - (void)type_in; - (void)kw_args; - - int family = AF_INET; - int type = SOCK_STREAM; - int proto = 0; - - if (n_args > 0) { - assert(mp_obj_is_small_int(args[0])); - family = MP_OBJ_SMALL_INT_VALUE(args[0]); - if (n_args > 1) { - assert(mp_obj_is_small_int(args[1])); - type = MP_OBJ_SMALL_INT_VALUE(args[1]); - if (n_args > 2) { - assert(mp_obj_is_small_int(args[2])); - proto = MP_OBJ_SMALL_INT_VALUE(args[2]); - } - } - } - - int fd = socket(family, type, proto); - RAISE_ERRNO(fd, errno); - return MP_OBJ_FROM_PTR(socket_new(fd)); -} - -STATIC const mp_rom_map_elem_t usocket_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&socket_fileno_obj) }, - { MP_ROM_QSTR(MP_QSTR_makefile), MP_ROM_PTR(&socket_makefile_obj) }, - { 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_connect), MP_ROM_PTR(&socket_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_bind), MP_ROM_PTR(&socket_bind_obj) }, - { MP_ROM_QSTR(MP_QSTR_listen), MP_ROM_PTR(&socket_listen_obj) }, - { MP_ROM_QSTR(MP_QSTR_accept), MP_ROM_PTR(&socket_accept_obj) }, - { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&socket_recv_obj) }, - { MP_ROM_QSTR(MP_QSTR_recvfrom), MP_ROM_PTR(&socket_recvfrom_obj) }, - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&socket_send_obj) }, - { MP_ROM_QSTR(MP_QSTR_sendto), MP_ROM_PTR(&socket_sendto_obj) }, - { MP_ROM_QSTR(MP_QSTR_setsockopt), MP_ROM_PTR(&socket_setsockopt_obj) }, - { MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socket_setblocking_obj) }, - { MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&socket_settimeout_obj) }, - { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(usocket_locals_dict, usocket_locals_dict_table); - -STATIC const mp_stream_p_t usocket_stream_p = { - MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) - .read = socket_read, - .write = socket_write, - .ioctl = socket_ioctl, -}; - -const mp_obj_type_t mp_type_socket = { - { &mp_type_type }, - .name = MP_QSTR_socket, - .print = socket_print, - .make_new = socket_make_new, - .getiter = NULL, - .iternext = NULL, - .protocol = &usocket_stream_p, - .locals_dict = (mp_obj_dict_t *)&usocket_locals_dict, -}; - -#define BINADDR_MAX_LEN sizeof(struct in6_addr) -STATIC mp_obj_t mod_socket_inet_pton(mp_obj_t family_in, mp_obj_t addr_in) { - int family = mp_obj_get_int(family_in); - byte binaddr[BINADDR_MAX_LEN]; - int r = inet_pton(family, mp_obj_str_get_str(addr_in), binaddr); - RAISE_ERRNO(r, errno); - if (r == 0) { - mp_raise_OSError(MP_EINVAL); - } - int binaddr_len = 0; - switch (family) { - case AF_INET: - binaddr_len = sizeof(struct in_addr); - break; - case AF_INET6: - binaddr_len = sizeof(struct in6_addr); - break; - } - return mp_obj_new_bytes(binaddr, binaddr_len); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_socket_inet_pton_obj, mod_socket_inet_pton); - -STATIC mp_obj_t mod_socket_inet_ntop(mp_obj_t family_in, mp_obj_t binaddr_in) { - int family = mp_obj_get_int(family_in); - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(binaddr_in, &bufinfo, MP_BUFFER_READ); - vstr_t vstr; - vstr_init_len(&vstr, family == AF_INET ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN); - if (inet_ntop(family, bufinfo.buf, vstr.buf, vstr.len) == NULL) { - mp_raise_OSError(errno); - } - vstr.len = strlen(vstr.buf); - return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_socket_inet_ntop_obj, mod_socket_inet_ntop); - -STATIC mp_obj_t mod_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { - // TODO: Implement 5th and 6th args - - const char *host = mp_obj_str_get_str(args[0]); - const char *serv = NULL; - struct addrinfo hints; - char buf[6]; - memset(&hints, 0, sizeof(hints)); - // getaddrinfo accepts port in string notation, so however - // it may seem stupid, we need to convert int to str - if (mp_obj_is_small_int(args[1])) { - unsigned port = (unsigned short)MP_OBJ_SMALL_INT_VALUE(args[1]); - snprintf(buf, sizeof(buf), "%u", port); - serv = buf; - hints.ai_flags = AI_NUMERICSERV; - #ifdef __UCLIBC_MAJOR__ - #if __UCLIBC_MAJOR__ == 0 && (__UCLIBC_MINOR__ < 9 || (__UCLIBC_MINOR__ == 9 && __UCLIBC_SUBLEVEL__ <= 32)) -// "warning" requires -Wno-cpp which is a relatively new gcc option, so we choose not to use it. -// #warning Working around uClibc bug with numeric service name - // Older versions og uClibc have bugs when numeric ports in service - // arg require also hints.ai_socktype (or hints.ai_protocol) != 0 - // This actually was fixed in 0.9.32.1, but uClibc doesn't allow to - // test for that. - // http://git.uclibc.org/uClibc/commit/libc/inet/getaddrinfo.c?id=bc3be18145e4d5 - // Note that this is crude workaround, precluding UDP socket addresses - // to be returned. TODO: set only if not set by Python args. - hints.ai_socktype = SOCK_STREAM; - #endif - #endif - } else { - serv = mp_obj_str_get_str(args[1]); - } - - if (n_args > 2) { - hints.ai_family = MP_OBJ_SMALL_INT_VALUE(args[2]); - if (n_args > 3) { - hints.ai_socktype = MP_OBJ_SMALL_INT_VALUE(args[3]); - } - } - - struct addrinfo *addr_list; - int res = getaddrinfo(host, serv, &hints, &addr_list); - - if (res != 0) { - // CPython: socket.gaierror - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, translate("[addrinfo error %d]"), res)); - } - assert(addr_list); - - mp_obj_t list = mp_obj_new_list(0, NULL); - for (struct addrinfo *addr = addr_list; addr; addr = addr->ai_next) { - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); - t->items[0] = MP_OBJ_NEW_SMALL_INT(addr->ai_family); - t->items[1] = MP_OBJ_NEW_SMALL_INT(addr->ai_socktype); - t->items[2] = MP_OBJ_NEW_SMALL_INT(addr->ai_protocol); - // "canonname will be a string representing the canonical name of the host - // if AI_CANONNAME is part of the flags argument; else canonname will be empty." ?? - if (addr->ai_canonname) { - t->items[3] = MP_OBJ_NEW_QSTR(qstr_from_str(addr->ai_canonname)); - } else { - t->items[3] = mp_const_none; - } - t->items[4] = mp_obj_new_bytearray(addr->ai_addrlen, addr->ai_addr); - mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); - } - freeaddrinfo(addr_list); - return list; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_socket_getaddrinfo_obj, 2, 4, mod_socket_getaddrinfo); - -STATIC mp_obj_t mod_socket_sockaddr(mp_obj_t sockaddr_in) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(sockaddr_in, &bufinfo, MP_BUFFER_READ); - switch (((struct sockaddr *)bufinfo.buf)->sa_family) { - case AF_INET: { - struct sockaddr_in *sa = (struct sockaddr_in *)bufinfo.buf; - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); - t->items[0] = MP_OBJ_NEW_SMALL_INT(AF_INET); - t->items[1] = mp_obj_new_bytes((byte *)&sa->sin_addr, sizeof(sa->sin_addr)); - t->items[2] = MP_OBJ_NEW_SMALL_INT(ntohs(sa->sin_port)); - return MP_OBJ_FROM_PTR(t); - } - case AF_INET6: { - struct sockaddr_in6 *sa = (struct sockaddr_in6 *)bufinfo.buf; - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); - t->items[0] = MP_OBJ_NEW_SMALL_INT(AF_INET6); - t->items[1] = mp_obj_new_bytes((byte *)&sa->sin6_addr, sizeof(sa->sin6_addr)); - t->items[2] = MP_OBJ_NEW_SMALL_INT(ntohs(sa->sin6_port)); - t->items[3] = MP_OBJ_NEW_SMALL_INT(ntohl(sa->sin6_flowinfo)); - t->items[4] = MP_OBJ_NEW_SMALL_INT(ntohl(sa->sin6_scope_id)); - return MP_OBJ_FROM_PTR(t); - } - default: { - struct sockaddr *sa = (struct sockaddr *)bufinfo.buf; - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); - t->items[0] = MP_OBJ_NEW_SMALL_INT(sa->sa_family); - t->items[1] = mp_obj_new_bytes((byte *)sa->sa_data, bufinfo.len - offsetof(struct sockaddr, sa_data)); - return MP_OBJ_FROM_PTR(t); - } - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_socket_sockaddr_obj, mod_socket_sockaddr); - -STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usocket) }, - { MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&mp_type_socket) }, - { MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&mod_socket_getaddrinfo_obj) }, - { MP_ROM_QSTR(MP_QSTR_inet_pton), MP_ROM_PTR(&mod_socket_inet_pton_obj) }, - { MP_ROM_QSTR(MP_QSTR_inet_ntop), MP_ROM_PTR(&mod_socket_inet_ntop_obj) }, - { MP_ROM_QSTR(MP_QSTR_sockaddr), MP_ROM_PTR(&mod_socket_sockaddr_obj) }, - -#define C(name) { MP_ROM_QSTR(MP_QSTR_##name), MP_ROM_INT(name) } - C(AF_UNIX), - C(AF_INET), - C(AF_INET6), - C(SOCK_STREAM), - C(SOCK_DGRAM), - C(SOCK_RAW), - - C(MSG_DONTROUTE), - C(MSG_DONTWAIT), - - C(SOL_SOCKET), - C(SO_BROADCAST), - C(SO_ERROR), - C(SO_KEEPALIVE), - C(SO_LINGER), - C(SO_REUSEADDR), -#undef C -}; - -STATIC MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); - -const mp_obj_module_t mp_module_socket = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&mp_module_socket_globals, -}; diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index a5a6ebc71c..7aa755ef6c 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -24,7 +24,15 @@ * THE SOFTWARE. */ -// options to control how MicroPython is built +// Options to control how MicroPython is built for this port, +// overriding defaults in py/mpconfig.h. + +// Variant-specific definitions. +#include "mpconfigvariant.h" + +// The minimal variant's config covers everything. +// If we're building the minimal variant, ignore the rest of this file. +#ifndef MICROPY_UNIX_MINIMAL #define MICROPY_ALLOC_PATH_MAX (PATH_MAX) #define MICROPY_PERSISTENT_CODE_LOAD (1) @@ -62,9 +70,13 @@ #define MICROPY_REPL_AUTO_INDENT (1) #define MICROPY_HELPER_LEXER_UNIX (1) #define MICROPY_ENABLE_SOURCE_LINE (1) +#ifndef MICROPY_FLOAT_IMPL #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +#endif #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#ifndef MICROPY_STREAMS_NON_BLOCK #define MICROPY_STREAMS_NON_BLOCK (1) +#endif #define MICROPY_STREAMS_POSIX_API (1) #define MICROPY_OPT_COMPUTED_GOTO (1) #ifndef MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE @@ -72,6 +84,7 @@ #endif #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_VFS_POSIX_FILE (1) #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) @@ -90,17 +103,20 @@ #define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) #define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) +#define MICROPY_PY_BUILTINS_SLICE_INDICES (1) #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_ATEXIT (1) #if MICROPY_PY_SYS_SETTRACE #define MICROPY_PERSISTENT_CODE_SAVE (1) #define MICROPY_COMP_CONST (0) #endif +#ifndef MICROPY_PY_SYS_PLATFORM #if defined(__APPLE__) && defined(__MACH__) #define MICROPY_PY_SYS_PLATFORM "darwin" #else #define MICROPY_PY_SYS_PLATFORM "linux" #endif +#endif #define MICROPY_PY_SYS_MAXSIZE (1) #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_PY_SYS_EXC_INFO (1) @@ -159,16 +175,14 @@ #define MICROPY_ERROR_PRINTER (&mp_stderr_print) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) +// VFS stat functions should return time values relative to 1970/1/1 +#define MICROPY_EPOCH_IS_1970 (1) + extern const struct _mp_print_t mp_stderr_print; -// Define to 1 to use undertested inefficient GC helper implementation -// (if more efficient arch-specific one is not available). -#ifndef MICROPY_GCREGS_SETJMP - #ifdef __mips__ - #define MICROPY_GCREGS_SETJMP (1) - #else - #define MICROPY_GCREGS_SETJMP (0) - #endif +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +// Fall back to setjmp() implementation for discovery of GC pointers in registers. +#define MICROPY_GCREGS_SETJMP (1) #endif #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) @@ -176,6 +190,9 @@ extern const struct _mp_print_t mp_stderr_print; #define MICROPY_KBD_EXCEPTION (1) #define MICROPY_ASYNC_KBD_INTR (1) +#define mp_type_fileio mp_type_vfs_posix_fileio +#define mp_type_textio mp_type_vfs_posix_textio + extern const struct _mp_obj_module_t mp_module_machine; extern const struct _mp_obj_module_t mp_module_os; extern const struct _mp_obj_module_t mp_module_uos_vfs; @@ -273,8 +290,6 @@ void mp_unix_mark_exec(void); #define MICROPY_FORCE_PLAT_ALLOC_EXEC (1) #endif -#define MP_PLAT_PRINT_STRN(str, len) do { ssize_t ret = write(1, str, len); (void)ret; } while (0) - #ifdef __linux__ // Can access physical memory using /dev/mem #define MICROPY_PLAT_DEV_MEM (1) @@ -331,6 +346,14 @@ void mp_unix_mark_exec(void); #include #endif -#define MICROPY_PY_BUILTINS_HELP (1) -#define MICROPY_PY_BUILTINS_HELP_MODULES (1) -#define CIRCUITPY_RE_DEBUG (1) +#if MICROPY_PY_THREAD +#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0) +#define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() +#endif + +#define MICROPY_EVENT_POLL_HOOK mp_hal_delay_us(500); + +#include +#define MICROPY_UNIX_MACHINE_IDLE sched_yield(); + +#endif // MICROPY_UNIX_MINIMAL diff --git a/ports/unix/mpconfigport.mk b/ports/unix/mpconfigport.mk index c8155fc732..78a758b769 100644 --- a/ports/unix/mpconfigport.mk +++ b/ports/unix/mpconfigport.mk @@ -18,16 +18,15 @@ MICROPY_PY_THREAD = 1 MICROPY_PY_TERMIOS = 1 # Subset of CPython socket module -MICROPY_PY_SOCKET = 1 +MICROPY_PY_SOCKET = 0 # ffi module requires libffi (libffi-dev Debian package) MICROPY_PY_FFI = 1 # ussl module requires one of the TLS libraries below MICROPY_PY_USSL = 0 -# axTLS has minimal size and fully integrated with MicroPython, but -# implements only a subset of modern TLS functionality, so may have -# problems with some servers. +# axTLS has minimal size but implements only a subset of modern TLS +# functionality, so may have problems with some servers. MICROPY_SSL_AXTLS = 0 # mbedTLS is more up to date and complete implementation, but also # more bloated. diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 898e63dc98..46f2ccd105 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -23,6 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include #include @@ -37,7 +38,21 @@ bool mp_hal_is_interrupted(void); void mp_hal_stdio_mode_raw(void); void mp_hal_stdio_mode_orig(void); -#if MICROPY_USE_READLINE == 1 && MICROPY_PY_BUILTINS_INPUT +#if MICROPY_PY_BUILTINS_INPUT && MICROPY_USE_READLINE == 0 + +#include +#include "py/misc.h" +#include "input.h" +#define mp_hal_readline mp_hal_readline +static inline int mp_hal_readline(vstr_t *vstr, const char *p) { + char *line = prompt((char *)p); + vstr_add_str(vstr, line); + free(line); + return 0; +} + +#elif MICROPY_PY_BUILTINS_INPUT && MICROPY_USE_READLINE == 1 + #include "py/misc.h" #include "lib/mp-readline/readline.h" // For built-in input() we need to wrap the standard readline() to enable raw mode @@ -48,6 +63,7 @@ static inline int mp_hal_readline(vstr_t *vstr, const char *p) { mp_hal_stdio_mode_orig(); return ret; } + #endif // TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep: @@ -60,6 +76,24 @@ static inline void mp_hal_delay_us(mp_uint_t us) { } #define mp_hal_ticks_cpu() 0 +// This macro is used to implement PEP 475 to retry specified syscalls on EINTR +#define MP_HAL_RETRY_SYSCALL(ret, syscall, raise) { \ + for (;;) { \ + MP_THREAD_GIL_EXIT(); \ + ret = syscall; \ + MP_THREAD_GIL_ENTER(); \ + if (ret == -1) { \ + int err = errno; \ + if (err == EINTR) { \ + mp_handle_pending(true); \ + continue; \ + } \ + raise; \ + } \ + break; \ + } \ +} + #define RAISE_ERRNO(err_flag, error_val) \ { if (err_flag == -1) \ { mp_raise_OSError(error_val); } } diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 40545485db..607ff2955f 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -39,6 +39,19 @@ #include #include +#include "lib/utils/gchelper.h" + +// Some platforms don't have SIGRTMIN but if we do have it, use it to avoid +// potential conflict with other uses of the more commonly used SIGUSR1. +#ifdef SIGRTMIN +#define MP_THREAD_GC_SIGNAL (SIGRTMIN + 5) +#else +#define MP_THREAD_GC_SIGNAL (SIGUSR1) +#endif + +// This value seems to be about right for both 32-bit and 64-bit builds. +#define THREAD_STACK_OVERFLOW_MARGIN (8192) + // this structure forms a linked list, one node per active thread typedef struct _thread_t { pthread_t id; // system id of thread @@ -49,8 +62,10 @@ typedef struct _thread_t { STATIC pthread_key_t tls_key; -// the mutex controls access to the linked list -STATIC pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; +// The mutex is used for any code in this port that needs to be thread safe. +// Specifically for thread management, access to the linked list is one example. +// But also, e.g. scheduler state. +STATIC pthread_mutex_t thread_mutex; STATIC thread_t *thread; // this is used to synchronise the signal handler of the thread @@ -62,13 +77,20 @@ STATIC sem_t *thread_signal_done_p; STATIC sem_t thread_signal_done; #endif +void mp_thread_unix_begin_atomic_section(void) { + pthread_mutex_lock(&thread_mutex); +} + +void mp_thread_unix_end_atomic_section(void) { + pthread_mutex_unlock(&thread_mutex); +} + // this signal handler is used to scan the regs and stack of a thread STATIC void mp_thread_gc(int signo, siginfo_t *info, void *context) { (void)info; // unused (void)context; // unused - if (signo == SIGUSR1) { - void gc_collect_regs_and_stack(void); - gc_collect_regs_and_stack(); + if (signo == MP_THREAD_GC_SIGNAL) { + gc_helper_collect_regs_and_stack(); // We have access to the context (regs, stack) of the thread but it seems // that we don't need the extra information, enough is captured by the // gc_collect_regs_and_stack function above @@ -89,6 +111,13 @@ void mp_thread_init(void) { pthread_key_create(&tls_key, NULL); pthread_setspecific(tls_key, &mp_state_ctx.thread); + // Needs to be a recursive mutex to emulate the behavior of + // BEGIN_ATOMIC_SECTION on bare metal. + pthread_mutexattr_t thread_mutex_attr; + pthread_mutexattr_init(&thread_mutex_attr); + pthread_mutexattr_settype(&thread_mutex_attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&thread_mutex, &thread_mutex_attr); + // create first entry in linked list of all threads thread = malloc(sizeof(thread_t)); thread->id = pthread_self(); @@ -108,18 +137,18 @@ void mp_thread_init(void) { sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = mp_thread_gc; sigemptyset(&sa.sa_mask); - sigaction(SIGUSR1, &sa, NULL); + sigaction(MP_THREAD_GC_SIGNAL, &sa, NULL); } void mp_thread_deinit(void) { - pthread_mutex_lock(&thread_mutex); + mp_thread_unix_begin_atomic_section(); while (thread->next != NULL) { thread_t *th = thread; thread = thread->next; pthread_cancel(th->id); free(th); } - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); #if defined(__APPLE__) sem_close(thread_signal_done_p); sem_unlink(thread_signal_done_name); @@ -135,7 +164,7 @@ void mp_thread_deinit(void) { // the global root pointers (in mp_state_ctx) while another thread is doing a // garbage collection and tracing these pointers. void mp_thread_gc_others(void) { - pthread_mutex_lock(&thread_mutex); + mp_thread_unix_begin_atomic_section(); for (thread_t *th = thread; th != NULL; th = th->next) { gc_collect_root(&th->arg, 1); if (th->id == pthread_self()) { @@ -144,34 +173,34 @@ void mp_thread_gc_others(void) { if (!th->ready) { continue; } - pthread_kill(th->id, SIGUSR1); + pthread_kill(th->id, MP_THREAD_GC_SIGNAL); #if defined(__APPLE__) sem_wait(thread_signal_done_p); #else sem_wait(&thread_signal_done); #endif } - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); } mp_state_thread_t *mp_thread_get_state(void) { return (mp_state_thread_t *)pthread_getspecific(tls_key); } -void mp_thread_set_state(void *state) { +void mp_thread_set_state(mp_state_thread_t *state) { pthread_setspecific(tls_key, state); } void mp_thread_start(void) { pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); - pthread_mutex_lock(&thread_mutex); + mp_thread_unix_begin_atomic_section(); for (thread_t *th = thread; th != NULL; th = th->next) { if (th->id == pthread_self()) { th->ready = 1; break; } } - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); } void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { @@ -185,6 +214,11 @@ void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { *stack_size = PTHREAD_STACK_MIN; } + // ensure there is enough stack to include a stack-overflow margin + if (*stack_size < 2 * THREAD_STACK_OVERFLOW_MARGIN) { + *stack_size = 2 * THREAD_STACK_OVERFLOW_MARGIN; + } + // set thread attributes pthread_attr_t attr; int ret = pthread_attr_init(&attr); @@ -201,19 +235,18 @@ void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { goto er; } - pthread_mutex_lock(&thread_mutex); + mp_thread_unix_begin_atomic_section(); // create thread pthread_t id; ret = pthread_create(&id, &attr, entry, arg); if (ret != 0) { - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); goto er; } // adjust stack_size to provide room to recover from hitting the limit - // this value seems to be about right for both 32-bit and 64-bit builds - *stack_size -= 8192; + *stack_size -= THREAD_STACK_OVERFLOW_MARGIN; // add thread to linked list of all threads thread_t *th = malloc(sizeof(thread_t)); @@ -223,7 +256,7 @@ void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { th->next = thread; thread = th; - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); return; @@ -232,7 +265,7 @@ er: } void mp_thread_finish(void) { - pthread_mutex_lock(&thread_mutex); + mp_thread_unix_begin_atomic_section(); thread_t *prev = NULL; for (thread_t *th = thread; th != NULL; th = th->next) { if (th->id == pthread_self()) { @@ -246,7 +279,7 @@ void mp_thread_finish(void) { } prev = th; } - pthread_mutex_unlock(&thread_mutex); + mp_thread_unix_end_atomic_section(); } void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { diff --git a/ports/unix/mpthreadport.h b/ports/unix/mpthreadport.h index c27144a9c8..94edbe2f04 100644 --- a/ports/unix/mpthreadport.h +++ b/ports/unix/mpthreadport.h @@ -31,3 +31,8 @@ typedef pthread_mutex_t mp_thread_mutex_t; void mp_thread_init(void); void mp_thread_deinit(void); void mp_thread_gc_others(void); + +// Unix version of "enable/disable IRQs". +// Functions as a port-global lock for any code that must be serialised. +void mp_thread_unix_begin_atomic_section(void); +void mp_thread_unix_end_atomic_section(void); diff --git a/ports/unix/qstrdefsport.h b/ports/unix/qstrdefsport.h index 873e832720..0c99d91c6e 100644 --- a/ports/unix/qstrdefsport.h +++ b/ports/unix/qstrdefsport.h @@ -23,3 +23,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + +// *FORMAT-OFF* diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index ea81935ad7..d19f3f717c 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -31,6 +31,7 @@ #include #include "py/mphal.h" +#include "py/mpthread.h" #include "py/runtime.h" #ifndef _WIN32 @@ -39,6 +40,11 @@ STATIC void sighandler(int signum) { if (signum == SIGINT) { #if MICROPY_ASYNC_KBD_INTR + #if MICROPY_PY_THREAD_GIL + // Since signals can occur at any time, we may not be holding the GIL when + // this callback is called, so it is not safe to raise an exception here + #error "MICROPY_ASYNC_KBD_INTR and MICROPY_PY_THREAD_GIL are not compatible" + #endif mp_obj_exception_clear_traceback(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); sigset_t mask; sigemptyset(&mask); @@ -51,8 +57,7 @@ STATIC void sighandler(int signum) { // this is the second time we are called, so die straight away exit(1); } - mp_obj_exception_clear_traceback(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)); + mp_keyboard_interrupt(); #endif } } @@ -113,9 +118,10 @@ void mp_hal_stdio_mode_orig(void) { int mp_hal_stdin_rx_chr(void) { unsigned char c; - int ret = read(0, &c, 1); + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, read(STDIN_FILENO, &c, 1), {}); if (ret == 0) { - c = 4; // EOF, ctrl-D + c = 4; // EOF, ctrl-D } else if (c == '\n') { c = '\r'; } @@ -123,8 +129,8 @@ int mp_hal_stdin_rx_chr(void) { } void mp_hal_stdout_tx_strn(const char *str, size_t len) { - int ret = write(1, str, len); - (void)ret; // to suppress compiler warning + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDOUT_FILENO, str, len), {}); } // cooked is same as uncooked because the terminal does some postprocessing @@ -159,3 +165,8 @@ mp_uint_t mp_hal_ticks_us(void) { return tv.tv_sec * 1000000 + tv.tv_usec; #endif } + +uint64_t mp_hal_time_ns(void) { + time_t now = time(NULL); + return (uint64_t)now * 1000000000ULL; +} diff --git a/ports/unix/coverage-frzmpy/frzmpy1.py b/ports/unix/variants/coverage/frzmpy/frzmpy1.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy1.py rename to ports/unix/variants/coverage/frzmpy/frzmpy1.py diff --git a/ports/unix/coverage-frzmpy/frzmpy2.py b/ports/unix/variants/coverage/frzmpy/frzmpy2.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy2.py rename to ports/unix/variants/coverage/frzmpy/frzmpy2.py diff --git a/ports/unix/coverage-frzmpy/frzmpy_pkg1/__init__.py b/ports/unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy_pkg1/__init__.py rename to ports/unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py diff --git a/ports/unix/coverage-frzmpy/frzmpy_pkg2/mod.py b/ports/unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy_pkg2/mod.py rename to ports/unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py diff --git a/ports/unix/coverage-frzstr/frzstr1.py b/ports/unix/variants/coverage/frzstr/frzstr1.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr1.py rename to ports/unix/variants/coverage/frzstr/frzstr1.py diff --git a/ports/unix/coverage-frzstr/frzstr_pkg1/__init__.py b/ports/unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr_pkg1/__init__.py rename to ports/unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py diff --git a/ports/unix/coverage-frzstr/frzstr_pkg2/mod.py b/ports/unix/variants/coverage/frzstr/frzstr_pkg2/mod.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr_pkg2/mod.py rename to ports/unix/variants/coverage/frzstr/frzstr_pkg2/mod.py diff --git a/ports/unix/variants/coverage/manifest.py b/ports/unix/variants/coverage/manifest.py new file mode 100644 index 0000000000..6111050884 --- /dev/null +++ b/ports/unix/variants/coverage/manifest.py @@ -0,0 +1,2 @@ +freeze_as_str("frzstr") +freeze_as_mpy("frzmpy") diff --git a/ports/unix/mpconfigport_coverage.h b/ports/unix/variants/coverage/mpconfigvariant.h similarity index 89% rename from ports/unix/mpconfigport_coverage.h rename to ports/unix/variants/coverage/mpconfigvariant.h index c619e1fa5f..d16c85e2c2 100644 --- a/ports/unix/mpconfigport_coverage.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -24,18 +24,18 @@ * THE SOFTWARE. */ -// Default unix config while intended to be comprehensive, may still not enable -// all the features, this config should enable more (testable) options. +// This config enables almost all possible features such that it can be used +// for coverage testing. #define MICROPY_VFS (1) #define MICROPY_PY_UOS_VFS (1) -#include - #define MICROPY_OPT_MATH_FACTORIAL (1) #define MICROPY_FLOAT_HIGH_QUALITY_HASH (1) #define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_READER_VFS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) #define MICROPY_WARNINGS_CATEGORY (1) #define MICROPY_MODULE_GETATTR (1) #define MICROPY_PY_DELATTR_SETATTR (1) @@ -50,6 +50,7 @@ #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) #define MICROPY_PY_IO_BUFFEREDWRITER (1) #define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_UASYNCIO (1) #define MICROPY_PY_URE_DEBUG (1) #define MICROPY_PY_URE_MATCH_GROUPS (1) #define MICROPY_PY_URE_MATCH_SPAN_START_END (1) @@ -61,10 +62,7 @@ #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) #define MICROPY_PY_UCRYPTOLIB (1) #define MICROPY_PY_UCRYPTOLIB_CTR (1) - -// TODO these should be generic, not bound to fatfs -#define mp_type_fileio mp_type_vfs_posix_fileio -#define mp_type_textio mp_type_vfs_posix_textio +#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (1) // use vfs's functions for import stat and builtin open #define mp_import_stat mp_vfs_import_stat diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk new file mode 100644 index 0000000000..57bf36509c --- /dev/null +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -0,0 +1,19 @@ +PROG ?= micropython-coverage + +# Disable optimisations and enable assert() on coverage builds. +DEBUG ?= 1 + +CFLAGS += \ + -fprofile-arcs -ftest-coverage \ + -Wformat -Wmissing-declarations -Wmissing-prototypes \ + -Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \ + -DMICROPY_UNIX_COVERAGE + +LDFLAGS += -fprofile-arcs -ftest-coverage + +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 + +FROZEN_DIR=variants/coverage/frzstr +FROZEN_MPY_DIR=variants/coverage/frzmpy diff --git a/ports/unix/variants/dev/manifest.py b/ports/unix/variants/dev/manifest.py new file mode 100644 index 0000000000..92a681116a --- /dev/null +++ b/ports/unix/variants/dev/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/variants/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") diff --git a/ports/unix/variants/dev/mpconfigvariant.h b/ports/unix/variants/dev/mpconfigvariant.h new file mode 100644 index 0000000000..7c3e84cc44 --- /dev/null +++ b/ports/unix/variants/dev/mpconfigvariant.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define MICROPY_READER_VFS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_VFS (1) +#define MICROPY_VFS_POSIX (1) + +#define MICROPY_PY_SYS_SETTRACE (1) +#define MICROPY_PY_UOS_VFS (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) + +#ifndef MICROPY_PY_UASYNCIO +#define MICROPY_PY_UASYNCIO (1) +#endif + +// Use vfs's functions for import stat and builtin open. +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj diff --git a/ports/unix/variants/dev/mpconfigvariant.mk b/ports/unix/variants/dev/mpconfigvariant.mk new file mode 100644 index 0000000000..1f8611b6fc --- /dev/null +++ b/ports/unix/variants/dev/mpconfigvariant.mk @@ -0,0 +1,9 @@ +PROG ?= micropython-dev + +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py + +MICROPY_ROM_TEXT_COMPRESSION = 1 +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 +MICROPY_PY_BLUETOOTH = 1 diff --git a/ports/unix/mpconfigport_fast.h b/ports/unix/variants/fast/mpconfigvariant.h similarity index 98% rename from ports/unix/mpconfigport_fast.h rename to ports/unix/variants/fast/mpconfigvariant.h index 0c197d5dc0..ba9981db1b 100644 --- a/ports/unix/mpconfigport_fast.h +++ b/ports/unix/variants/fast/mpconfigvariant.h @@ -28,7 +28,6 @@ // synthetic benchmarking, at the expense of features supported and memory // usage. This config is not intended to be used in production. -#include #define MICROPY_PY___FILE__ (0) // 91 is a magic number proposed by @dpgeorge, which make pystone run ~ at tie // with CPython 3.4. diff --git a/ports/unix/variants/fast/mpconfigvariant.mk b/ports/unix/variants/fast/mpconfigvariant.mk new file mode 100644 index 0000000000..595e575645 --- /dev/null +++ b/ports/unix/variants/fast/mpconfigvariant.mk @@ -0,0 +1,7 @@ +# build synthetically fast interpreter for benchmarking + +COPT += -fno-crossjumping -O2 + +PROG = micropython-fast + +FROZEN_MANIFEST = diff --git a/ports/unix/mpconfigport_freedos.h b/ports/unix/variants/freedos/mpconfigvariant.h similarity index 94% rename from ports/unix/mpconfigport_freedos.h rename to ports/unix/variants/freedos/mpconfigvariant.h index 19c73b7623..9e93661ddf 100644 --- a/ports/unix/mpconfigport_freedos.h +++ b/ports/unix/variants/freedos/mpconfigvariant.h @@ -26,12 +26,10 @@ // options to control how MicroPython is built -#include +#define MICROPY_PY_USELECT_POSIX (0) -#undef MICROPY_STREAMS_NON_BLOCK #define MICROPY_STREAMS_NON_BLOCK (0) -#undef MICROPY_PY_SYS_PLATFORM #define MICROPY_PY_SYS_PLATFORM "freedos" // djgpp dirent struct does not have d_ino field diff --git a/ports/unix/variants/freedos/mpconfigvariant.mk b/ports/unix/variants/freedos/mpconfigvariant.mk new file mode 100644 index 0000000000..a30db3e0c1 --- /dev/null +++ b/ports/unix/variants/freedos/mpconfigvariant.mk @@ -0,0 +1,20 @@ +CC = i586-pc-msdosdjgpp-gcc + +STRIP = i586-pc-msdosdjgpp-strip + +SIZE = i586-pc-msdosdjgpp-size + +CFLAGS += \ + -DMICROPY_NLR_SETJMP \ + -Dtgamma=gamma \ + -DMICROPY_EMIT_X86=0 \ + -DMICROPY_NO_ALLOCA=1 \ + +PROG = micropython-freedos + +MICROPY_PY_SOCKET = 0 +MICROPY_PY_FFI = 0 +MICROPY_PY_JNI = 0 +MICROPY_PY_BTREE = 0 +MICROPY_PY_THREAD = 0 +MICROPY_PY_USSL = 0 diff --git a/ports/unix/variants/manifest.py b/ports/unix/variants/manifest.py new file mode 100644 index 0000000000..7708e598db --- /dev/null +++ b/ports/unix/variants/manifest.py @@ -0,0 +1,2 @@ +freeze_as_mpy("$(MPY_DIR)/tools", "upip.py") +freeze_as_mpy("$(MPY_DIR)/tools", "upip_utarfile.py", opt=3) diff --git a/ports/unix/mpconfigport_minimal.h b/ports/unix/variants/minimal/mpconfigvariant.h similarity index 93% rename from ports/unix/mpconfigport_minimal.h rename to ports/unix/variants/minimal/mpconfigvariant.h index 3977818b03..a959a95059 100644 --- a/ports/unix/mpconfigport_minimal.h +++ b/ports/unix/variants/minimal/mpconfigvariant.h @@ -26,6 +26,9 @@ // options to control how MicroPython is built +// Prevent the rest of the default mpconfigport.h being used. +#define MICROPY_UNIX_MINIMAL (1) + #define MICROPY_ALLOC_QSTR_CHUNK_INIT (64) #define MICROPY_ALLOC_PARSE_RULE_INIT (8) #define MICROPY_ALLOC_PARSE_RULE_INC (8) @@ -107,14 +110,9 @@ extern const struct _mp_obj_module_t mp_module_os; // Do not change anything beyond this line ////////////////////////////////////////// -// Define to 1 to use undertested inefficient GC helper implementation -// (if more efficient arch-specific one is not available). -#ifndef MICROPY_GCREGS_SETJMP - #ifdef __mips__ - #define MICROPY_GCREGS_SETJMP (1) - #else - #define MICROPY_GCREGS_SETJMP (0) - #endif +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +// Fall back to setjmp() implementation for discovery of GC pointers in registers. +#define MICROPY_GCREGS_SETJMP (1) #endif // type definitions for the specific machine diff --git a/ports/unix/variants/minimal/mpconfigvariant.mk b/ports/unix/variants/minimal/mpconfigvariant.mk new file mode 100644 index 0000000000..ec3b21c0b9 --- /dev/null +++ b/ports/unix/variants/minimal/mpconfigvariant.mk @@ -0,0 +1,13 @@ +# build a minimal interpreter +PROG = micropython-minimal + +FROZEN_MANIFEST = + +MICROPY_ROM_TEXT_COMPRESSION = 1 +MICROPY_PY_BTREE = 0 +MICROPY_PY_FFI = 0 +MICROPY_PY_SOCKET = 0 +MICROPY_PY_THREAD = 0 +MICROPY_PY_TERMIOS = 0 +MICROPY_PY_USSL = 0 +MICROPY_USE_READLINE = 0 diff --git a/ports/unix/variants/nanbox/mpconfigvariant.h b/ports/unix/variants/nanbox/mpconfigvariant.h new file mode 100644 index 0000000000..f827158fb7 --- /dev/null +++ b/ports/unix/variants/nanbox/mpconfigvariant.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// This config is mostly used to ensure that the nan-boxing object model +// continues to build (i.e. catches usage of mp_obj_t that don't work with +// this representation). + +// select nan-boxing object model +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_D) + +// native emitters don't work with nan-boxing +#define MICROPY_EMIT_X86 (0) +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_ARM (0) + +#include + +typedef int64_t mp_int_t; +typedef uint64_t mp_uint_t; +#define UINT_FMT "%llu" +#define INT_FMT "%lld" diff --git a/ports/unix/variants/nanbox/mpconfigvariant.mk b/ports/unix/variants/nanbox/mpconfigvariant.mk new file mode 100644 index 0000000000..9752b922c1 --- /dev/null +++ b/ports/unix/variants/nanbox/mpconfigvariant.mk @@ -0,0 +1,4 @@ +# build interpreter with nan-boxing as object model (object repr D) +PROG = micropython-nanbox + +MICROPY_FORCE_32BIT = 1 diff --git a/ports/unix/fdfile.h b/ports/unix/variants/standard/mpconfigvariant.h similarity index 73% rename from ports/unix/fdfile.h rename to ports/unix/variants/standard/mpconfigvariant.h index d7da673be9..2868f949cc 100644 --- a/ports/unix/fdfile.h +++ b/ports/unix/variants/standard/mpconfigvariant.h @@ -3,8 +3,7 @@ * * The MIT License (MIT) * - * SPDX-FileCopyrightText: Copyright (c) 2013, 2014 Damien P. George - * Copyright (c) 2016 Paul Sokolovsky + * Copyright (c) 2019 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,17 +23,3 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_UNIX_FDFILE_H -#define MICROPY_INCLUDED_UNIX_FDFILE_H - -#include "py/obj.h" - -typedef struct _mp_obj_fdfile_t { - mp_obj_base_t base; - int fd; -} mp_obj_fdfile_t; - -extern const mp_obj_type_t mp_type_fileio; -extern const mp_obj_type_t mp_type_textio; - -#endif // MICROPY_INCLUDED_UNIX_FDFILE_H diff --git a/ports/unix/variants/standard/mpconfigvariant.mk b/ports/unix/variants/standard/mpconfigvariant.mk new file mode 100644 index 0000000000..cf3efab8ae --- /dev/null +++ b/ports/unix/variants/standard/mpconfigvariant.mk @@ -0,0 +1,3 @@ +# This is the default variant when you `make` the Unix port. + +PROG ?= micropython diff --git a/py/argcheck.c b/py/argcheck.c index 684fb204f0..e26d6114ba 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -40,11 +40,11 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { 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(translate("function doesn't take keyword arguments")); - } + #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) { @@ -52,8 +52,8 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else - mp_raise_TypeError_varg( - translate("function takes %d positional arguments but %d were given"), + 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 } @@ -62,16 +62,16 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else - mp_raise_TypeError_varg( - translate("function missing %d required positional arguments"), + 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_TypeError_varg( - translate("function expected at most %d arguments, got %d"), + mp_raise_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("function expected at most %d arguments, got %d"), n_args_max, n_args); #endif } @@ -107,8 +107,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else - mp_raise_TypeError_varg( - translate("'%q' argument required"), allowed[i].qst); + mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("'%q' argument required"), allowed[i].qst); #endif } out_vals[i] = allowed[i].defval; @@ -133,7 +132,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n mp_arg_error_terse_mismatch(); #else // TODO better error message - mp_raise_TypeError(translate("extra positional arguments given")); + mp_raise_TypeError(MP_ERROR_TEXT("extra positional arguments given")); #endif } if (kws_found < kws->used) { @@ -141,7 +140,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n mp_arg_error_terse_mismatch(); #else // TODO better error message - mp_raise_TypeError(translate("extra keyword arguments given")); + mp_raise_TypeError(MP_ERROR_TEXT("extra keyword arguments given")); #endif } } @@ -153,11 +152,11 @@ void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, } NORETURN void mp_arg_error_terse_mismatch(void) { - mp_raise_TypeError(translate("argument num/types mismatch")); + 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(translate("keyword argument(s) not yet implemented - use normal args instead")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("keyword argument(s) not yet implemented - use normal args instead")); } #endif diff --git a/py/asmarm.c b/py/asmarm.c index c42d61cb1c..30ab546ae6 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -303,6 +303,11 @@ void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint 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); diff --git a/py/asmarm.h b/py/asmarm.h index 7ec4490599..3efb8042d3 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -101,6 +101,7 @@ void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num); void asm_arm_mov_reg_pcrel(asm_arm_t *as, uint reg_dest, uint label); void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint rs); +void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs); void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs); // memory @@ -187,6 +188,7 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_arm_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_arm_lsl_reg_reg((as), (reg_dest), (reg_shift)) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_arm_lsr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_arm_asr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_arm_orr_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_arm_eor_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) diff --git a/py/asmthumb.h b/py/asmthumb.h index c37358e886..ba193fbab4 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -80,12 +80,19 @@ void asm_thumb_exit(asm_thumb_t *as); #define ASM_THUMB_OP_IT (0xbf00) #define ASM_THUMB_OP_ITE_EQ (0xbf0c) +#define ASM_THUMB_OP_ITE_NE (0xbf14) #define ASM_THUMB_OP_ITE_CS (0xbf2c) +#define ASM_THUMB_OP_ITE_CC (0xbf34) #define ASM_THUMB_OP_ITE_MI (0xbf4c) +#define ASM_THUMB_OP_ITE_PL (0xbf54) #define ASM_THUMB_OP_ITE_VS (0xbf6c) +#define ASM_THUMB_OP_ITE_VC (0xbf74) #define ASM_THUMB_OP_ITE_HI (0xbf8c) +#define ASM_THUMB_OP_ITE_LS (0xbf94) #define ASM_THUMB_OP_ITE_GE (0xbfac) +#define ASM_THUMB_OP_ITE_LT (0xbfb4) #define ASM_THUMB_OP_ITE_GT (0xbfcc) +#define ASM_THUMB_OP_ITE_LE (0xbfd4) #define ASM_THUMB_OP_NOP (0xbf00) #define ASM_THUMB_OP_WFI (0xbf30) @@ -345,6 +352,7 @@ void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenien #define ASM_MOV_REG_PCREL(as, rlo_dest, label) asm_thumb_mov_reg_pcrel((as), (rlo_dest), (label)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSL, (reg_dest), (reg_shift)) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSR, (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ASR, (reg_dest), (reg_shift)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ORR, (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_EOR, (reg_dest), (reg_src)) diff --git a/py/asmx64.c b/py/asmx64.c index 723671d5a3..fd64eaf98b 100644 --- a/py/asmx64.c +++ b/py/asmx64.c @@ -67,6 +67,7 @@ // #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 */ @@ -382,6 +383,10 @@ 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); } diff --git a/py/asmx64.h b/py/asmx64.h index f4d36154f7..ec9a088f88 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -61,10 +61,13 @@ // condition codes, used for jcc and setcc (despite their j-name!) #define ASM_X64_CC_JB (0x2) // below, unsigned +#define ASM_X64_CC_JAE (0x3) // above or equal, unsigned #define ASM_X64_CC_JZ (0x4) #define ASM_X64_CC_JE (0x4) #define ASM_X64_CC_JNZ (0x5) #define ASM_X64_CC_JNE (0x5) +#define ASM_X64_CC_JBE (0x6) // below or equal, unsigned +#define ASM_X64_CC_JA (0x7) // above, unsigned #define ASM_X64_CC_JL (0xc) // less, signed #define ASM_X64_CC_JGE (0xd) // greater or equal, signed #define ASM_X64_CC_JLE (0xe) // less or equal, signed @@ -98,6 +101,7 @@ void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_shl_r64_cl(asm_x64_t *as, int dest_r64); +void asm_x64_shr_r64_cl(asm_x64_t *as, int dest_r64); void asm_x64_sar_r64_cl(asm_x64_t *as, int dest_r64); void asm_x64_add_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_sub_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); @@ -190,6 +194,7 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x64_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG(as, reg) asm_x64_shl_r64_cl((as), (reg)) +#define ASM_LSR_REG(as, reg) asm_x64_shr_r64_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x64_sar_r64_cl((as), (reg)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_x64_or_r64_r64((as), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_x64_xor_r64_r64((as), (reg_dest), (reg_src)) diff --git a/py/asmx86.c b/py/asmx86.c index c730a0c9a4..b44da76dab 100644 --- a/py/asmx86.c +++ b/py/asmx86.c @@ -67,6 +67,7 @@ // #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 */ @@ -259,6 +260,10 @@ 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); } @@ -403,7 +408,7 @@ void asm_x86_entry(asm_x86_t *as, int num_locals) { 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 |= 1; // make it odd so stack is aligned on 16 byte boundary + 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; } @@ -496,11 +501,14 @@ void asm_x86_push_local_addr(asm_x86_t *as, int local_num, int temp_r32) { #endif void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r32) { - // TODO align stack on 16-byte boundary before the call - assert(n_args <= 5); - if (n_args > 4) { - asm_x86_push_r32(as, ASM_X86_REG_ARG_5); + 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); } @@ -520,7 +528,7 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r // the caller must clean up the stack if (n_args > 0) { - asm_x86_add_i32_to_r32(as, WORD_SIZE * n_args, ASM_X86_REG_ESP); + asm_x86_add_i32_to_r32(as, (n_args + align) * WORD_SIZE, ASM_X86_REG_ESP); } } diff --git a/py/asmx86.h b/py/asmx86.h index 5c2d9df08c..a8fb4e3f9e 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -60,14 +60,16 @@ #define ASM_X86_REG_ARG_2 ASM_X86_REG_ECX #define ASM_X86_REG_ARG_3 ASM_X86_REG_EDX #define ASM_X86_REG_ARG_4 ASM_X86_REG_EBX -#define ASM_X86_REG_ARG_5 ASM_X86_REG_ESI // condition codes, used for jcc and setcc (despite their j-name!) #define ASM_X86_CC_JB (0x2) // below, unsigned +#define ASM_X86_CC_JAE (0x3) // above or equal, unsigned #define ASM_X86_CC_JZ (0x4) #define ASM_X86_CC_JE (0x4) #define ASM_X86_CC_JNZ (0x5) #define ASM_X86_CC_JNE (0x5) +#define ASM_X86_CC_JBE (0x6) // below or equal, unsigned +#define ASM_X86_CC_JA (0x7) // above, unsigned #define ASM_X86_CC_JL (0xc) // less, signed #define ASM_X86_CC_JGE (0xd) // greater or equal, signed #define ASM_X86_CC_JLE (0xe) // less or equal, signed @@ -94,6 +96,7 @@ void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_shl_r32_cl(asm_x86_t *as, int dest_r32); +void asm_x86_shr_r32_cl(asm_x86_t *as, int dest_r32); void asm_x86_sar_r32_cl(asm_x86_t *as, int dest_r32); void asm_x86_add_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_sub_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); @@ -129,7 +132,6 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define REG_ARG_2 ASM_X86_REG_ARG_2 #define REG_ARG_3 ASM_X86_REG_ARG_3 #define REG_ARG_4 ASM_X86_REG_ARG_4 -#define REG_ARG_5 ASM_X86_REG_ARG_5 // caller-save, so can be used as temporaries #define REG_TEMP0 ASM_X86_REG_EAX @@ -187,6 +189,7 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x86_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG(as, reg) asm_x86_shl_r32_cl((as), (reg)) +#define ASM_LSR_REG(as, reg) asm_x86_shr_r32_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x86_sar_r32_cl((as), (reg)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_x86_or_r32_r32((as), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_x86_xor_r32_r32((as), (reg_dest), (reg_src)) diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 6ce107d686..529bfa5f0c 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -243,6 +243,10 @@ static inline void asm_xtensa_op_sll(asm_xtensa_t *as, uint reg_dest, uint reg_s asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 10, reg_dest, reg_src, 0)); } +static inline void asm_xtensa_op_srl(asm_xtensa_t *as, uint reg_dest, uint reg_src) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 9, reg_dest, 0, reg_src)); +} + static inline void asm_xtensa_op_sra(asm_xtensa_t *as, uint reg_dest, uint reg_src) { asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 11, reg_dest, 0, reg_src)); } @@ -372,6 +376,11 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); asm_xtensa_op_ssl((as), (reg_shift)); \ asm_xtensa_op_sll((as), (reg_dest), (reg_dest)); \ } while (0) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) \ + do { \ + asm_xtensa_op_ssr((as), (reg_shift)); \ + asm_xtensa_op_srl((as), (reg_dest), (reg_dest)); \ + } while (0) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) \ do { \ asm_xtensa_op_ssr((as), (reg_shift)); \ diff --git a/py/bc.c b/py/bc.c index ebdb26192d..dff36b15a1 100644 --- a/py/bc.c +++ b/py/bc.c @@ -86,10 +86,10 @@ STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL (void)f; mp_raise_TypeError_varg( - translate("function takes %d positional arguments but %d were given"), expected, given); + MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), expected, given); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED mp_raise_TypeError_varg( - translate("%q() takes %d positional arguments but %d were given"), + 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 } @@ -213,7 +213,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw if (wanted_arg_name == arg_names[j]) { if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) { mp_raise_TypeError_varg( - translate("function got multiple values for argument '%q'"), MP_OBJ_QSTR_VALUE(wanted_arg_name)); + 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; @@ -222,10 +222,10 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw // 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(translate("unexpected keyword argument")); + mp_raise_TypeError(MP_ERROR_TEXT("unexpected keyword argument")); #else mp_raise_TypeError_varg( - translate("unexpected keyword argument '%q'"), MP_OBJ_QSTR_VALUE(wanted_arg_name)); + 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]); @@ -251,7 +251,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw while (d < &code_state->state[n_state]) { if (*d++ == MP_OBJ_NULL) { mp_raise_TypeError_varg( - translate("function missing required positional argument #%d"), &code_state->state[n_state] - d); + MP_ERROR_TEXT("function missing required positional argument #%d"), &code_state->state[n_state] - d); } } @@ -267,7 +267,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw code_state->state[n_state - 1 - n_pos_args - i] = elem->value; } else { mp_raise_TypeError_varg( - translate("function missing required keyword argument '%q'"), + MP_ERROR_TEXT("function missing required keyword argument '%q'"), MP_OBJ_QSTR_VALUE(arg_names[n_pos_args + i])); } } @@ -276,7 +276,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw } else { // no keyword arguments given if (n_kwonly_args != 0) { - mp_raise_TypeError(translate("function missing keyword-only argument")); + 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); diff --git a/py/bc0.h b/py/bc0.h index 4c5f9fd8e0..500dee5e7f 100644 --- a/py/bc0.h +++ b/py/bc0.h @@ -48,15 +48,15 @@ #define MP_BC_BASE_BYTE_O (0x50) // LLLLSSDTTTTTEEFF #define MP_BC_BASE_BYTE_E (0x60) // --BREEEYYI------ #define MP_BC_LOAD_CONST_SMALL_INT_MULTI (0x70) // LLLLLLLLLLLLLLLL -// (0x80) // LLLLLLLLLLLLLLLL -// (0x90) // LLLLLLLLLLLLLLLL -// (0xa0) // LLLLLLLLLLLLLLLL +// (0x80) // LLLLLLLLLLLLLLLL +// (0x90) // LLLLLLLLLLLLLLLL +// (0xa0) // LLLLLLLLLLLLLLLL #define MP_BC_LOAD_FAST_MULTI (0xb0) // LLLLLLLLLLLLLLLL #define MP_BC_STORE_FAST_MULTI (0xc0) // SSSSSSSSSSSSSSSS #define MP_BC_UNARY_OP_MULTI (0xd0) // OOOOOOO #define MP_BC_BINARY_OP_MULTI (0xd7) // OOOOOOOOO -// (0xe0) // OOOOOOOOOOOOOOOO -// (0xf0) // OOOOOOOOOO------ +// (0xe0) // OOOOOOOOOOOOOOOO +// (0xf0) // OOOOOOOOOO------ #define MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM (64) #define MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS (16) diff --git a/py/binary.c b/py/binary.c index 38d04e75ec..9081e35f5d 100644 --- a/py/binary.c +++ b/py/binary.c @@ -143,7 +143,7 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { } if (size == 0) { - mp_raise_ValueError(translate("bad typecode")); + mp_raise_ValueError(MP_ERROR_TEXT("bad typecode")); } if (palign != NULL) { @@ -184,9 +184,9 @@ mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { #endif #if MICROPY_PY_BUILTINS_FLOAT case 'f': - return mp_obj_new_float(((float *)p)[index]); + return mp_obj_new_float_from_f(((float *)p)[index]); case 'd': - return mp_obj_new_float(((double *)p)[index]); + return mp_obj_new_float_from_d(((double *)p)[index]); #endif #if MICROPY_NONSTANDARD_TYPECODES // Extension to CPython: array of objects @@ -256,12 +256,12 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * union { uint32_t i; float f; } fpu = {val}; - return mp_obj_new_float((mp_float_t)fpu.f); + 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(fpu.f); + 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) { @@ -325,7 +325,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p union { uint32_t i; float f; } fp_sp; - fp_sp.f = mp_obj_get_float(val_in); + fp_sp.f = mp_obj_get_float_to_f(val_in); val = fp_sp.i; break; } @@ -334,7 +334,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p uint32_t i32[2]; double f; } fp_dp; - fp_dp.f = mp_obj_get_float(val_in); + fp_dp.f = mp_obj_get_float_to_d(val_in); if (BYTES_PER_WORD == 8) { val = fp_dp.i64; } else { @@ -354,7 +354,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p mp_obj_int_buffer_overflow_check(val_in, size, signed_type); mp_obj_int_to_bytes_impl(val_in, struct_type == '>', size, p); return; - } else + } #endif { val = mp_obj_get_int(val_in); @@ -368,6 +368,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p p += size - sizeof(val); } } + break; } } } @@ -379,10 +380,10 @@ void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_ switch (typecode) { #if MICROPY_PY_BUILTINS_FLOAT case 'f': - ((float *)p)[index] = mp_obj_get_float(val_in); + ((float *)p)[index] = mp_obj_get_float_to_f(val_in); break; case 'd': - ((double *)p)[index] = mp_obj_get_float(val_in); + ((double *)p)[index] = mp_obj_get_float_to_d(val_in); break; #endif #if MICROPY_NONSTANDARD_TYPECODES @@ -449,10 +450,10 @@ void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_i #endif #if MICROPY_PY_BUILTINS_FLOAT case 'f': - ((float *)p)[index] = val; + ((float *)p)[index] = (float)val; break; case 'd': - ((double *)p)[index] = val; + ((double *)p)[index] = (double)val; break; #endif #if MICROPY_NONSTANDARD_TYPECODES diff --git a/py/builtin.h b/py/builtin.h index e1c7856958..2c0437d403 100644 --- a/py/builtin.h +++ b/py/builtin.h @@ -103,6 +103,7 @@ extern const mp_obj_module_t mp_module_thread; extern const mp_obj_dict_t mp_module_builtins_globals; // extmod modules +extern const mp_obj_module_t mp_module_uasyncio; extern const mp_obj_module_t mp_module_uerrno; extern const mp_obj_module_t mp_module_uctypes; extern const mp_obj_module_t mp_module_uzlib; diff --git a/py/builtinevex.c b/py/builtinevex.c index db2dcd5efe..a96a3a5344 100644 --- a/py/builtinevex.c +++ b/py/builtinevex.c @@ -102,7 +102,7 @@ STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { parse_input_kind = MP_PARSE_EVAL_INPUT; break; default: - mp_raise_ValueError(translate("bad compile mode")); + mp_raise_ValueError(MP_ERROR_TEXT("bad compile mode")); } mp_obj_code_t *code = m_new_obj(mp_obj_code_t); diff --git a/py/builtinhelp.c b/py/builtinhelp.c index 35e9805bee..344ded80b0 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -147,7 +147,7 @@ STATIC void mp_help_print_obj(const mp_obj_t obj) { } #endif - mp_obj_type_t *type = mp_obj_get_type(obj); + const mp_obj_type_t *type = mp_obj_get_type(obj); // try to print something sensible about the given object const compressed_string_t *compressed = translate("object "); diff --git a/py/builtinimport.c b/py/builtinimport.c index e48d750122..d78d51d484 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -247,7 +247,7 @@ STATIC void do_load(mp_obj_t module_obj, vstr_t *file) { } #else // If we get here then the file was not frozen and we can't compile scripts. - mp_raise_ImportError(translate("script compilation not supported")); + mp_raise_ImportError(MP_ERROR_TEXT("script compilation not supported")); #endif } @@ -332,7 +332,7 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // We must have some component left over to import from if (p == this_name) { - mp_raise_ValueError(translate("cannot perform relative import")); + mp_raise_ValueError(MP_ERROR_TEXT("cannot perform relative import")); } uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); @@ -512,7 +512,7 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { 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(translate("relative import")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("relative import")); } // Check if module already exists, and return it if it does @@ -534,10 +534,9 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // Couldn't find the module, so fail #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_msg(&mp_type_ImportError, translate("module not found")); + mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); #else - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ImportError, - translate("no module named '%q'"), module_name_qstr)); + mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), module_name_qstr); #endif } diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index cee70f62ff..894532d21b 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -243,9 +243,6 @@ CFLAGS += -DCIRCUITPY_RANDOM=$(CIRCUITPY_RANDOM) CIRCUITPY_RE ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_RE=$(CIRCUITPY_RE) -CIRCUITPY_RE_DEBUG ?= 0 -CFLAGS += -DCIRCUITPY_RE_DEBUG=$(CIRCUITPY_RE_DEBUG) - CIRCUITPY_REPL_BLE ?= 0 CFLAGS += -DCIRCUITPY_REPL_BLE=$(CIRCUITPY_REPL_BLE) diff --git a/py/compile.c b/py/compile.c index 910e816a57..5f66557382 100644 --- a/py/compile.c +++ b/py/compile.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * SPDX-FileCopyrightText: Copyright (c) 2013-2015 Damien P. George + * SPDX-FileCopyrightText: Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -451,7 +451,7 @@ STATIC void c_assign_atom_expr(compiler_t *comp, mp_parse_node_struct_t *pns, as } } - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("can't assign to expression")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("can't assign to expression")); } // we need to allow for a caller passing in 1 initial node (node_head) followed by an array of nodes (nodes_tail) @@ -470,7 +470,7 @@ STATIC void c_assign_tuple(compiler_t *comp, mp_parse_node_t node_head, uint num EMIT_ARG(unpack_ex, num_head + i, num_tail - i - 1); have_star_index = num_head + i; } else { - compile_syntax_error(comp, nodes_tail[i], translate("multiple *x in assignment")); + compile_syntax_error(comp, nodes_tail[i], MP_ERROR_TEXT("multiple *x in assignment")); return; } } @@ -596,7 +596,7 @@ STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t assign_ return; cannot_assign: - compile_syntax_error(comp, pn, translate("can't assign to expression")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("can't assign to expression")); } // stuff for lambda and comprehensions and generators: @@ -701,7 +701,7 @@ STATIC void compile_funcdef_lambdef_param(compiler_t *comp, mp_parse_node_t pn) // 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, translate("non-default argument follows default argument")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("non-default argument follows default argument")); return; } @@ -824,13 +824,13 @@ STATIC qstr compile_classdef_helper(compiler_t *comp, mp_parse_node_struct_t *pn } // 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, int name_len, mp_parse_node_t *name_nodes, uint *emit_options) { +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], translate("invalid decorator")); + compile_syntax_error(comp, name_nodes[0], MP_ERROR_TEXT("invalid micropython decorator")); return true; } @@ -863,17 +863,17 @@ STATIC bool compile_built_in_decorator(compiler_t *comp, int name_len, mp_parse_ #endif #endif } else { - compile_syntax_error(comp, name_nodes[1], translate("invalid micropython decorator")); + compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid micropython decorator")); } #if 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], translate("invalid architecture")); + compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid architecture")); } } 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], translate("invalid architecture")); + compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid architecture")); } } #endif @@ -884,20 +884,20 @@ STATIC bool compile_built_in_decorator(compiler_t *comp, int name_len, mp_parse_ STATIC void compile_decorated(compiler_t *comp, mp_parse_node_struct_t *pns) { // get the list of decorators mp_parse_node_t *nodes; - int n = mp_parse_node_extract_list(&pns->nodes[0], PN_decorators, &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 - int num_built_in_decorators = 0; - for (int i = 0; i < n; i++) { + 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; - int name_len = mp_parse_node_extract_list(&pns_decorator->nodes[0], PN_dotted_name, &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)) { @@ -909,7 +909,7 @@ STATIC void compile_decorated(compiler_t *comp, mp_parse_node_struct_t *pns) { // compile the decorator function compile_node(comp, name_nodes[0]); - for (int j = 1; j < name_len; j++) { + 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); } @@ -941,7 +941,7 @@ STATIC void compile_decorated(compiler_t *comp, mp_parse_node_struct_t *pns) { } // call each decorator - for (int i = 0; i < n - num_built_in_decorators; i++) { + for (size_t i = 0; i < n - num_built_in_decorators; i++) { EMIT_ARG(call_function, 1, 0, 0); } @@ -1029,7 +1029,7 @@ STATIC void c_del_stmt(compiler_t *comp, mp_parse_node_t pn) { return; cannot_delete: - compile_syntax_error(comp, (mp_parse_node_t)pn, translate("can't delete expression")); + 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) { @@ -1041,10 +1041,10 @@ STATIC void compile_break_cont_stmt(compiler_t *comp, mp_parse_node_struct_t *pn const compressed_string_t *error_msg; if (MP_PARSE_NODE_STRUCT_KIND(pns) == PN_break_stmt) { label = comp->break_label; - error_msg = translate("'break' outside loop"); + error_msg = MP_ERROR_TEXT("'break' outside loop"); } else { label = comp->continue_label; - error_msg = translate("'continue' outside loop"); + error_msg = MP_ERROR_TEXT("'continue' outside loop"); } if (label == INVALID_LABEL) { compile_syntax_error(comp, (mp_parse_node_t)pns, error_msg); @@ -1054,10 +1054,12 @@ STATIC void compile_break_cont_stmt(compiler_t *comp, mp_parse_node_struct_t *pn } 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, translate("'return' outside 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); @@ -1196,10 +1198,10 @@ STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { // get the list of . and/or ...'s mp_parse_node_t *nodes; - int n = mp_parse_node_extract_list(&pn_rel, PN_one_or_more_period_or_ellipsis, &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 (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { if (MP_PARSE_NODE_IS_TOKEN_KIND(nodes[i], MP_TOKEN_DEL_PERIOD)) { import_level++; } else { @@ -1212,7 +1214,7 @@ STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { 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, translate("import * not at module level")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("import * not at module level")); return; } #endif @@ -1233,8 +1235,8 @@ STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { // build the "fromlist" tuple mp_parse_node_t *pn_nodes; - int n = mp_parse_node_extract_list(&pns->nodes[1], PN_import_as_names, &pn_nodes); - for (int i = 0; i < n; i++) { + 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 @@ -1245,7 +1247,7 @@ STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { // do the import qstr dummy_q; do_import_name(comp, pn_import_source, &dummy_q); - for (int i = 0; i < n; i++) { + 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 @@ -1262,7 +1264,7 @@ STATIC void compile_import_from(compiler_t *comp, mp_parse_node_struct_t *pns) { 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, translate("identifier redefined as global")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("identifier redefined as global")); return; } id_info->kind = ID_INFO_KIND_GLOBAL_EXPLICIT; @@ -1279,10 +1281,10 @@ STATIC void compile_declare_nonlocal(compiler_t *comp, mp_parse_node_t pn, id_in 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, translate("no binding for nonlocal found")); + 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, translate("identifier redefined as nonlocal")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("identifier redefined as nonlocal")); } } @@ -1291,13 +1293,13 @@ STATIC void compile_global_nonlocal_stmt(compiler_t *comp, mp_parse_node_struct_ 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, translate("can't declare nonlocal in outer code")); + 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; - int n = mp_parse_node_extract_list(&pns->nodes[0], PN_name_list, &nodes); - for (int i = 0; i < n; i++) { + 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) { @@ -1357,8 +1359,8 @@ STATIC void compile_if_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // compile elif blocks (if any) mp_parse_node_t *pn_elif; - int n_elif = mp_parse_node_extract_list(&pns->nodes[2], PN_if_stmt_elif_list, &pn_elif); - for (int i = 0; i < n_elif; i++) { + 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]; @@ -1535,7 +1537,7 @@ STATIC void compile_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { && 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; - int n_args = mp_parse_node_extract_list(&pn_range_args, PN_arglist, &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; @@ -1636,7 +1638,7 @@ STATIC void compile_try_except(compiler_t *comp, mp_parse_node_t pn_body, int n_ 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], translate("default 'except' must be last")); + compile_syntax_error(comp, pn_excepts[i], MP_ERROR_TEXT("default 'except' must be last")); compile_decrease_except_level(comp); return; } @@ -1731,7 +1733,7 @@ STATIC void compile_try_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } 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; - int n_except = mp_parse_node_extract_list(&pns2->nodes[0], PN_try_stmt_except_list, &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]); @@ -1742,13 +1744,13 @@ STATIC void compile_try_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } else { // just try-except mp_parse_node_t *pn_excepts; - int n_except = mp_parse_node_extract_list(&pns->nodes[1], PN_try_stmt_except_list, &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, int n, mp_parse_node_t *nodes, mp_parse_node_t body) { +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); @@ -1778,7 +1780,7 @@ STATIC void compile_with_stmt_helper(compiler_t *comp, int n, mp_parse_node_t *n 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; - int n = mp_parse_node_extract_list(&pns->nodes[0], PN_with_stmt_list, &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 @@ -1865,7 +1867,7 @@ STATIC void compile_async_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns EMIT_ARG(label_assign, break_label); } -STATIC void compile_async_with_stmt_helper(compiler_t *comp, int n, mp_parse_node_t *nodes, mp_parse_node_t body) { +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); @@ -1983,7 +1985,7 @@ STATIC void compile_async_with_stmt(compiler_t *comp, mp_parse_node_struct_t *pn } // get the nodes for the pre-bit of the with (the a as b, c as d, ... bit) mp_parse_node_t *nodes; - int n = mp_parse_node_extract_list(&pns->nodes[0], PN_with_stmt_list, &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 @@ -2010,7 +2012,8 @@ STATIC void compile_async_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { #endif STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { - if (MP_PARSE_NODE_IS_NULL(pns->nodes[1])) { + 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__); @@ -2028,10 +2031,26 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT(pop_top); // discard last result since this is a statement and leaves nothing on the stack } } - } else if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { - mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; + } 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_expr_stmt_augassign) { + 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])); @@ -2056,10 +2075,10 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } else { plain_assign: #if MICROPY_COMP_DOUBLE_TUPLE_ASSIGN - if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_testlist_star_expr) + 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 *)pns->nodes[1]; + 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 @@ -2099,7 +2118,7 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } #endif - compile_node(comp, pns->nodes[1]); // rhs + compile_node(comp, pn_rhs); // rhs c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store } } else { @@ -2137,6 +2156,27 @@ STATIC void compile_lambdef(compiler_t *comp, mp_parse_node_struct_t *pns) { 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")); + } + 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); @@ -2209,7 +2249,7 @@ STATIC void compile_comparison(compiler_t *comp, mp_parse_node_struct_t *pns) { } STATIC void compile_star_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("*x must be assignment target")); + 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) { @@ -2292,7 +2332,7 @@ STATIC void compile_atom_expr_normal(compiler_t *comp, mp_parse_node_struct_t *p } if (!found) { compile_syntax_error(comp, (mp_parse_node_t)pns_trail[0], - translate("super() can't find self")); // really a TypeError + MP_ERROR_TEXT("super() can't find self")); // really a TypeError return; } @@ -2354,7 +2394,7 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar // get the list of arguments mp_parse_node_t *args; - int n_args = mp_parse_node_extract_list(&pn_arglist, PN_arglist, &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 @@ -2364,27 +2404,33 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar uint n_keyword = 0; uint star_flags = 0; mp_parse_node_struct_t *star_args_node = NULL, *dblstar_args_node = NULL; - for (int i = 0; i < n_args; i++) { + 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, translate("can't have multiple *x")); + 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, translate("can't have multiple **x")); + 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, translate("LHS of keyword arg must be an id")); + 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])); @@ -2400,11 +2446,11 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar } else { normal_argument: if (star_flags) { - compile_syntax_error(comp, args[i], translate("non-keyword arg after */**")); + compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after */**")); return; } if (n_keyword > 0) { - compile_syntax_error(comp, args[i], translate("non-keyword arg after keyword arg")); + compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after keyword arg")); return; } compile_node(comp, args[i]); @@ -2556,7 +2602,7 @@ STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t * // get tail elements (2nd, 3rd, ...) mp_parse_node_t *nodes; - int n = mp_parse_node_extract_list(&pns1->nodes[0], PN_dictorsetmaker_list2, &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; @@ -2575,16 +2621,16 @@ STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t * } // process rest of elements - for (int i = 0; i < n; i++) { + 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, translate("invalid syntax")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("expecting key:value for dict")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting key:value for dict")); #endif return; } @@ -2592,9 +2638,9 @@ STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t * } else { if (is_key_value) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("invalid syntax")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("expecting just a value for set")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting just a value for set")); #endif return; } @@ -2723,7 +2769,7 @@ STATIC void compile_classdef(compiler_t *comp, mp_parse_node_struct_t *pns) { 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, translate("'yield' outside function")); + 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])) { @@ -2734,7 +2780,7 @@ STATIC void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { pns = (mp_parse_node_struct_t *)pns->nodes[0]; #if MICROPY_PY_ASYNC_AWAIT if ((comp->scope_cur->scope_flags & MP_SCOPE_FLAG_ASYNC) != 0) { - compile_syntax_error(comp, (mp_parse_node_t)pns, translate("'yield from' inside async function")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'yield from' inside async function")); return; } #endif @@ -2750,7 +2796,7 @@ STATIC void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { #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, translate("'await' outside function")); + compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'await' outside function")); return; } compile_require_async_context(comp, pns); @@ -2862,11 +2908,11 @@ STATIC int compile_viper_type_annotation(compiler_t *comp, mp_parse_node_t pn_an 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, translate("unknown type '%q'"), type_name); + 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, translate("annotation must be an identifier")); + compile_syntax_error(comp, pn_annotation, MP_ERROR_TEXT("annotation must be an identifier")); } return native_type; } @@ -2877,7 +2923,7 @@ STATIC void compile_scope_func_lambda_param(compiler_t *comp, mp_parse_node_t pn // check that **kw is last if ((comp->scope_cur->scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { - compile_syntax_error(comp, pn, translate("invalid syntax")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("invalid syntax")); return; } @@ -2909,7 +2955,7 @@ STATIC void compile_scope_func_lambda_param(compiler_t *comp, mp_parse_node_t pn } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == pn_star) { if (comp->have_star) { // more than one star - compile_syntax_error(comp, pn, translate("invalid syntax")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("invalid syntax")); return; } comp->have_star = true; @@ -2943,7 +2989,7 @@ STATIC void compile_scope_func_lambda_param(compiler_t *comp, mp_parse_node_t pn 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, translate("name reused for argument")); + compile_syntax_error(comp, pn, MP_ERROR_TEXT("argument name reused")); return; } id_info->kind = ID_INFO_KIND_LOCAL; @@ -3126,7 +3172,7 @@ STATIC void compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } EMIT(return_value); - } else if (scope->kind == SCOPE_LIST_COMP || scope->kind == SCOPE_DICT_COMP || scope->kind == SCOPE_SET_COMP || scope->kind == SCOPE_GEN_EXPR) { + } else if (SCOPE_IS_COMP_LIKE(scope->kind)) { // a bit of a hack at the moment assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); @@ -3222,7 +3268,7 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind comp->next_label = 0; if (scope->kind != SCOPE_FUNCTION) { - compile_syntax_error(comp, MP_PARSE_NODE_NULL, translate("inline assembler must be a function")); + compile_syntax_error(comp, MP_PARSE_NODE_NULL, MP_ERROR_TEXT("inline assembler must be a function")); return; } @@ -3240,7 +3286,7 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind // parameters are in pns->nodes[1] if (comp->pass == MP_PASS_CODE_SIZE) { mp_parse_node_t *pn_params; - int n_params = mp_parse_node_extract_list(&pns->nodes[1], PN_typedargslist, &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; @@ -3268,19 +3314,19 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind type_sig = MP_NATIVE_TYPE_UINT; break; default: - compile_syntax_error(comp, pn_annotation, translate("unknown type")); + compile_syntax_error(comp, pn_annotation, MP_ERROR_TEXT("unknown type")); return; } } else { - compile_syntax_error(comp, pn_annotation, translate("return annotation must be an identifier")); + 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; - int num = mp_parse_node_extract_list(&pn_body, PN_suite_block_stmts, &nodes); + size_t num = mp_parse_node_extract_list(&pn_body, PN_suite_block_stmts, &nodes); - for (int i = 0; i < num; i++) { + 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) { @@ -3289,7 +3335,7 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) != PN_expr_stmt) { // not an instruction; error not_an_instruction: - compile_syntax_error(comp, nodes[i], translate("expecting an assembler instruction")); + compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("expecting an assembler instruction")); return; } @@ -3314,24 +3360,24 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind 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; - int n_args = mp_parse_node_extract_list(&pns2->nodes[0], PN_arglist, &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], translate("'label' requires 1 argument")); + 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], translate("label redefined")); + 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], translate("'align' requires 1 argument")); + compile_syntax_error(comp, nodes[i], MP_ERROR_TEXT("'align' requires 1 argument")); return; } if (pass > MP_PASS_SCOPE) { @@ -3340,14 +3386,14 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind } } 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], translate("'data' requires at least 2 arguments")); + 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 (int j = 1; j < n_args; j++) { + for (uint j = 1; j < n_args; j++) { if (!MP_PARSE_NODE_IS_SMALL_INT(pn_arg[j])) { - compile_syntax_error(comp, nodes[i], translate("'data' requires integer arguments")); + 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, diff --git a/py/dynruntime.h b/py/dynruntime.h index a2fd4895ff..696746f751 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -31,6 +31,11 @@ #include "py/nativeglue.h" #include "py/objstr.h" +#include "py/objtype.h" + +#if !MICROPY_ENABLE_DYNRUNTIME +#error "dynruntime.h included in non-dynamic-module build." +#endif #undef MP_ROM_QSTR #undef MP_OBJ_QSTR_VALUE @@ -106,9 +111,10 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_obj_new_list(n, items) (mp_fun_table.new_list((n), (items))) #define mp_obj_get_type(o) (mp_fun_table.obj_get_type((o))) +#define mp_obj_cast_to_native_base(o, t) (mp_obj_cast_to_native_base_dyn((o), (t))) #define mp_obj_get_int(o) (mp_fun_table.native_from_obj(o, MP_NATIVE_TYPE_INT)) #define mp_obj_get_int_truncated(o) (mp_fun_table.native_from_obj(o, MP_NATIVE_TYPE_UINT)) -#define mp_obj_str_get_str(s) ((void *)mp_fun_table.native_from_obj(s, MP_NATIVE_TYPE_PTR)) +#define mp_obj_str_get_str(s) (mp_obj_str_get_data_dyn((s), NULL)) #define mp_obj_str_get_data(o, len) (mp_obj_str_get_data_dyn((o), (len))) #define mp_get_buffer_raise(o, bufinfo, fl) (mp_fun_table.get_buffer_raise((o), (bufinfo), (fl))) #define mp_get_stream_raise(s, flags) (mp_fun_table.get_stream_raise((s), (flags))) @@ -125,10 +131,27 @@ static inline mp_obj_t mp_obj_new_str_of_type_dyn(const mp_obj_type_t *type, con } } +static inline mp_obj_t mp_obj_cast_to_native_base_dyn(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 (self_type->parent != native_type) { + // The self_in object is not a direct descendant of native_type, so fail the cast. + // This is a very simple version of mp_obj_is_subclass_fast that could be improved. + return MP_OBJ_NULL; + } else { + mp_obj_instance_t *self = (mp_obj_instance_t *)MP_OBJ_TO_PTR(self_in); + return self->subobj[0]; + } +} + static inline void *mp_obj_str_get_data_dyn(mp_obj_t o, size_t *l) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(o, &bufinfo, MP_BUFFER_READ); - *l = bufinfo.len; + if (l != NULL) { + *l = bufinfo.len; + } return bufinfo.buf; } diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 8b65745afd..cb5ab845eb 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -27,6 +27,7 @@ CFLAGS += -std=c99 CFLAGS += -Os CFLAGS += -Wall -Werror -DNDEBUG CFLAGS += -DNO_QSTR +CFLAGS += -DMICROPY_ENABLE_DYNRUNTIME CFLAGS += -DMP_CONFIGFILE='<$(CONFIG_H)>' CFLAGS += -fpic -fno-common CFLAGS += -U _FORTIFY_SOURCE # prevent use of __*_chk libc functions diff --git a/py/emitbc.c b/py/emitbc.c index 52f5019bc2..ac78db67bb 100644 --- a/py/emitbc.c +++ b/py/emitbc.c @@ -110,7 +110,6 @@ STATIC void emit_write_uint(emit_t *emit, emit_allocator_t allocator, mp_uint_t // all functions must go through this one to emit code info STATIC byte *emit_get_cur_to_write_code_info(emit_t *emit, int num_bytes_to_write) { - // printf("emit %d\n", num_bytes_to_write); if (emit->pass < MP_PASS_EMIT) { emit->code_info_offset += num_bytes_to_write; return emit->dummy_data; @@ -140,7 +139,6 @@ STATIC void emit_write_code_info_qstr(emit_t *emit, qstr 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); - // printf(" %d %d\n", bytes_to_skip, lines_to_skip); while (bytes_to_skip > 0 || lines_to_skip > 0) { mp_uint_t b, l; if (lines_to_skip <= 6 || bytes_to_skip > 0xf) { @@ -169,7 +167,6 @@ STATIC void emit_write_code_info_bytes_lines(emit_t *emit, mp_uint_t bytes_to_sk // all functions must go through this one to emit byte code STATIC byte *emit_get_cur_to_write_bytecode(emit_t *emit, int num_bytes_to_write) { - // printf("emit %d\n", num_bytes_to_write); if (emit->pass < MP_PASS_EMIT) { emit->bytecode_offset += num_bytes_to_write; return emit->dummy_data; @@ -470,7 +467,6 @@ void mp_emit_bc_adjust_stack_size(emit_t *emit, mp_int_t delta) { } void mp_emit_bc_set_source_line(emit_t *emit, mp_uint_t source_line) { - // printf("source: line %d -> %d offset %d -> %d\n", emit->last_source_line, source_line, emit->last_source_line_offset, emit->bytecode_offset); #if MICROPY_ENABLE_SOURCE_LINE if (MP_STATE_VM(mp_optimise_value) >= 3) { // If we compile with -O3, don't store line numbers. diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 1602f505b3..1800cb43f9 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -99,17 +99,17 @@ STATIC void emit_inline_thumb_end_pass(emit_inline_asm_t *emit, mp_uint_t type_s 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, translate("can only have up to 4 parameters to Thumb assembly")); + 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, translate("parameters must be registers in sequence r0 to r3")); + 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' && p[1] == '0' + i)) { - emit_inline_thumb_error_msg(emit, translate("parameters must be registers in sequence r0 to r3")); + emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence r0 to r3")); return 0; } } @@ -189,7 +189,7 @@ STATIC mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_n if (r->reg > max_reg) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects at most r%d"), op, max_reg)); + MP_ERROR_TEXT("'%s' expects at most r%d"), op, max_reg)); return 0; } else { return r->reg; @@ -198,7 +198,7 @@ STATIC mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_n } emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects a register"), op)); + MP_ERROR_TEXT("'%s' expects a register"), op)); return 0; } @@ -212,7 +212,7 @@ STATIC mp_uint_t get_arg_special_reg(emit_inline_asm_t *emit, const char *op, mp } emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects a special register"), op)); + MP_ERROR_TEXT("'%s' expects a special register"), op)); return 0; } @@ -231,7 +231,7 @@ STATIC mp_uint_t get_arg_vfpreg(emit_inline_asm_t *emit, const char *op, mp_pars if (regno > 31) { emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects at most r%d"), op, 31)); + MP_ERROR_TEXT("'%s' expects at most r%d"), op, 31)); return 0; } else { return regno; @@ -240,7 +240,7 @@ STATIC mp_uint_t get_arg_vfpreg(emit_inline_asm_t *emit, const char *op, mp_pars malformed: emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects an FPU register"), op)); + MP_ERROR_TEXT("'%s' expects an FPU register"), op)); return 0; } #endif @@ -293,19 +293,19 @@ STATIC mp_uint_t get_arg_reglist(emit_inline_asm_t *emit, const char *op, mp_par return reglist; bad_arg: - emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, translate("'%s' expects {r0, r1, ...}"), op)); + 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, translate("'%s' expects an integer"), op)); + 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, translate("'%s' integer 0x%x does not fit in mask 0x%x"), op, i, fit_mask)); + 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; @@ -329,13 +329,13 @@ STATIC bool get_arg_addr(emit_inline_asm_t *emit, const char *op, mp_parse_node_ return true; bad_arg: - emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, translate("'%s' expects an address of the form [a, b]"), op)); + 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, translate("'%s' expects a label"), op)); + 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); @@ -346,7 +346,7 @@ STATIC int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_ } // 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, translate("label '%q' not defined"), label_qstr)); + 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; } @@ -533,8 +533,10 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } else { goto unknown_op; } - } else + return; + } #endif + if (n_args == 0) { if (op == MP_QSTR_nop) { asm_thumb_op16(&emit->as, ASM_THUMB_OP_NOP); @@ -815,11 +817,11 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a return; unknown_op: - emit_inline_thumb_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, translate("unsupported Thumb instruction '%s' with %d arguments"), op_str, n_args)); + 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, translate("branch not in range")); + emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("branch not in range")); return; } diff --git a/py/emitinlinextensa.c b/py/emitinlinextensa.c index e6f7e755bf..7611162322 100644 --- a/py/emitinlinextensa.c +++ b/py/emitinlinextensa.c @@ -83,17 +83,17 @@ STATIC void emit_inline_xtensa_end_pass(emit_inline_asm_t *emit, mp_uint_t type_ 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, translate("can only have up to 4 parameters to Xtensa assembly")); + 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, translate("parameters must be registers in sequence a2 to a5")); + 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' && p[1] == '2' + i)) { - emit_inline_xtensa_error_msg(emit, translate("parameters must be registers in sequence a2 to a5")); + emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence a2 to a5")); return 0; } } @@ -161,19 +161,19 @@ STATIC mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_n } emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - translate("'%s' expects a register"), op)); + 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, translate("'%s' expects an integer"), op)); + 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, translate("'%s' integer %d is not within range %d..%d"), op, i, min, 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; @@ -181,7 +181,7 @@ STATIC uint32_t get_arg_i(emit_inline_asm_t *emit, const char *op, mp_parse_node 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, translate("'%s' expects a label"), op)); + 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); @@ -192,7 +192,7 @@ STATIC int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_ } // 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, translate("label '%q' not defined"), label_qstr)); + 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; } @@ -326,12 +326,12 @@ STATIC void emit_inline_xtensa_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_ return; unknown_op: - emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, translate("unsupported Xtensa instruction '%s' with %d arguments"), op_str, n_args)); + 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, translate("branch not in range")); + emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("branch not in range")); return; */ } diff --git a/py/emitnative.c b/py/emitnative.c index cb41864cc8..06d508d20b 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -869,7 +869,7 @@ STATIC vtype_kind_t load_reg_stack_imm(emit_t *emit, int reg_dest, const stack_i } else if (si->vtype == VTYPE_PTR_NONE) { emit_native_mov_reg_const(emit, reg_dest, MP_F_CONST_NONE_OBJ); } else { - mp_raise_NotImplementedError(translate("conversion to object")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("conversion to object")); } return VTYPE_PYOBJ; } @@ -1443,7 +1443,7 @@ STATIC void emit_native_load_fast(emit_t *emit, qstr qst, mp_uint_t local_num) { DEBUG_printf("load_fast(%s, " UINT_FMT ")\n", qstr_str(qst), local_num); vtype_kind_t vtype = emit->local_vtype[local_num]; if (vtype == VTYPE_UNBOUND) { - EMIT_NATIVE_VIPER_TYPE_ERROR(emit, translate("local '%q' used before type known"), qst); + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("local '%q' used before type known"), qst); } emit_native_pre(emit); if (local_num < REG_LOCAL_NUM && CAN_USE_REGS_FOR_LOCALS(emit)) { @@ -1618,7 +1618,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { } default: EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't load from '%q'"), vtype_to_qstr(vtype_base)); + MP_ERROR_TEXT("can't load from '%q'"), vtype_to_qstr(vtype_base)); } } else { // index is not an immediate @@ -1628,7 +1628,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { emit_pre_pop_reg(emit, &vtype_base, REG_ARG_1); if (vtype_index != VTYPE_INT && vtype_index != VTYPE_UINT) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't load with '%q' index"), vtype_to_qstr(vtype_index)); + MP_ERROR_TEXT("can't load with '%q' index"), vtype_to_qstr(vtype_index)); } switch (vtype_base) { case VTYPE_PTR8: { @@ -1656,7 +1656,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { } default: EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't load from '%q'"), vtype_to_qstr(vtype_base)); + MP_ERROR_TEXT("can't load from '%q'"), vtype_to_qstr(vtype_base)); } } emit_post_push_reg(emit, VTYPE_INT, REG_RET); @@ -1680,7 +1680,7 @@ STATIC void emit_native_store_fast(emit_t *emit, qstr qst, mp_uint_t local_num) } else if (emit->local_vtype[local_num] != vtype) { // type of local is not the same as object stored in it EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("local '%q' has type '%q' but source is '%q'"), + MP_ERROR_TEXT("local '%q' has type '%q' but source is '%q'"), qst, vtype_to_qstr(emit->local_vtype[local_num]), vtype_to_qstr(vtype)); } } @@ -1781,7 +1781,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { #endif if (vtype_value != VTYPE_BOOL && vtype_value != VTYPE_INT && vtype_value != VTYPE_UINT) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't store '%q'"), vtype_to_qstr(vtype_value)); + MP_ERROR_TEXT("can't store '%q'"), vtype_to_qstr(vtype_value)); } switch (vtype_base) { case VTYPE_PTR8: { @@ -1847,7 +1847,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { } default: EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't store to '%q'"), vtype_to_qstr(vtype_base)); + MP_ERROR_TEXT("can't store to '%q'"), vtype_to_qstr(vtype_base)); } } else { // index is not an immediate @@ -1858,7 +1858,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { emit_pre_pop_reg(emit, &vtype_base, REG_ARG_1); if (vtype_index != VTYPE_INT && vtype_index != VTYPE_UINT) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't store with '%q' index"), vtype_to_qstr(vtype_index)); + MP_ERROR_TEXT("can't store with '%q' index"), vtype_to_qstr(vtype_index)); } #if N_X86 // special case: x86 needs byte stores to be from lower 4 regs (REG_ARG_3 is EDX) @@ -1868,7 +1868,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { #endif if (vtype_value != VTYPE_BOOL && vtype_value != VTYPE_INT && vtype_value != VTYPE_UINT) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't store '%q'"), vtype_to_qstr(vtype_value)); + MP_ERROR_TEXT("can't store '%q'"), vtype_to_qstr(vtype_value)); } switch (vtype_base) { case VTYPE_PTR8: { @@ -1908,7 +1908,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { } default: EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't store to '%q'"), vtype_to_qstr(vtype_base)); + MP_ERROR_TEXT("can't store to '%q'"), vtype_to_qstr(vtype_base)); } } @@ -2030,7 +2030,7 @@ STATIC void emit_native_jump_helper(emit_t *emit, bool cond, mp_uint_t label, bo } if (!(vtype == VTYPE_BOOL || vtype == VTYPE_INT || vtype == VTYPE_UINT)) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't implicitly convert '%q' to 'bool'"), vtype_to_qstr(vtype)); + MP_ERROR_TEXT("can't implicitly convert '%q' to 'bool'"), vtype_to_qstr(vtype)); } } // For non-pop need to save the vtype so that emit_native_adjust_stack_size @@ -2096,7 +2096,7 @@ STATIC void emit_native_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t exc ASM_MOV_REG_PCREL(emit->as, REG_RET, label & ~MP_EMIT_BREAK_FROM_FOR); ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_RET); // Cancel any active exception (see also emit_native_pop_except_jump) - emit_native_mov_reg_const(emit, REG_RET, MP_F_CONST_NONE_OBJ); + ASM_MOV_REG_IMM(emit->as, REG_RET, (mp_uint_t)MP_OBJ_NULL); ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_RET); // Jump to the innermost active finally label = first_finally->label; @@ -2191,9 +2191,8 @@ STATIC void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) { ASM_MOV_REG_LOCAL(emit->as, REG_ARG_1, LOCAL_IDX_EXC_VAL(emit)); // get exc - // Check if exc is None and jump to non-exc handler if it is - emit_native_mov_reg_const(emit, REG_ARG_2, MP_F_CONST_NONE_OBJ); - ASM_JUMP_IF_REG_EQ(emit->as, REG_ARG_1, REG_ARG_2, *emit->label_slot + 2); + // Check if exc is MP_OBJ_NULL (i.e. zero) and jump to non-exc handler if it is + ASM_JUMP_IF_REG_ZERO(emit->as, REG_ARG_1, *emit->label_slot + 2, false); ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_2, REG_ARG_1, 0); // get type(exc) emit_post_push_reg(emit, VTYPE_PYOBJ, REG_ARG_2); // push type(exc) @@ -2213,9 +2212,9 @@ STATIC void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) { emit_call(emit, MP_F_OBJ_IS_TRUE); ASM_JUMP_IF_REG_ZERO(emit->as, REG_RET, *emit->label_slot + 1, true); - // Replace exception with None + // Replace exception with MP_OBJ_NULL. emit_native_label_assign(emit, *emit->label_slot); - emit_native_mov_reg_const(emit, REG_TEMP0, MP_F_CONST_NONE_OBJ); + ASM_MOV_REG_IMM(emit->as, REG_TEMP0, (mp_uint_t)MP_OBJ_NULL); ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); // end of with cleanup nlr_catch block @@ -2293,7 +2292,7 @@ STATIC void emit_native_for_iter_end(emit_t *emit) { STATIC void emit_native_pop_except_jump(emit_t *emit, mp_uint_t label, bool within_exc_handler) { if (within_exc_handler) { // Cancel any active exception so subsequent handlers don't see it - emit_native_mov_reg_const(emit, REG_TEMP0, MP_F_CONST_NONE_OBJ); + ASM_MOV_REG_IMM(emit->as, REG_TEMP0, (mp_uint_t)MP_OBJ_NULL); ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); } else { emit_native_leave_exc_stack(emit, false); @@ -2310,7 +2309,7 @@ STATIC void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) { } else { adjust_stack(emit, 1); EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("unary op %q not implemented"), mp_unary_op_method_name[op]); + MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]); } } @@ -2318,7 +2317,8 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { DEBUG_printf("binary_op(" UINT_FMT ")\n", op); vtype_kind_t vtype_lhs = peek_vtype(emit, 1); vtype_kind_t vtype_rhs = peek_vtype(emit, 0); - if (vtype_lhs == VTYPE_INT && vtype_rhs == VTYPE_INT) { + if ((vtype_lhs == VTYPE_INT || vtype_lhs == VTYPE_UINT) + && (vtype_rhs == VTYPE_INT || vtype_rhs == VTYPE_UINT)) { // for integers, inplace and normal ops are equivalent, so use just normal ops if (MP_BINARY_OP_INPLACE_OR <= op && op <= MP_BINARY_OP_INPLACE_POWER) { op += MP_BINARY_OP_OR - MP_BINARY_OP_INPLACE_OR; @@ -2335,9 +2335,13 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { if (op == MP_BINARY_OP_LSHIFT) { ASM_LSL_REG(emit->as, REG_RET); } else { - ASM_ASR_REG(emit->as, REG_RET); + if (vtype_lhs == VTYPE_UINT) { + ASM_LSR_REG(emit->as, REG_RET); + } else { + ASM_ASR_REG(emit->as, REG_RET); + } } - emit_post_push_reg(emit, VTYPE_INT, REG_RET); + emit_post_push_reg(emit, vtype_lhs, REG_RET); return; } #endif @@ -2345,6 +2349,10 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { // special cases for floor-divide and module because we dispatch to helper functions if (op == MP_BINARY_OP_FLOOR_DIVIDE || op == MP_BINARY_OP_MODULO) { emit_pre_pop_reg_reg(emit, &vtype_rhs, REG_ARG_2, &vtype_lhs, REG_ARG_1); + if (vtype_lhs != VTYPE_INT) { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, + MP_ERROR_TEXT("div/mod not implemented for uint"), mp_binary_op_method_name[op]); + } if (op == MP_BINARY_OP_FLOOR_DIVIDE) { emit_call(emit, MP_F_SMALL_INT_FLOOR_DIVIDE); } else { @@ -2357,33 +2365,41 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { int reg_rhs = REG_ARG_3; emit_pre_pop_reg_flexible(emit, &vtype_rhs, ®_rhs, REG_RET, REG_ARG_2); emit_pre_pop_reg(emit, &vtype_lhs, REG_ARG_2); + #if !(N_X64 || N_X86) - if (op == MP_BINARY_OP_LSHIFT) { - ASM_LSL_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); - } else if (op == MP_BINARY_OP_RSHIFT) { - ASM_ASR_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); - } else + if (op == MP_BINARY_OP_LSHIFT || op == MP_BINARY_OP_RSHIFT) { + if (op == MP_BINARY_OP_LSHIFT) { + ASM_LSL_REG_REG(emit->as, REG_ARG_2, reg_rhs); + } else { + if (vtype_lhs == VTYPE_UINT) { + ASM_LSR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + } else { + ASM_ASR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + } + } + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); + return; + } #endif + if (op == MP_BINARY_OP_OR) { ASM_OR_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_XOR) { ASM_XOR_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_AND) { ASM_AND_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_ADD) { ASM_ADD_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_SUBTRACT) { ASM_SUB_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_MULTIPLY) { ASM_MUL_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (MP_BINARY_OP_LESS <= op && op <= MP_BINARY_OP_NOT_EQUAL) { // comparison ops are (in enum order): // MP_BINARY_OP_LESS @@ -2392,11 +2408,26 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { // MP_BINARY_OP_LESS_EQUAL // MP_BINARY_OP_MORE_EQUAL // MP_BINARY_OP_NOT_EQUAL + + if (vtype_lhs != vtype_rhs) { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("comparison of int and uint")); + } + + size_t op_idx = op - MP_BINARY_OP_LESS + (vtype_lhs == VTYPE_UINT ? 0 : 6); + need_reg_single(emit, REG_RET, 0); #if N_X64 asm_x64_xor_r64_r64(emit->as, REG_RET, REG_RET); asm_x64_cmp_r64_with_r64(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6] = { + static byte ops[6 + 6] = { + // unsigned + ASM_X64_CC_JB, + ASM_X64_CC_JA, + ASM_X64_CC_JE, + ASM_X64_CC_JBE, + ASM_X64_CC_JAE, + ASM_X64_CC_JNE, + // signed ASM_X64_CC_JL, ASM_X64_CC_JG, ASM_X64_CC_JE, @@ -2404,11 +2435,19 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_X64_CC_JGE, ASM_X64_CC_JNE, }; - asm_x64_setcc_r8(emit->as, ops[op - MP_BINARY_OP_LESS], REG_RET); + asm_x64_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_X86 asm_x86_xor_r32_r32(emit->as, REG_RET, REG_RET); asm_x86_cmp_r32_with_r32(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6] = { + static byte ops[6 + 6] = { + // unsigned + ASM_X86_CC_JB, + ASM_X86_CC_JA, + ASM_X86_CC_JE, + ASM_X86_CC_JBE, + ASM_X86_CC_JAE, + ASM_X86_CC_JNE, + // signed ASM_X86_CC_JL, ASM_X86_CC_JG, ASM_X86_CC_JE, @@ -2416,24 +2455,39 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_X86_CC_JGE, ASM_X86_CC_JNE, }; - asm_x86_setcc_r8(emit->as, ops[op - MP_BINARY_OP_LESS], REG_RET); + asm_x86_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_THUMB asm_thumb_cmp_rlo_rlo(emit->as, REG_ARG_2, reg_rhs); - static uint16_t ops[6] = { - ASM_THUMB_OP_ITE_GE, + static uint16_t ops[6 + 6] = { + // unsigned + ASM_THUMB_OP_ITE_CC, + ASM_THUMB_OP_ITE_HI, + ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LS, + ASM_THUMB_OP_ITE_CS, + ASM_THUMB_OP_ITE_NE, + // signed + ASM_THUMB_OP_ITE_LT, ASM_THUMB_OP_ITE_GT, ASM_THUMB_OP_ITE_EQ, - ASM_THUMB_OP_ITE_GT, + ASM_THUMB_OP_ITE_LE, ASM_THUMB_OP_ITE_GE, - ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_NE, }; - static byte ret[6] = { 0, 1, 1, 0, 1, 0, }; - asm_thumb_op16(emit->as, ops[op - MP_BINARY_OP_LESS]); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, ret[op - MP_BINARY_OP_LESS]); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, ret[op - MP_BINARY_OP_LESS] ^ 1); + asm_thumb_op16(emit->as, ops[op_idx]); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); #elif N_ARM asm_arm_cmp_reg_reg(emit->as, REG_ARG_2, reg_rhs); - static uint ccs[6] = { + static uint ccs[6 + 6] = { + // unsigned + ASM_ARM_CC_CC, + ASM_ARM_CC_HI, + ASM_ARM_CC_EQ, + ASM_ARM_CC_LS, + ASM_ARM_CC_CS, + ASM_ARM_CC_NE, + // signed ASM_ARM_CC_LT, ASM_ARM_CC_GT, ASM_ARM_CC_EQ, @@ -2441,9 +2495,17 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_ARM_CC_GE, ASM_ARM_CC_NE, }; - asm_arm_setcc_reg(emit->as, REG_RET, ccs[op - MP_BINARY_OP_LESS]); + asm_arm_setcc_reg(emit->as, REG_RET, ccs[op_idx]); #elif N_XTENSA || N_XTENSAWIN - static uint8_t ccs[6] = { + static uint8_t ccs[6 + 6] = { + // unsigned + ASM_XTENSA_CC_LTU, + 0x80 | ASM_XTENSA_CC_LTU, // for GTU we'll swap args + ASM_XTENSA_CC_EQ, + 0x80 | ASM_XTENSA_CC_GEU, // for LEU we'll swap args + ASM_XTENSA_CC_GEU, + ASM_XTENSA_CC_NE, + // signed ASM_XTENSA_CC_LT, 0x80 | ASM_XTENSA_CC_LT, // for GT we'll swap args ASM_XTENSA_CC_EQ, @@ -2451,7 +2513,7 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_XTENSA_CC_GE, ASM_XTENSA_CC_NE, }; - uint8_t cc = ccs[op - MP_BINARY_OP_LESS]; + uint8_t cc = ccs[op_idx]; if ((cc & 0x80) == 0) { asm_xtensa_setcc_reg_reg_reg(emit->as, cc, REG_RET, REG_ARG_2, reg_rhs); } else { @@ -2465,7 +2527,7 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { // TODO other ops not yet implemented adjust_stack(emit, 1); EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("binary op %q not implemented"), mp_binary_op_method_name[op]); + MP_ERROR_TEXT("binary op %q not implemented"), mp_binary_op_method_name[op]); } } else if (vtype_lhs == VTYPE_PYOBJ && vtype_rhs == VTYPE_PYOBJ) { emit_pre_pop_reg_reg(emit, &vtype_rhs, REG_ARG_3, &vtype_lhs, REG_ARG_2); @@ -2486,7 +2548,7 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { } else { adjust_stack(emit, -1); EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("can't do binary op between '%q' and '%q'"), + MP_ERROR_TEXT("can't do binary op between '%q' and '%q'"), vtype_to_qstr(vtype_lhs), vtype_to_qstr(vtype_rhs)); } } @@ -2664,7 +2726,7 @@ STATIC void emit_native_call_function(emit_t *emit, mp_uint_t n_positional, mp_u break; default: // this can happen when casting a cast: int(int) - mp_raise_NotImplementedError(translate("casting")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("casting")); } } else { assert(vtype_fun == VTYPE_PYOBJ); @@ -2728,7 +2790,7 @@ STATIC void emit_native_return_value(emit_t *emit) { emit_pre_pop_reg(emit, &vtype, return_vtype == VTYPE_PYOBJ ? REG_PARENT_RET : REG_ARG_1); if (vtype != return_vtype) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - translate("return expected '%q' but got '%q'"), + MP_ERROR_TEXT("return expected '%q' but got '%q'"), vtype_to_qstr(return_vtype), vtype_to_qstr(vtype)); } } @@ -2757,7 +2819,7 @@ STATIC void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { vtype_kind_t vtype_exc; emit_pre_pop_reg(emit, &vtype_exc, REG_ARG_1); // arg1 = object to raise if (vtype_exc != VTYPE_PYOBJ) { - EMIT_NATIVE_VIPER_TYPE_ERROR(emit, translate("must raise an object")); + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("must raise an object")); } // TODO probably make this 1 call to the runtime (which could even call convert, native_raise(obj, type)) emit_call(emit, MP_F_NATIVE_RAISE); @@ -2767,7 +2829,7 @@ STATIC void emit_native_yield(emit_t *emit, int kind) { // Note: 1 (yield) or 3 (yield from) labels are reserved for this function, starting at *emit->label_slot if (emit->do_viper_types) { - mp_raise_NotImplementedError(translate("native yield")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("native yield")); } emit->scope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; @@ -2813,6 +2875,7 @@ STATIC void emit_native_yield(emit_t *emit, int kind) { // Found active handler, get its PC ASM_MOV_REG_PCREL(emit->as, REG_RET, e->label); ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_RET); + break; } } } diff --git a/py/formatfloat.c b/py/formatfloat.c index f8d06d2d71..06775167c0 100644 --- a/py/formatfloat.c +++ b/py/formatfloat.c @@ -100,15 +100,15 @@ static inline int fp_isless1(float x) { static const FPTYPE g_pos_pow[] = { #if FPDECEXP > 32 - 1e256, 1e128, 1e64, + MICROPY_FLOAT_CONST(1e256), MICROPY_FLOAT_CONST(1e128), MICROPY_FLOAT_CONST(1e64), #endif - 1e32, 1e16, 1e8, 1e4, 1e2, 1e1 + 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 - 1e-256, 1e-128, 1e-64, + MICROPY_FLOAT_CONST(1e-256), MICROPY_FLOAT_CONST(1e-128), MICROPY_FLOAT_CONST(1e-64), #endif - 1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1 + 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) { diff --git a/py/gc.c b/py/gc.c index 003ab6c30f..307af17fac 100644 --- a/py/gc.c +++ b/py/gc.c @@ -178,7 +178,7 @@ void gc_init(void *start, void *end) { MP_STATE_MEM(gc_alloc_amount) = 0; #endif - #if MICROPY_PY_THREAD + #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_MEM(gc_mutex)); #endif diff --git a/py/grammar.h b/py/grammar.h index ce77cf3525..3b4ceb8c60 100644 --- a/py/grammar.h +++ b/py/grammar.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * SPDX-FileCopyrightText: Copyright (c) 2013-2015 Damien P. George + * SPDX-FileCopyrightText: Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,10 +24,17 @@ * THE SOFTWARE. */ +// *FORMAT-OFF* + // rules for writing rules: // - zero_or_more is implemented using opt_rule around a one_or_more rule // - don't put opt_rule in arguments of or rule; instead, wrap the call to this or rule in opt_rule +// Generic sub-rules used by multiple rules below. + +DEF_RULE_NC(generic_colon_test, and_ident(2), tok(DEL_COLON), rule(test)) +DEF_RULE_NC(generic_equal_test, and_ident(2), tok(DEL_EQUAL), rule(test)) + // # Start symbols for the grammar: // # single_input is a single interactive statement; // # file_input is a module or sequence of commands read from an input file; @@ -69,19 +76,16 @@ DEF_RULE_NC(funcdefrettype, and_ident(2), tok(DEL_MINUS_MORE), rule(test)) // note: typedargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(typedargslist, list_with_end, rule(typedargslist_item), tok(DEL_COMMA)) DEF_RULE_NC(typedargslist_item, or(3), rule(typedargslist_name), rule(typedargslist_star), rule(typedargslist_dbl_star)) -DEF_RULE_NC(typedargslist_name, and_ident(3), tok(NAME), opt_rule(typedargslist_colon), opt_rule(typedargslist_equal)) +DEF_RULE_NC(typedargslist_name, and_ident(3), tok(NAME), opt_rule(generic_colon_test), opt_rule(generic_equal_test)) DEF_RULE_NC(typedargslist_star, and(2), tok(OP_STAR), opt_rule(tfpdef)) -DEF_RULE_NC(typedargslist_dbl_star, and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(typedargslist_colon)) -DEF_RULE_NC(typedargslist_colon, and_ident(2), tok(DEL_COLON), rule(test)) -DEF_RULE_NC(typedargslist_equal, and_ident(2), tok(DEL_EQUAL), rule(test)) -DEF_RULE_NC(tfpdef, and(2), tok(NAME), opt_rule(typedargslist_colon)) +DEF_RULE_NC(typedargslist_dbl_star, and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(generic_colon_test)) +DEF_RULE_NC(tfpdef, and(2), tok(NAME), opt_rule(generic_colon_test)) // note: varargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(varargslist, list_with_end, rule(varargslist_item), tok(DEL_COMMA)) DEF_RULE_NC(varargslist_item, or(3), rule(varargslist_name), rule(varargslist_star), rule(varargslist_dbl_star)) -DEF_RULE_NC(varargslist_name, and_ident(2), tok(NAME), opt_rule(varargslist_equal)) +DEF_RULE_NC(varargslist_name, and_ident(2), tok(NAME), opt_rule(generic_equal_test)) DEF_RULE_NC(varargslist_star, and(2), tok(OP_STAR), opt_rule(vfpdef)) DEF_RULE_NC(varargslist_dbl_star, and(2), tok(OP_DBL_STAR), tok(NAME)) -DEF_RULE_NC(varargslist_equal, and_ident(2), tok(DEL_EQUAL), rule(test)) DEF_RULE_NC(vfpdef, and_ident(1), tok(NAME)) // stmt: compound_stmt | simple_stmt @@ -94,20 +98,22 @@ DEF_RULE_NC(simple_stmt, and_ident(2), rule(simple_stmt_2), tok(NEWLINE)) DEF_RULE(simple_stmt_2, c(generic_all_nodes), list_with_end, rule(small_stmt), tok(DEL_SEMICOLON)) // small_stmt: expr_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | nonlocal_stmt | assert_stmt -// expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) +// expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) // testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [','] +// annassign: ':' test ['=' (yield_expr|testlist_star_expr)] // augassign: '+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=' -// # For normal assignments, additional restrictions enforced by the interpreter +// # For normal and annotated assignments, additional restrictions enforced by the interpreter DEF_RULE_NC(small_stmt, or(8), rule(del_stmt), rule(pass_stmt), rule(flow_stmt), rule(import_stmt), rule(global_stmt), rule(nonlocal_stmt), rule(assert_stmt), rule(expr_stmt)) DEF_RULE(expr_stmt, c(expr_stmt), and(2), rule(testlist_star_expr), opt_rule(expr_stmt_2)) -DEF_RULE_NC(expr_stmt_2, or(2), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) +DEF_RULE_NC(expr_stmt_2, or(3), rule(annassign), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) DEF_RULE_NC(expr_stmt_augassign, and_ident(2), rule(augassign), rule(expr_stmt_6)) DEF_RULE_NC(expr_stmt_assign_list, one_or_more, rule(expr_stmt_assign)) DEF_RULE_NC(expr_stmt_assign, and_ident(2), tok(DEL_EQUAL), rule(expr_stmt_6)) DEF_RULE_NC(expr_stmt_6, or(2), rule(yield_expr), rule(testlist_star_expr)) DEF_RULE(testlist_star_expr, c(generic_tuple), list_with_end, rule(testlist_star_expr_2), tok(DEL_COMMA)) DEF_RULE_NC(testlist_star_expr_2, or(2), rule(star_expr), rule(test)) +DEF_RULE_NC(annassign, and(3), tok(DEL_COLON), rule(test), opt_rule(expr_stmt_assign)) DEF_RULE_NC(augassign, or(13), tok(DEL_PLUS_EQUAL), tok(DEL_MINUS_EQUAL), tok(DEL_STAR_EQUAL), tok(DEL_AT_EQUAL), tok(DEL_SLASH_EQUAL), tok(DEL_PERCENT_EQUAL), tok(DEL_AMPERSAND_EQUAL), tok(DEL_PIPE_EQUAL), tok(DEL_CARET_EQUAL), tok(DEL_DBL_LESS_EQUAL), tok(DEL_DBL_MORE_EQUAL), tok(DEL_DBL_STAR_EQUAL), tok(DEL_DBL_SLASH_EQUAL)) // del_stmt: 'del' exprlist @@ -182,10 +188,10 @@ DEF_RULE_NC(async_stmt_2, or(3), rule(funcdef), rule(with_stmt), rule(for_stmt)) #else DEF_RULE_NC(compound_stmt, or(8), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated)) #endif -DEF_RULE(if_stmt, c(if_stmt), and(6), tok(KW_IF), rule(test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) +DEF_RULE(if_stmt, c(if_stmt), and(6), tok(KW_IF), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) DEF_RULE_NC(if_stmt_elif_list, one_or_more, rule(if_stmt_elif)) -DEF_RULE_NC(if_stmt_elif, and(4), tok(KW_ELIF), rule(test), tok(DEL_COLON), rule(suite)) -DEF_RULE(while_stmt, c(while_stmt), and(5), tok(KW_WHILE), rule(test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) +DEF_RULE_NC(if_stmt_elif, and(4), tok(KW_ELIF), rule(namedexpr_test), tok(DEL_COLON), rule(suite)) +DEF_RULE(while_stmt, c(while_stmt), and(5), tok(KW_WHILE), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) DEF_RULE(for_stmt, c(for_stmt), and(7), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(testlist), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) DEF_RULE(try_stmt, c(try_stmt), and(4), tok(KW_TRY), tok(DEL_COLON), rule(suite), rule(try_stmt_2)) DEF_RULE_NC(try_stmt_2, or(2), rule(try_stmt_except_and_more), rule(try_stmt_finally)) @@ -208,6 +214,12 @@ DEF_RULE(suite_block_stmts, c(generic_all_nodes), one_or_more, rule(stmt)) // lambdef: 'lambda' [varargslist] ':' test // lambdef_nocond: 'lambda' [varargslist] ':' test_nocond +#if MICROPY_PY_ASSIGN_EXPR +DEF_RULE(namedexpr_test, c(namedexpr), and_ident(2), rule(test), opt_rule(namedexpr_test_2)) +DEF_RULE_NC(namedexpr_test_2, and_ident(2), tok(OP_ASSIGN), rule(test)) +#else +DEF_RULE_NC(namedexpr_test, or(1), rule(test)) +#endif DEF_RULE_NC(test, or(2), rule(lambdef), rule(test_if_expr)) DEF_RULE(test_if_expr, c(test_if_expr), and_ident(2), rule(or_test), opt_rule(test_if_else)) DEF_RULE_NC(test_if_else, and(4), tok(KW_IF), rule(or_test), tok(KW_ELSE), rule(test)) @@ -274,7 +286,7 @@ DEF_RULE_NC(atom_2b, or(2), rule(yield_expr), rule(testlist_comp)) DEF_RULE(atom_bracket, c(atom_bracket), and(3), tok(DEL_BRACKET_OPEN), opt_rule(testlist_comp), tok(DEL_BRACKET_CLOSE)) DEF_RULE(atom_brace, c(atom_brace), and(3), tok(DEL_BRACE_OPEN), opt_rule(dictorsetmaker), tok(DEL_BRACE_CLOSE)) DEF_RULE_NC(testlist_comp, and_ident(2), rule(testlist_comp_2), opt_rule(testlist_comp_3)) -DEF_RULE_NC(testlist_comp_2, or(2), rule(star_expr), rule(test)) +DEF_RULE_NC(testlist_comp_2, or(2), rule(star_expr), rule(namedexpr_test)) DEF_RULE_NC(testlist_comp_3, or(2), rule(comp_for), rule(testlist_comp_3b)) DEF_RULE_NC(testlist_comp_3b, and_ident(2), tok(DEL_COMMA), opt_rule(testlist_comp_3c)) DEF_RULE_NC(testlist_comp_3c, list_with_end, rule(testlist_comp_2), tok(DEL_COMMA)) @@ -310,8 +322,7 @@ DEF_RULE(testlist, c(generic_tuple), list_with_end, rule(test), tok(DEL_COMMA)) // TODO dictorsetmaker lets through more than is allowed DEF_RULE_NC(dictorsetmaker, and_ident(2), rule(dictorsetmaker_item), opt_rule(dictorsetmaker_tail)) #if MICROPY_PY_BUILTINS_SET -DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and_ident(2), rule(test), opt_rule(dictorsetmaker_colon)) -DEF_RULE_NC(dictorsetmaker_colon, and_ident(2), tok(DEL_COLON), rule(test)) +DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and_ident(2), rule(test), opt_rule(generic_colon_test)) #else DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and(3), rule(test), tok(DEL_COLON), rule(test)) #endif @@ -340,8 +351,12 @@ DEF_RULE_NC(arglist_dbl_star, and(2), tok(OP_DBL_STAR), rule(test)) // comp_if: 'if' test_nocond [comp_iter] DEF_RULE_NC(argument, and_ident(2), rule(test), opt_rule(argument_2)) -DEF_RULE_NC(argument_2, or(2), rule(comp_for), rule(argument_3)) -DEF_RULE_NC(argument_3, and_ident(2), tok(DEL_EQUAL), rule(test)) +#if MICROPY_PY_ASSIGN_EXPR +DEF_RULE_NC(argument_2, or(3), rule(comp_for), rule(generic_equal_test), rule(argument_3)) +DEF_RULE_NC(argument_3, and(2), tok(OP_ASSIGN), rule(test)) +#else +DEF_RULE_NC(argument_2, or(2), rule(comp_for), rule(generic_equal_test)) +#endif DEF_RULE_NC(comp_iter, or(2), rule(comp_for), rule(comp_if)) DEF_RULE_NC(comp_for, and_blank(5), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(or_test), opt_rule(comp_iter)) DEF_RULE_NC(comp_if, and(3), tok(KW_IF), rule(test_nocond), opt_rule(comp_iter)) diff --git a/py/lexer.c b/py/lexer.c index d324054ba4..c04b9f1a8d 100644 --- a/py/lexer.c +++ b/py/lexer.c @@ -232,7 +232,8 @@ STATIC void indent_pop(mp_lexer_t *lex) { // 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 + "()[]{},;~" // singles + ":e=" // : := " >= >> >>= "*e=c*e=" // * *= ** **= @@ -252,8 +253,9 @@ 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_COLON, MP_TOKEN_DEL_SEMICOLON, MP_TOKEN_OP_TILDE, + 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, @@ -474,7 +476,7 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) // 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(translate("unicode name escapes")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("unicode name escapes")); break; default: if (c >= '0' && c <= '7') { diff --git a/py/lexer.h b/py/lexer.h index 1e21166382..8f9960877c 100644 --- a/py/lexer.h +++ b/py/lexer.h @@ -104,6 +104,7 @@ typedef enum _mp_token_kind_t { MP_TOKEN_KW_WITH, MP_TOKEN_KW_YIELD, + MP_TOKEN_OP_ASSIGN, MP_TOKEN_OP_TILDE, // Order of these 6 matches corresponding mp_binary_op_t operator diff --git a/py/makecompresseddata.py b/py/makecompresseddata.py new file mode 100644 index 0000000000..9603de8713 --- /dev/null +++ b/py/makecompresseddata.py @@ -0,0 +1,205 @@ +from __future__ import print_function + +import collections +import re +import sys + +import gzip +import zlib + + +_COMPRESSED_MARKER = 0xFF + + +def check_non_ascii(msg): + for c in msg: + if ord(c) >= 0x80: + print( + 'Unable to generate compressed data: message "{}" contains a non-ascii character "{}".'.format( + msg, c + ), + file=sys.stderr, + ) + sys.exit(1) + + +# Replace with . +# Trival scheme to demo/test. +def space_compression(error_strings): + for line in error_strings: + check_non_ascii(line) + result = "" + for i in range(len(line)): + if i > 0 and line[i] == " ": + result = result[:-1] + result += "\\{:03o}".format(ord(line[i - 1])) + else: + result += line[i] + error_strings[line] = result + return None + + +# Replace common words with <0x80 | index>. +# Index is into a table of words stored as aaaaa<0x80|a>bbb<0x80|b>... +# Replaced words are assumed to have spaces either side to avoid having to store the spaces in the compressed strings. +def word_compression(error_strings): + topn = collections.Counter() + + for line in error_strings.keys(): + check_non_ascii(line) + for word in line.split(" "): + topn[word] += 1 + + # Order not just by frequency, but by expected saving. i.e. prefer a longer string that is used less frequently. + # Use the word itself for ties so that compression is deterministic. + def bytes_saved(item): + w, n = item + return -((len(w) + 1) * (n - 1)), w + + top128 = sorted(topn.items(), key=bytes_saved)[:128] + + index = [w for w, _ in top128] + index_lookup = {w: i for i, w in enumerate(index)} + + for line in error_strings.keys(): + result = "" + need_space = False + for word in line.split(" "): + if word in index_lookup: + result += "\\{:03o}".format(0b10000000 | index_lookup[word]) + need_space = False + else: + if need_space: + result += " " + need_space = True + result += word + error_strings[line] = result.strip() + + return "".join(w[:-1] + "\\{:03o}".format(0b10000000 | ord(w[-1])) for w in index) + + +# Replace chars in text with variable length bit sequence. +# For comparison only (the table is not emitted). +def huffman_compression(error_strings): + # https://github.com/tannewt/huffman + import huffman + + all_strings = "".join(error_strings) + cb = huffman.codebook(collections.Counter(all_strings).items()) + + for line in error_strings: + b = "1" + for c in line: + b += cb[c] + n = len(b) + if n % 8 != 0: + n += 8 - (n % 8) + result = "" + for i in range(0, n, 8): + result += "\\{:03o}".format(int(b[i : i + 8], 2)) + if len(result) > len(line) * 4: + result = line + error_strings[line] = result + + # TODO: This would be the prefix lengths and the table ordering. + return "_" * (10 + len(cb)) + + +# Replace common N-letter sequences with <0x80 | index>, where +# the common sequences are stored in a separate table. +# This isn't very useful, need a smarter way to find top-ngrams. +def ngram_compression(error_strings): + topn = collections.Counter() + N = 2 + + for line in error_strings.keys(): + check_non_ascii(line) + if len(line) < N: + continue + for i in range(0, len(line) - N, N): + topn[line[i : i + N]] += 1 + + def bytes_saved(item): + w, n = item + return -(len(w) * (n - 1)) + + top128 = sorted(topn.items(), key=bytes_saved)[:128] + + index = [w for w, _ in top128] + index_lookup = {w: i for i, w in enumerate(index)} + + for line in error_strings.keys(): + result = "" + for i in range(0, len(line) - N + 1, N): + word = line[i : i + N] + if word in index_lookup: + result += "\\{:03o}".format(0b10000000 | index_lookup[word]) + else: + result += word + if len(line) % N != 0: + result += line[len(line) - len(line) % N :] + error_strings[line] = result.strip() + + return "".join(index) + + +def main(collected_path, fn): + error_strings = collections.OrderedDict() + max_uncompressed_len = 0 + num_uses = 0 + + # Read in all MP_ERROR_TEXT strings. + with open(collected_path, "r") as f: + for line in f: + line = line.strip() + if not line: + continue + num_uses += 1 + error_strings[line] = None + max_uncompressed_len = max(max_uncompressed_len, len(line)) + + # So that objexcept.c can figure out how big the buffer needs to be. + print("#define MP_MAX_UNCOMPRESSED_TEXT_LEN ({})".format(max_uncompressed_len)) + + # Run the compression. + compressed_data = fn(error_strings) + + # Print the data table. + print('MP_COMPRESSED_DATA("{}")'.format(compressed_data)) + + # Print the replacements. + for uncomp, comp in error_strings.items(): + if uncomp == comp: + prefix = "" + else: + prefix = "\\{:03o}".format(_COMPRESSED_MARKER) + print('MP_MATCH_COMPRESSED("{}", "{}{}")'.format(uncomp, prefix, comp)) + + # Used to calculate the "true" length of the (escaped) compressed strings. + def unescape(s): + return re.sub(r"\\\d\d\d", "!", s) + + # Stats. Note this doesn't include the cost of the decompressor code. + uncomp_len = sum(len(s) + 1 for s in error_strings.keys()) + comp_len = sum(1 + len(unescape(s)) + 1 for s in error_strings.values()) + data_len = len(compressed_data) + 1 if compressed_data else 0 + print("// Total input length: {}".format(uncomp_len)) + print("// Total compressed length: {}".format(comp_len)) + print("// Total data length: {}".format(data_len)) + print("// Predicted saving: {}".format(uncomp_len - comp_len - data_len)) + + # Somewhat meaningless comparison to zlib/gzip. + all_input_bytes = "\\0".join(error_strings.keys()).encode() + print() + if hasattr(gzip, "compress"): + gzip_len = len(gzip.compress(all_input_bytes)) + num_uses * 4 + print("// gzip length: {}".format(gzip_len)) + print("// Percentage of gzip: {:.1f}%".format(100 * (comp_len + data_len) / gzip_len)) + if hasattr(zlib, "compress"): + zlib_len = len(zlib.compress(all_input_bytes)) + num_uses * 4 + print("// zlib length: {}".format(zlib_len)) + print("// Percentage of zlib: {:.1f}%".format(100 * (comp_len + data_len) / zlib_len)) + + +if __name__ == "__main__": + main(sys.argv[1], word_compression) diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index cbd80224f7..cec13b242a 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -27,7 +27,7 @@ elif platform.python_version_tuple()[0] == "3": unichr = chr # end compatibility code -# Blacklist of qstrings that are specially handled in further +# Blocklist of qstrings that are specially handled in further # processing and should be ignored QSTRING_BLOCK_LIST = set(["NULL", "number_of"]) diff --git a/py/map.c b/py/map.c index 2f2c11c639..e0dc7dbc5a 100644 --- a/py/map.c +++ b/py/map.c @@ -179,6 +179,7 @@ mp_map_elem_t *PLACE_IN_ITCM(mp_map_lookup)(mp_map_t * map, mp_obj_t index, mp_m --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; diff --git a/py/misc.h b/py/misc.h index 757f2510d4..a2cd3d5f68 100644 --- a/py/misc.h +++ b/py/misc.h @@ -165,6 +165,7 @@ bool unichar_isprint(unichar c); bool unichar_isdigit(unichar c); bool unichar_isxdigit(unichar c); bool unichar_isident(unichar c); +bool unichar_isalnum(unichar c); bool unichar_isupper(unichar c); bool unichar_islower(unichar c); unichar unichar_tolower(unichar c); @@ -241,14 +242,83 @@ extern mp_uint_t mp_verbose_flag; /** float internals *************/ #if MICROPY_PY_BUILTINS_FLOAT + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE #define MP_FLOAT_EXP_BITS (11) #define MP_FLOAT_FRAC_BITS (52) +typedef uint64_t mp_float_uint_t; #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT #define MP_FLOAT_EXP_BITS (8) #define MP_FLOAT_FRAC_BITS (23) +typedef uint32_t mp_float_uint_t; #endif + #define MP_FLOAT_EXP_BIAS ((1 << (MP_FLOAT_EXP_BITS - 1)) - 1) + +typedef union _mp_float_union_t { + mp_float_t f; + #if MP_ENDIANNESS_LITTLE + struct { + mp_float_uint_t frc : MP_FLOAT_FRAC_BITS; + mp_float_uint_t exp : MP_FLOAT_EXP_BITS; + mp_float_uint_t sgn : 1; + } p; + #else + struct { + mp_float_uint_t sgn : 1; + mp_float_uint_t exp : MP_FLOAT_EXP_BITS; + mp_float_uint_t frc : MP_FLOAT_FRAC_BITS; + } p; + #endif + mp_float_uint_t i; +} mp_float_union_t; + #endif // MICROPY_PY_BUILTINS_FLOAT +/** ROM string compression *************/ + +#if MICROPY_ROM_TEXT_COMPRESSION + +#ifdef NO_QSTR + +// Compression enabled but doing QSTR extraction. +// So leave MP_COMPRESSED_ROM_TEXT in place for makeqstrdefs.py / makecompresseddata.py to find them. + +#else + +// Compression enabled and doing a regular build. +// Map MP_COMPRESSED_ROM_TEXT to the compressed strings. + +// Force usage of the MP_ERROR_TEXT macro by requiring an opaque type. +typedef struct {} *mp_rom_error_text_t; + +#include + +inline __attribute__((always_inline)) const char *MP_COMPRESSED_ROM_TEXT(const char *msg) { + // "genhdr/compressed.data.h" contains an invocation of the MP_MATCH_COMPRESSED macro for each compressed string. + // The giant if(strcmp) tree is optimized by the compiler, which turns this into a direct return of the compressed data. + #define MP_MATCH_COMPRESSED(a, b) if (strcmp(msg, a) == 0) { return b; } else + + // It also contains a single invocation of the MP_COMPRESSED_DATA macro, we don't need that here. + #define MP_COMPRESSED_DATA(x) + + #include "genhdr/compressed.data.h" + +#undef MP_COMPRESSED_DATA +#undef MP_MATCH_COMPRESSED + + return msg; +} + +#endif + +#else + +// Compression not enabled, just make it a no-op. + +typedef const char *mp_rom_error_text_t; +#define MP_COMPRESSED_ROM_TEXT(x) x + +#endif // MICROPY_ROM_TEXT_COMPRESSION + #endif // MICROPY_INCLUDED_PY_MISC_H diff --git a/py/mkenv.mk b/py/mkenv.mk index 8c7fc3fb7a..89628759e9 100644 --- a/py/mkenv.mk +++ b/py/mkenv.mk @@ -69,11 +69,6 @@ OBJCOPY = $(CROSS_COMPILE)objcopy SIZE = $(CROSS_COMPILE)size STRIP = $(CROSS_COMPILE)strip AR = $(CROSS_COMPILE)ar -ifeq ($(MICROPY_FORCE_32BIT),1) -CC += -m32 -CXX += -m32 -LD += -m32 -endif MAKE_FROZEN = $(PYTHON3) $(TOP)/tools/make-frozen.py MPY_CROSS = $(TOP)/mpy-cross/mpy-cross @@ -82,6 +77,8 @@ PREPROCESS_FROZEN_MODULES = PYTHONPATH=$(TOP)/tools/python-semver $(TOP)/tools/p MPY_LIB_DIR = $(TOP)/../micropython-lib +MPY_LIB_DIR = $(TOP)/../micropython-lib + all: .PHONY: all diff --git a/py/mkrules.mk b/py/mkrules.mk index bca3a3f591..a7563790dc 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -4,6 +4,9 @@ THIS_MAKEFILE = $(lastword $(MAKEFILE_LIST)) include $(dir $(THIS_MAKEFILE))mkenv.mk endif +# Extra deps that need to happen before object compilation. +OBJ_EXTRA_ORDER_DEPS = + # This file expects that OBJ contains a list of all of the object files. # The directory portion of each object file is used to locate the source # and should not contain any ..'s but rather be relative to the top of the @@ -98,20 +101,6 @@ $(OBJ_DIRS): $(HEADER_BUILD): $(Q)$(MKDIR) -p $@ -ifneq ($(FROZEN_MANIFEST),) -# to build frozen_content.c from a manifest -$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h - $(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) $(FROZEN_MANIFEST) - -ifneq ($(FROZEN_DIR),) -$(error FROZEN_DIR cannot be used in conjunction with FROZEN_MANIFEST) -endif - -ifneq ($(FROZEN_MPY_DIR),) -$(error FROZEN_MPY_DIR cannot be used in conjunction with FROZEN_MANIFEST) -endif -endif - ifneq ($(FROZEN_DIR),) $(info Warning: FROZEN_DIR is deprecated in favour of FROZEN_MANIFEST) $(BUILD)/frozen.c: $(wildcard $(FROZEN_DIR)/*) $(HEADER_BUILD) $(FROZEN_EXTRA_DEPS) diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 3b962e678e..0d53a07392 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -160,7 +160,7 @@ STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { str[3] = (c & 0x3F) | 0x80; len = 4; } else { - mp_raise_ValueError(translate("chr() arg not in range(0x110000)")); + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(0x110000)")); } return mp_obj_new_str_via_qstr((char *)str, len); #else @@ -169,7 +169,7 @@ STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { uint8_t str[1] = {ord}; return mp_obj_new_str_via_qstr((char *)str, 1); } else { - mp_raise_ValueError(translate("chr() arg not in range(256)")); + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(256)")); } #endif } @@ -247,10 +247,10 @@ STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { vstr_init(&line, 16); int ret = mp_hal_readline(&line, ""); if (ret == CHAR_CTRL_C) { - nlr_raise(mp_obj_new_exception(&mp_type_KeyboardInterrupt)); + mp_raise_type(&mp_type_KeyboardInterrupt); } if (line.len == 0 && ret == CHAR_CTRL_D) { - nlr_raise(mp_obj_new_exception(&mp_type_EOFError)); + mp_raise_type(&mp_type_EOFError); } return mp_obj_new_str_from_vstr(&mp_type_str, &line); } @@ -288,7 +288,7 @@ STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t if (default_elem != NULL) { best_obj = default_elem->value; } else { - mp_raise_ValueError(translate("arg is an empty sequence")); + mp_raise_ValueError(MP_ERROR_TEXT("arg is an empty sequence")); } } return best_obj; @@ -324,7 +324,7 @@ 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) { - nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + mp_raise_type(&mp_type_StopIteration); } else { return ret; } @@ -338,7 +338,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); 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_msg(&mp_type_StopIteration, NULL); + mp_raise_type(&mp_type_StopIteration); } else { return ret; } @@ -375,10 +375,10 @@ STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("ord expects a character")); + mp_raise_TypeError(MP_ERROR_TEXT("ord expects a character")); #else mp_raise_TypeError_varg( - translate("ord() expected a character, but string of length %d found"), (int)len); + 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); @@ -389,7 +389,7 @@ STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); default: #if !MICROPY_PY_BUILTINS_POW3 - mp_raise_msg(&mp_type_NotImplementedError, translate("3-arg pow() not supported")); + 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 @@ -515,7 +515,7 @@ STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { 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, num_dig); + 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); @@ -551,7 +551,7 @@ 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(translate("must use keyword argument for key function")); + 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, args, NULL); mp_obj_list_sort(1, &self, kwargs); @@ -564,7 +564,11 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); 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 - ((defval == MP_OBJ_NULL) ? mp_load_method : mp_load_method_maybe)(base, attr, dest); + 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) { @@ -779,8 +783,6 @@ STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { { 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) }, - // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/ - // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation // Extra builtins as defined by a port MICROPY_PORT_BUILTINS diff --git a/py/modcmath.c b/py/modcmath.c index 63f289d498..a361ab53b4 100644 --- a/py/modcmath.c +++ b/py/modcmath.c @@ -72,7 +72,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_exp_obj, mp_cmath_exp); 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(0.5 * MICROPY_FLOAT_C_FUN(log)(real * real + imag * imag), MICROPY_FLOAT_C_FUN(atan2)(imag, real)); + 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); @@ -81,7 +81,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_log_obj, mp_cmath_log); 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(0.5 * MICROPY_FLOAT_C_FUN(log10)(real * real + imag * imag), 0.4342944819032518 * MICROPY_FLOAT_C_FUN(atan2)(imag, real)); + 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 @@ -90,8 +90,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_log10_obj, mp_cmath_log10); 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, 0.25); - mp_float_t theta = 0.5 * MICROPY_FLOAT_C_FUN(atan2)(imag, real); + 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); diff --git a/py/modio.c b/py/modio.c index 01dcbff1e9..4646e17e88 100644 --- a/py/modio.c +++ b/py/modio.c @@ -59,12 +59,17 @@ STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int 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 = mp_call_method_n_kw(1, 0, dest); - if (ret == mp_const_none) { + 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 { - return mp_obj_get_int(ret); + *errcode = -ret; + return MP_STREAM_ERROR; } } STATIC mp_uint_t iobase_read(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode) { diff --git a/py/modmath.c b/py/modmath.c index 413137e352..6c86271911 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -36,9 +36,11 @@ // 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(translate("math domain error")); + 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)) { @@ -134,7 +136,17 @@ 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) @@ -150,7 +162,14 @@ 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) @@ -224,12 +243,9 @@ STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { mp_float_t base = mp_obj_get_float(args[1]); if (base <= (mp_float_t)0.0) { math_error(); -// Turn off warning when comparing exactly with integral value 1.0 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wfloat-equal" } else if (base == (mp_float_t)1.0) { #pragma GCC diagnostic pop - mp_raise_msg(&mp_type_ZeroDivisionError, translate("division by zero")); + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("division by zero")); } return mp_obj_new_float(l / MICROPY_FLOAT_C_FUN(log)(base)); } @@ -252,7 +268,13 @@ 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 fractional_part = MICROPY_FLOAT_C_FUN(modf)(mp_obj_get_float(x_obj), &int_part); + 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); @@ -300,7 +322,7 @@ STATIC mp_obj_t mp_math_factorial_inner(mp_uint_t start, mp_uint_t end) { 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_msg(&mp_type_ValueError, translate("negative factorial")); + mp_raise_ValueError(MP_ERROR_TEXT("negative factorial")); } else if (max == 0) { return MP_OBJ_NEW_SMALL_INT(1); } @@ -314,7 +336,7 @@ STATIC mp_obj_t mp_math_factorial(mp_obj_t x_obj) { 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_msg(&mp_type_ValueError, translate("negative factorial")); + mp_raise_ValueError(MP_ERROR_TEXT("negative factorial")); } else if (max <= 1) { return MP_OBJ_NEW_SMALL_INT(1); } diff --git a/py/modmicropython.c b/py/modmicropython.c index 64d61b48ce..411cb0dc2a 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -132,9 +132,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_lock_obj, mp_micropython_he STATIC mp_obj_t mp_micropython_heap_unlock(void) { gc_unlock(); - return mp_const_none; + return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(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_MEM(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) @@ -152,7 +159,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_micropython_kbd_intr_obj, mp_micropython_kbd #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, translate("schedule queue full")); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("schedule queue full")); } return mp_const_none; } @@ -186,6 +193,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = { #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) }, diff --git a/py/modstruct.c b/py/modstruct.c index 0053d2577e..b263fa6c36 100644 --- a/py/modstruct.c +++ b/py/modstruct.c @@ -145,7 +145,7 @@ STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { // negative offsets are relative to the end of the buffer offset = bufinfo.len + offset; if (offset < 0) { - mp_raise_ValueError(translate("buffer too small")); + mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } } p += offset; @@ -154,7 +154,7 @@ STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { // Check that the input buffer is big enough to unpack all the values if (p + total_sz > end_p) { - mp_raise_ValueError(translate("buffer too small")); + mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } for (size_t i = 0; i < num_items;) { @@ -248,7 +248,7 @@ STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { // negative offsets are relative to the end of the buffer offset = (mp_int_t)bufinfo.len + offset; if (offset < 0) { - mp_raise_ValueError(translate("buffer too small")); + mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } } byte *p = (byte *)bufinfo.buf; @@ -258,7 +258,7 @@ STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { // 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(translate("buffer too small")); + mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); } struct_pack_into_internal(args[0], p, n_args - 3, &args[3]); diff --git a/py/modsys.c b/py/modsys.c index ecabe247eb..cbd1712920 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -55,7 +55,7 @@ const mp_print_t mp_sys_stdout_print = {&mp_sys_stdout_obj, mp_stream_write_adap #endif // version - Python language version that this implementation conforms to, as a string -STATIC const MP_DEFINE_STR_OBJ(version_obj, "3.4.0"); +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) @@ -107,7 +107,7 @@ STATIC const mp_rom_obj_tuple_t mp_sys_implementation_obj = { #ifdef MICROPY_PY_SYS_PLATFORM // platform - the platform that MicroPython is running on -STATIC const MP_DEFINE_STR_OBJ(platform_obj, MICROPY_PY_SYS_PLATFORM); +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 @@ -189,11 +189,11 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { 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(&version_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(&platform_obj) }, + { 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) }, @@ -210,7 +210,7 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { // 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_maxsize_obj) }, + { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_PTR(&mp_sys_maxsize_obj) }, #endif #endif diff --git a/py/modthread.c b/py/modthread.c index 84b12f7fb1..5ef5930545 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -237,7 +237,7 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) } else { // positional and keyword arguments if (mp_obj_get_type(args[2]) != &mp_type_dict) { - mp_raise_TypeError(translate("expecting a dict for keyword args")); + 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); @@ -251,7 +251,7 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) } } - // copy agross the positional arguments + // 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)); @@ -273,7 +273,7 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) 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) { - nlr_raise(mp_obj_new_exception(&mp_type_SystemExit)); + mp_raise_type(&mp_type_SystemExit); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_exit_obj, mod_thread_exit); diff --git a/py/moduerrno.c b/py/moduerrno.c index a5ed50ce01..d13028e73a 100644 --- a/py/moduerrno.c +++ b/py/moduerrno.c @@ -138,31 +138,31 @@ const char *mp_common_errno_to_str(mp_obj_t errno_val, char *buf, size_t len) { const compressed_string_t *desc = NULL; switch (MP_OBJ_SMALL_INT_VALUE(errno_val)) { case EPERM: - desc = translate("Permission denied"); + desc = MP_ERROR_TEXT("Permission denied"); break; case ENOENT: - desc = translate("No such file/directory"); + desc = MP_ERROR_TEXT("No such file/directory"); break; case EIO: - desc = translate("Input/output error"); + desc = MP_ERROR_TEXT("Input/output error"); break; case EACCES: - desc = translate("Permission denied"); + desc = MP_ERROR_TEXT("Permission denied"); break; case EEXIST: - desc = translate("File exists"); + desc = MP_ERROR_TEXT("File exists"); break; case ENODEV: - desc = translate("Unsupported operation"); + desc = MP_ERROR_TEXT("Unsupported operation"); break; case EINVAL: - desc = translate("Invalid argument"); + desc = MP_ERROR_TEXT("Invalid argument"); break; case ENOSPC: - desc = translate("No space left on device"); + desc = MP_ERROR_TEXT("No space left on device"); break; case EROFS: - desc = translate("Read-only filesystem"); + desc = MP_ERROR_TEXT("Read-only filesystem"); break; } if (desc != NULL && decompress_length(desc) <= len) { diff --git a/py/mpconfig.h b/py/mpconfig.h index 0682ccc878..cc5c5d154f 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -58,25 +58,28 @@ // A MicroPython object is a machine word having the following form: // - xxxx...xxx1 : a small int, bits 1 and above are the value -// - xxxx...xx10 : a qstr, bits 2 and above are the value +// - xxxx...x010 : a qstr, bits 3 and above are the value +// - xxxx...x110 : an immediate object, bits 3 and above are the value // - xxxx...xx00 : a pointer to an mp_obj_base_t (unless a fake object) #define MICROPY_OBJ_REPR_A (0) // A MicroPython object is a machine word having the following form: // - xxxx...xx01 : a small int, bits 2 and above are the value -// - xxxx...xx11 : a qstr, bits 2 and above are the value +// - xxxx...x011 : a qstr, bits 3 and above are the value +// - xxxx...x111 : an immediate object, bits 3 and above are the value // - xxxx...xxx0 : a pointer to an mp_obj_base_t (unless a fake object) #define MICROPY_OBJ_REPR_B (1) // A MicroPython object is a machine word having the following form (called R): // - iiiiiiii iiiiiiii iiiiiiii iiiiiii1 small int with 31-bit signed value -// - 01111111 1qqqqqqq qqqqqqqq qqqqq110 str with 20-bit qstr value +// - 01111111 1qqqqqqq qqqqqqqq qqqq0110 str with 19-bit qstr value +// - 01111111 10000000 00000000 ssss1110 immediate object with 4-bit value // - s1111111 10000000 00000000 00000010 +/- inf // - s1111111 1xxxxxxx xxxxxxxx xxxxx010 nan, x != 0 // - seeeeeee efffffff ffffffff ffffff10 30-bit fp, e != 0xff // - pppppppp pppppppp pppppppp pppppp00 ptr (4 byte alignment) -// Str and float stored as O = R + 0x80800000, retrieved as R = O - 0x80800000. -// This makes strs easier to encode/decode as they have zeros in the top 9 bits. +// Str, immediate and float stored as O = R + 0x80800000, retrieved as R = O - 0x80800000. +// This makes strs/immediates easier to encode/decode as they have zeros in the top 9 bits. // This scheme only works with 32-bit word size and float enabled. #define MICROPY_OBJ_REPR_C (2) @@ -86,6 +89,7 @@ // - 01111111 11111000 00000000 00000000 00000000 00000000 00000000 00000000 normalised nan // - 01111111 11111101 iiiiiiii iiiiiiii iiiiiiii iiiiiiii iiiiiiii iiiiiii1 small int // - 01111111 11111110 00000000 00000000 qqqqqqqq qqqqqqqq qqqqqqqq qqqqqqq1 str +// - 01111111 11111111 ss000000 00000000 00000000 00000000 00000000 00000000 immediate object // - 01111111 11111100 00000000 00000000 pppppppp pppppppp pppppppp pppppp00 ptr (4 byte alignment) // Stored as O = R + 0x8004000000000000, retrieved as R = O - 0x8004000000000000. // This makes pointers have all zeros in the top 32 bits. @@ -97,6 +101,13 @@ #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) #endif +// Whether to encode None/False/True as immediate objects instead of pointers to +// real objects. Reduces code size by a decent amount without hurting +// performance, for all representations except D on some architectures. +#ifndef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D) +#endif + /*****************************************************************************/ /* Memory allocation policy */ @@ -342,6 +353,18 @@ // Convenience definition for whether any native or inline assembler emitter is enabled #define MICROPY_EMIT_MACHINE_CODE (MICROPY_EMIT_NATIVE || MICROPY_EMIT_INLINE_ASM) +// Whether native relocatable code loaded from .mpy files is explicitly tracked +// so that the GC cannot reclaim it. Needed on architectures that allocate +// executable memory on the MicroPython heap and don't explicitly track this +// data some other way. +#ifndef MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE +#if !MICROPY_EMIT_MACHINE_CODE || defined(MP_PLAT_ALLOC_EXEC) || defined(MP_PLAT_COMMIT_EXEC) +#define MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE (0) +#else +#define MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE (1) +#endif +#endif + /*****************************************************************************/ /* Compiler configuration */ @@ -585,11 +608,31 @@ #define MICROPY_HELPER_REPL (0) #endif +// Allow enabling debug prints after each REPL line +#ifndef MICROPY_REPL_INFO +#define MICROPY_REPL_INFO (0) +#endif + // Whether to include emacs-style readline behavior in REPL #ifndef MICROPY_REPL_EMACS_KEYS #define MICROPY_REPL_EMACS_KEYS (0) #endif +// Whether to include emacs-style word movement/kill readline behavior in REPL. +// This adds Alt+F, Alt+B, Alt+D and Alt+Backspace for forward-word, backward-word, forward-kill-word +// and backward-kill-word, respectively. +#ifndef MICROPY_REPL_EMACS_WORDS_MOVE +#define MICROPY_REPL_EMACS_WORDS_MOVE (0) +#endif + +// Whether to include extra convenience keys for word movement/kill in readline REPL. +// This adds Ctrl+Right, Ctrl+Left and Ctrl+W for forward-word, backward-word and backward-kill-word +// respectively. Ctrl+Delete is not implemented because it's a very different escape sequence. +// Depends on MICROPY_REPL_EMACS_WORDS_MOVE. +#ifndef MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (0) +#endif + // Whether to implement auto-indent in REPL #ifndef MICROPY_REPL_AUTO_INDENT #define MICROPY_REPL_AUTO_INDENT (0) @@ -723,7 +766,7 @@ typedef double mp_float_t; // Whether to support module-level __getattr__ (see PEP 562) #ifndef MICROPY_MODULE_GETATTR -#define MICROPY_MODULE_GETATTR (0) +#define MICROPY_MODULE_GETATTR (1) #endif // Whether module weak links are supported @@ -834,6 +877,11 @@ typedef double mp_float_t; #define MICROPY_PY_ASYNC_AWAIT (1) #endif +// Support for assignment expressions with := (see PEP 572, Python 3.8+) +#ifndef MICROPY_PY_ASSIGN_EXPR +#define MICROPY_PY_ASSIGN_EXPR (1) +#endif + // Non-standard .pend_throw() method for generators, allowing for // Future-like behavior with respect to exception handling: an // exception set with .pend_throw() will activate on the next call @@ -919,6 +967,11 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_SLICE_ATTRS (0) #endif +// Whether to support the .indices(len) method on slice objects +#ifndef MICROPY_PY_BUILTINS_SLICE_INDICES +#define MICROPY_PY_BUILTINS_SLICE_INDICES (0) +#endif + // Whether to support frozenset object #ifndef MICROPY_PY_BUILTINS_FROZENSET #define MICROPY_PY_BUILTINS_FROZENSET (0) @@ -1058,6 +1111,11 @@ typedef double mp_float_t; #define MICROPY_PY_MICROPYTHON_STACK_USE (MICROPY_PY_MICROPYTHON_MEM_INFO) #endif +// Whether to provide the "micropython.heap_locked" function +#ifndef MICROPY_PY_MICROPYTHON_HEAP_LOCKED +#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (0) +#endif + // Whether to provide "array" module. Note that large chunk of the // underlying code is shared with "bytearray" builtin type, so to // get real savings, it should be disabled too. @@ -1123,6 +1181,21 @@ typedef double mp_float_t; #define MICROPY_PY_MATH_ISCLOSE (0) #endif +// Whether to provide fix for atan2 Inf handling. +#ifndef MICROPY_PY_MATH_ATAN2_FIX_INFNAN +#define MICROPY_PY_MATH_ATAN2_FIX_INFNAN (0) +#endif + +// Whether to provide fix for fmod Inf handling. +#ifndef MICROPY_PY_MATH_FMOD_FIX_INFNAN +#define MICROPY_PY_MATH_FMOD_FIX_INFNAN (0) +#endif + +// Whether to provide fix for modf negative zero handling. +#ifndef MICROPY_PY_MATH_MODF_FIX_NEGZERO +#define MICROPY_PY_MATH_MODF_FIX_NEGZERO (0) +#endif + // Whether to provide "cmath" module #ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) @@ -1281,6 +1354,10 @@ typedef double mp_float_t; // Extended modules +#ifndef MICROPY_PY_UASYNCIO +#define MICROPY_PY_UASYNCIO (0) +#endif + #ifndef MICROPY_PY_UCTYPES #define MICROPY_PY_UCTYPES (0) #endif @@ -1413,6 +1490,17 @@ typedef double mp_float_t; #define MICROPY_PORT_ROOT_POINTERS #endif +/*****************************************************************************/ +/* Hooks for a port to wrap functions with attributes */ + +#ifndef MICROPY_WRAP_MP_KEYBOARD_INTERRUPT +#define MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(f) f +#endif + +#ifndef MICROPY_WRAP_MP_SCHED_SCHEDULE +#define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) f +#endif + /*****************************************************************************/ /* Miscellaneous settings */ @@ -1443,7 +1531,9 @@ typedef double mp_float_t; #define BYTES_PER_WORD (sizeof(mp_uint_t)) #endif +#ifndef BITS_PER_BYTE #define BITS_PER_BYTE (8) +#endif #define BITS_PER_WORD (BITS_PER_BYTE * BYTES_PER_WORD) // mp_int_t value with most significant bit set #define WORD_MSBIT_HIGH (((mp_uint_t)1) << (BYTES_PER_WORD * 8 - 1)) diff --git a/py/mphal.h b/py/mphal.h index e2b0abf926..1916cecbf3 100644 --- a/py/mphal.h +++ b/py/mphal.h @@ -26,6 +26,7 @@ #ifndef MICROPY_INCLUDED_PY_MPHAL_H #define MICROPY_INCLUDED_PY_MPHAL_H +#include #include "py/mpconfig.h" #ifdef MICROPY_MPHALPORT_H @@ -74,6 +75,11 @@ mp_uint_t mp_hal_ticks_us(void); mp_uint_t mp_hal_ticks_cpu(void); #endif +#ifndef mp_hal_time_ns +// Nanoseconds since 1970/1/1. +uint64_t mp_hal_time_ns(void); +#endif + // If port HAL didn't define its own pin API, use generic // "virtual pin" API from the core. #ifndef mp_hal_pin_obj_t diff --git a/py/mpprint.c b/py/mpprint.c index 1ba44279f7..c91ef912f0 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -542,7 +542,7 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { case 'g': case 'G': { #if ((MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT) || (MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE)) - mp_float_t f = va_arg(args, 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 diff --git a/py/mpstate.h b/py/mpstate.h index 6f13944c94..808e6aba0c 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -101,7 +101,7 @@ typedef struct _mp_state_mem_t { size_t gc_collected; #endif - #if MICROPY_PY_THREAD + #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL // This is a global mutex used to make the GC thread-safe. mp_thread_mutex_t gc_mutex; #endif @@ -175,6 +175,11 @@ typedef struct _mp_state_vm_t { mp_obj_dict_t *mp_module_builtins_override_dict; #endif + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE + // An mp_obj_list_t that tracks relocated native code to prevent the GC from reclaiming them. + mp_obj_t track_reloc_code_list; + #endif + // include any root pointers defined by a port MICROPY_PORT_ROOT_POINTERS @@ -199,7 +204,7 @@ typedef struct _mp_state_vm_t { size_t qstr_last_alloc; size_t qstr_last_used; - #if MICROPY_PY_THREAD + #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL // This is a global mutex used to make qstr interning thread-safe. mp_thread_mutex_t qstr_mutex; #endif diff --git a/py/mpthread.h b/py/mpthread.h index bd023e6279..fa9e054e3d 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -30,16 +30,16 @@ #if MICROPY_PY_THREAD +struct _mp_state_thread_t; + #ifdef MICROPY_MPTHREADPORT_H #include MICROPY_MPTHREADPORT_H #else #include #endif -struct _mp_state_thread_t; - struct _mp_state_thread_t *mp_thread_get_state(void); -void mp_thread_set_state(void *state); +void mp_thread_set_state(struct _mp_state_thread_t *state); void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size); void mp_thread_start(void); void mp_thread_finish(void); diff --git a/py/mpz.c b/py/mpz.c index 248d0ec359..0ec4dc706e 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -783,22 +783,7 @@ void mpz_set_from_ll(mpz_t *z, long long val, bool is_signed) { #if MICROPY_PY_BUILTINS_FLOAT void mpz_set_from_float(mpz_t *z, mp_float_t src) { - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE - typedef uint64_t mp_float_int_t; - #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - typedef uint32_t mp_float_int_t; - #endif - union { - mp_float_t f; - #if MP_ENDIANNESS_LITTLE - struct { mp_float_int_t frc : MP_FLOAT_FRAC_BITS, exp : MP_FLOAT_EXP_BITS, sgn : 1; - } p; - #else - struct { mp_float_int_t sgn : 1, exp : MP_FLOAT_EXP_BITS, frc : MP_FLOAT_FRAC_BITS; - } p; - #endif - } u = {src}; - + mp_float_union_t u = {src}; z->neg = u.p.sgn; if (u.p.exp == 0) { // value == 0 || value < 1 @@ -820,7 +805,7 @@ void mpz_set_from_float(mpz_t *z, mp_float_t src) { 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_int_t frc = u.p.frc | ((mp_float_int_t)1 << MP_FLOAT_FRAC_BITS); + 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; diff --git a/py/mpz.h b/py/mpz.h index 6b941cb7b9..b273cdcc62 100644 --- a/py/mpz.h +++ b/py/mpz.h @@ -93,7 +93,7 @@ typedef int8_t mpz_dbl_dig_signed_t; typedef struct _mpz_t { size_t neg : 1; size_t fixed_dig : 1; - size_t alloc : 8 * sizeof(size_t) - 2; + size_t alloc : (8 * sizeof(size_t) - 2); size_t len; mpz_dig_t *dig; } mpz_t; diff --git a/py/nativeglue.c b/py/nativeglue.c index b2afdebaa5..0dfaa27d66 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -118,13 +118,13 @@ mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type) { 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, "set unsupported"); + 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, "set unsupported"); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("set unsupported")); } #endif @@ -133,7 +133,7 @@ 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, "slice unsupported"); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("slice unsupported")); } #endif @@ -227,53 +227,35 @@ STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *re return false; } -#if MICROPY_PY_BUILTINS_FLOAT - -STATIC mp_obj_t mp_obj_new_float_from_f(float f) { - return mp_obj_new_float((mp_float_t)f); -} - -STATIC mp_obj_t mp_obj_new_float_from_d(double d) { - return mp_obj_new_float((mp_float_t)d); -} - -STATIC float mp_obj_get_float_to_f(mp_obj_t o) { - return (float)mp_obj_get_float(o); -} - -STATIC double mp_obj_get_float_to_d(mp_obj_t o) { - return (double)mp_obj_get_float(o); -} - -#else +#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, translate("float unsupported")); + 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, translate("float unsupported")); + 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, translate("float unsupported")); + 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, translate("float unsupported")); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("float unsupported")); } #endif -// these must correspond to the respective enum in runtime0.h +// these must correspond to the respective enum in nativeglue.h const mp_fun_table_t mp_fun_table = { - &mp_const_none_obj, - &mp_const_false_obj, - &mp_const_true_obj, + mp_const_none, + mp_const_false, + mp_const_true, mp_native_from_obj, mp_native_to_obj, mp_native_swap_globals, diff --git a/py/nativeglue.h b/py/nativeglue.h index 5cc4ead933..9d9a97b9e7 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -135,7 +135,7 @@ typedef struct _mp_fun_table_t { mp_int_t (*small_int_floor_divide)(mp_int_t num, mp_int_t denom); mp_int_t (*small_int_modulo)(mp_int_t dividend, mp_int_t divisor); bool (*yield_from)(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value); - void *setjmp; + void *setjmp_; // Additional entries for dynamic runtime, starts at index 50 void *(*memset_)(void *s, int c, size_t n); void *(*memmove_)(void *dest, const void *src, size_t n); @@ -145,8 +145,8 @@ typedef struct _mp_fun_table_t { #if defined(__GNUC__) NORETURN // Only certain compilers support no-return attributes in function pointer declarations #endif - void (*raise_msg)(const mp_obj_type_t *exc_type, const char *msg); - mp_obj_type_t *(*obj_get_type)(mp_const_obj_t o_in); + void (*raise_msg)(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); + const mp_obj_type_t *(*obj_get_type)(mp_const_obj_t o_in); mp_obj_t (*obj_new_str)(const char *data, size_t len); mp_obj_t (*obj_new_bytes)(const byte *data, size_t len); mp_obj_t (*obj_new_bytearray_by_ref)(size_t n, void *items); diff --git a/py/nlr.h b/py/nlr.h index 065d6869b0..7b8e6a4222 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -42,6 +42,8 @@ #define MICROPY_NLR_NUM_REGS_XTENSA (10) #define MICROPY_NLR_NUM_REGS_XTENSAWIN (17) +// *FORMAT-OFF* + // If MICROPY_NLR_SETJMP is not enabled then auto-detect the machine arch #if !defined(MICROPY_NLR_SETJMP) || !MICROPY_NLR_SETJMP // A lot of nlr-related things need different treatment on Windows @@ -68,9 +70,9 @@ #if defined(__SOFTFP__) #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB) #else -// With hardware FP registers s16-s31 are callee save so in principle -// should be saved and restored by the NLR code. gcc only uses s16-s21 -// so only save/restore those as an optimisation. + // With hardware FP registers s16-s31 are callee save so in principle + // should be saved and restored by the NLR code. gcc only uses s16-s21 + // so only save/restore those as an optimisation. #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB_FP) #endif #elif defined(__xtensa__) @@ -80,7 +82,7 @@ #elif defined(__powerpc__) #define MICROPY_NLR_SETJMP (0) #define MICROPY_NLR_POWERPC (1) -// this could be less but using 128 for safety + // this could be less but using 128 for safety #define MICROPY_NLR_NUM_REGS (128) #else #define MICROPY_NLR_SETJMP (1) @@ -93,6 +95,8 @@ #define MICROPY_NLR_SETJMP (0) #endif +// *FORMAT-ON* + #if MICROPY_NLR_SETJMP #include #endif diff --git a/py/nlrthumb.c b/py/nlrthumb.c index efa6bf8c50..c52914ac40 100644 --- a/py/nlrthumb.c +++ b/py/nlrthumb.c @@ -77,14 +77,12 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { ".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 - : // output operands - : "r" (nlr) // input operands - // Do not use r1, r2, r3 as temporary saving registers. - // gcc 7.2.1 started doing this, and r3 got clobbered in nlr_push_tail. - // See https://github.com/adafruit/circuitpython/issues/500 for details. - : "r1", "r2", "r3" // clobbers + #endif ); #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) diff --git a/py/nlrx86.c b/py/nlrx86.c index 598b199c29..0cc8b9e6da 100644 --- a/py/nlrx86.c +++ b/py/nlrx86.c @@ -56,9 +56,7 @@ __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); __attribute__((naked)) #endif unsigned int nlr_push(nlr_buf_t *nlr) { - #if !USE_NAKED (void)nlr; - #endif __asm volatile ( #if UNDO_PRELUDE diff --git a/py/obj.c b/py/obj.c index 9ced2f99c3..5652133ae6 100644 --- a/py/obj.c +++ b/py/obj.c @@ -43,19 +43,62 @@ #include "supervisor/shared/stack.h" #include "supervisor/shared/translate.h" -mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in) { +const mp_obj_type_t *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_obj_type_t *)&mp_type_int; - } else if (mp_obj_is_qstr(o_in)) { - return (mp_obj_type_t *)&mp_type_str; + 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_obj_type_t *)&mp_type_float; + 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 (mp_obj_type_t *)o->type; + return o->type; } + + #endif } const char *mp_obj_get_type_str(mp_const_obj_t o_in) { @@ -81,7 +124,7 @@ void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t return; } #endif - mp_obj_type_t *type = mp_obj_get_type(o_in); + 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 { @@ -151,7 +194,7 @@ bool PLACE_IN_ITCM(mp_obj_is_true)(mp_obj_t arg) { return 1; } } else { - mp_obj_type_t *type = mp_obj_get_type(arg); + 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) { @@ -171,14 +214,14 @@ bool PLACE_IN_ITCM(mp_obj_is_true)(mp_obj_t arg) { } bool mp_obj_is_callable(mp_obj_t o_in) { - mp_call_fun_t call = mp_obj_get_type(o_in)->call; + 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 '==' operator (and so the inverse of '!='). +// This function implements the '==' and '!=' operators. // // From the Python language reference: // (https://docs.python.org/3/reference/expressions.html#not-in) @@ -191,69 +234,89 @@ bool mp_obj_is_callable(mp_obj_t o_in) { // 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." -bool mp_obj_equal(mp_obj_t o1, mp_obj_t o2) { - // Float (and complex) NaN is never equal to anything, not even itself, - // so we must have a special check here to cover those cases. - if (o1 == o2 - #if MICROPY_PY_BUILTINS_FLOAT - && !mp_obj_is_float(o1) - #endif - #if MICROPY_PY_BUILTINS_COMPLEX - && !mp_obj_is_type(o1, &mp_type_complex) - #endif - ) { - return true; - } - if (o1 == mp_const_none || o2 == mp_const_none) { - return false; - } +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; - // 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 false; - } else { - mp_obj_t temp = o2; - o2 = o1; - o1 = temp; - // o2 is now the SMALL_INT, o1 is not - // fall through to generic op - } + // 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); - } else { - // a string is never equal to anything else - goto str_cmp_err; - } - } else if (mp_obj_is_str(o2)) { - // o1 is not a string (else caught above), so the objects are not equal - str_cmp_err: + return mp_obj_str_equal(o1, o2) ? local_true : local_false; #if MICROPY_PY_STR_BYTES_CMP_WARN - if (mp_obj_is_type(o1, &mp_type_bytes) || mp_obj_is_type(o2, &mp_type_bytes)) { + } 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 - return false; + } 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) - mp_obj_type_t *type = mp_obj_get_type(o1); - if (type->binary_op != NULL) { - mp_obj_t r = type->binary_op(MP_BINARY_OP_EQUAL, o1, o2); - if (r != MP_OBJ_NULL) { - return r == mp_const_true ? true : false; + 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, and objects are not the same object, so - // they are defined as not equal - return false; + // 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) { @@ -309,7 +372,7 @@ bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value) { } else if (arg == mp_const_true) { val = 1; } else if (mp_obj_is_small_int(arg)) { - val = MP_OBJ_SMALL_INT_VALUE(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); @@ -329,10 +392,10 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { if (!mp_obj_get_float_maybe(arg, &val)) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError_varg(translate("can't convert to %q"), MP_QSTR_float); + mp_raise_TypeError_varg(MP_ERROR_TEXT("can't convert to %q"), MP_QSTR_float); #else mp_raise_TypeError_varg( - translate("can't convert %q to %q"), mp_obj_get_type_qstr(arg), MP_QSTR_float); + MP_ERROR_TEXT("can't convert %q to %q"), mp_obj_get_type_qstr(arg), MP_QSTR_float); #endif } @@ -340,7 +403,7 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { } #if MICROPY_PY_BUILTINS_COMPLEX -void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { +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; @@ -348,7 +411,7 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { *real = 1; *imag = 0; } else if (mp_obj_is_small_int(arg)) { - *real = MP_OBJ_SMALL_INT_VALUE(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)) { @@ -361,11 +424,18 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { } 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_varg(translate("can't convert to %q"), MP_QSTR_complex); + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to complex")); #else mp_raise_TypeError_varg( - translate("can't convert %q to %q"), mp_obj_get_type_qstr(arg), MP_QSTR_complex); + MP_ERROR_TEXT("can't convert %s to complex"), mp_obj_get_type_str(arg)); #endif } } @@ -380,10 +450,10 @@ void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { mp_obj_list_get(o, len, items); } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("expected tuple/list")); + mp_raise_TypeError(MP_ERROR_TEXT("expected tuple/list")); #else mp_raise_TypeError_varg( - translate("object '%q' is not a tuple or list"), mp_obj_get_type_qstr(o)); + MP_ERROR_TEXT("object '%s' isn't a tuple or list"), mp_obj_get_type_str(o)); #endif } } @@ -394,10 +464,10 @@ void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items) { mp_obj_get_array(o, &seq_len, items); if (seq_len != len) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_ValueError(translate("tuple/list has wrong length")); + mp_raise_ValueError(MP_ERROR_TEXT("tuple/list has wrong length")); #else - mp_raise_ValueError_varg(translate("requested length %d but object has length %d"), - (int)len, (int)seq_len); + mp_raise_ValueError_varg( + MP_ERROR_TEXT("requested length %d but object has length %d"), (int)len, (int)seq_len); #endif } } @@ -409,11 +479,11 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool 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(translate("indices must be integers")); + mp_raise_TypeError(MP_ERROR_TEXT("indices must be integers")); #else mp_raise_TypeError_varg( - translate("%q indices must be integers, not %q"), - type->name, mp_obj_get_type_qstr(index)); + MP_ERROR_TEXT("%q indices must be integers, not %s"), + type->name, mp_obj_get_type_str(index)); #endif } @@ -429,10 +499,10 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool } else { if (i < 0 || (mp_uint_t)i >= len) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_IndexError(translate("index out of range")); + mp_raise_IndexError(MP_ERROR_TEXT("index out of range")); #else mp_raise_msg_varg(&mp_type_IndexError, - translate("%q index out of range"), type->name); + MP_ERROR_TEXT("%q index out of range"), type->name); #endif } } @@ -464,10 +534,10 @@ 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(translate("object has no len")); + mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); #else mp_raise_TypeError_varg( - translate("object of type '%q' has no len()"), mp_obj_get_type_qstr(o_in)); + MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in)); #endif } else { return len; @@ -485,7 +555,7 @@ mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) { GET_STR_LEN(o_in, l); return MP_OBJ_NEW_SMALL_INT(l); } else { - mp_obj_type_t *type = mp_obj_get_type(o_in); + 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 { @@ -495,8 +565,7 @@ mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) { } mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { - mp_obj_type_t *type = mp_obj_get_type(base); - + const mp_obj_type_t *type = mp_obj_get_type(base); if (type->subscr != NULL) { mp_obj_t ret = type->subscr(base, index, value); // May have called port specific C code. Make sure it didn't mess up the heap. @@ -507,24 +576,24 @@ mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { } if (value == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("object does not support item deletion")); + mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); #else mp_raise_TypeError_varg( - translate("'%q' object does not support item deletion"), mp_obj_get_type_qstr(base)); + 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(translate("object is not subscriptable")); + mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); #else mp_raise_TypeError_varg( - translate("'%q' object is not subscriptable"), mp_obj_get_type_qstr(base)); + 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(translate("object does not support item assignment")); + mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item assignment")); #else mp_raise_TypeError_varg( - translate("'%q' object does not support item assignment"), mp_obj_get_type_qstr(base)); + MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base)); #endif } } @@ -550,7 +619,7 @@ typedef struct { STATIC mp_obj_t generic_it_iternext(mp_obj_t self_in) { mp_obj_generic_it_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_type_t *type = mp_obj_get_type(self->obj); + const mp_obj_type_t *type = mp_obj_get_type(self->obj); mp_obj_t current_length = type->unary_op(MP_UNARY_OP_LEN, self->obj); if (self->cur < MP_OBJ_SMALL_INT_VALUE(current_length)) { mp_obj_t o_out = type->subscr(self->obj, MP_OBJ_NEW_SMALL_INT(self->cur), MP_OBJ_SENTINEL); @@ -572,7 +641,7 @@ mp_obj_t mp_obj_new_generic_iterator(mp_obj_t obj, mp_obj_iter_buf_t *iter_buf) } bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { - mp_obj_type_t *type = mp_obj_get_type(obj); + const mp_obj_type_t *type = mp_obj_get_type(obj); if (type->buffer_p.get_buffer == NULL) { return false; } @@ -585,7 +654,7 @@ bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { 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(translate("object with buffer protocol required")); + mp_raise_TypeError(MP_ERROR_TEXT("object with buffer protocol required")); } } diff --git a/py/obj.h b/py/obj.h index 091bac13bd..0813737f74 100644 --- a/py/obj.h +++ b/py/obj.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_PY_OBJ_H #define MICROPY_INCLUDED_PY_OBJ_H -#include +#include #include "py/mpconfig.h" #include "py/misc.h" @@ -92,10 +92,16 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) << 1) | 1)) static inline bool mp_obj_is_qstr(mp_const_obj_t o) { - return (((mp_int_t)(o)) & 3) == 2; + return (((mp_int_t)(o)) & 7) == 2; } -#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 2) -#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 2)) +#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 3) +#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2)) + +static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { + return (((mp_int_t)(o)) & 7) == 6; +} +#define MP_OBJ_IMMEDIATE_OBJ_VALUE(o) (((mp_uint_t)(o)) >> 3) +#define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 3) | 6)) #if MICROPY_PY_BUILTINS_FLOAT #define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) @@ -121,10 +127,16 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) << 2) | 1)) static inline bool mp_obj_is_qstr(mp_const_obj_t o) { - return (((mp_int_t)(o)) & 3) == 3; + return (((mp_int_t)(o)) & 7) == 3; } -#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 2) -#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 3)) +#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 3) +#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 3)) + +static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { + return (((mp_int_t)(o)) & 7) == 7; +} +#define MP_OBJ_IMMEDIATE_OBJ_VALUE(o) (((mp_uint_t)(o)) >> 3) +#define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 3) | 7)) #if MICROPY_PY_BUILTINS_FLOAT #define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) @@ -173,10 +185,16 @@ static inline mp_obj_t mp_obj_new_float(mp_float_t f) { #endif static inline bool mp_obj_is_qstr(mp_const_obj_t o) { - return (((mp_uint_t)(o)) & 0xff800007) == 0x00000006; + return (((mp_uint_t)(o)) & 0xff80000f) == 0x00000006; } -#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 3) -#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 0x00000006)) +#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 4) +#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 4) | 0x00000006)) + +static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { + return (((mp_uint_t)(o)) & 0xff80000f) == 0x0000000e; +} +#define MP_OBJ_IMMEDIATE_OBJ_VALUE(o) (((mp_uint_t)(o)) >> 4) +#define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 4) | 0xe)) static inline bool mp_obj_is_obj(mp_const_obj_t o) { return (((mp_int_t)(o)) & 3) == 0; @@ -194,7 +212,13 @@ static inline bool mp_obj_is_qstr(mp_const_obj_t o) { return (((uint64_t)(o)) & 0xffff000000000000) == 0x0002000000000000; } #define MP_OBJ_QSTR_VALUE(o) ((((uint32_t)(o)) >> 1) & 0xffffffff) -#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 1) | 0x0002000000000001)) +#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)(((uint64_t)(((uint32_t)(qst)) << 1)) | 0x0002000000000001)) + +static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { + return (((uint64_t)(o)) & 0xffff000000000000) == 0x0003000000000000; +} +#define MP_OBJ_IMMEDIATE_OBJ_VALUE(o) ((((uint32_t)(o)) >> 46) & 3) +#define MP_OBJ_NEW_IMMEDIATE_OBJ(val) (((uint64_t)(val) << 46) | 0x0003000000000000) #if MICROPY_PY_BUILTINS_FLOAT @@ -262,6 +286,24 @@ typedef union _mp_rom_obj_t { uint64_t u64; // Macros to create objects that are stored in ROM. +#ifndef MP_ROM_NONE +#if MICROPY_OBJ_IMMEDIATE_OBJS +#define MP_ROM_NONE mp_const_none +#else +#define MP_ROM_NONE MP_ROM_PTR(&mp_const_none_obj) +#endif +#endif + +#ifndef MP_ROM_FALSE +#if MICROPY_OBJ_IMMEDIATE_OBJS +#define MP_ROM_FALSE mp_const_false +#define MP_ROM_TRUE mp_const_true +#else +#define MP_ROM_FALSE MP_ROM_PTR(&mp_const_false_obj) +#define MP_ROM_TRUE MP_ROM_PTR(&mp_const_true_obj) +#endif +#endif + #ifndef MP_ROM_INT typedef mp_const_obj_t mp_rom_obj_t; #define MP_ROM_INT(i) MP_OBJ_NEW_SMALL_INT(i) @@ -396,11 +438,6 @@ typedef struct _mp_rom_map_elem_t { mp_rom_obj_t value; } mp_rom_map_elem_t; -// TODO maybe have a truncated mp_map_t for fixed tables, since alloc=used -// put alloc last in the structure, so the truncated version does not need it -// this would save 1 ROM word for all ROM objects that have a locals_dict -// would also need a trucated dict structure - typedef struct _mp_map_t { size_t all_keys_are_qstrs : 1; size_t is_fixed : 1; // a fixed array that can't be modified; must also be ordered @@ -423,6 +460,7 @@ typedef enum _mp_map_lookup_kind_t { extern const mp_map_t mp_const_empty_map; static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) { + assert(pos < map->alloc); return (map)->table[pos].key != MP_OBJ_NULL && (map)->table[pos].key != MP_OBJ_SENTINEL; } @@ -463,6 +501,23 @@ typedef mp_obj_t (*mp_fun_var_t)(size_t n, const mp_obj_t *); // this arg to mp_map_lookup(). typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); +// Flags for type behaviour (mp_obj_type_t.flags) +// If MP_TYPE_FLAG_EQ_NOT_REFLEXIVE is clear then __eq__ is reflexive (A==A returns True). +// If MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE is clear then the type can't be equal to an +// instance of any different class that also clears this flag. If this flag is set +// then the type may check for equality against a different type. +// If MP_TYPE_FLAG_EQ_HAS_NEQ_TEST is clear then the type only implements the __eq__ +// operator and not the __ne__ operator. If it's set then __ne__ may be implemented. +// If MP_TYPE_FLAG_BINDS_SELF is set then the type as a method binds self as the first arg. +// If MP_TYPE_FLAG_BUILTIN_FUN is set then the type is a built-in function type. +#define MP_TYPE_FLAG_IS_SUBCLASSED (0x0001) +#define MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) +#define MP_TYPE_FLAG_EQ_NOT_REFLEXIVE (0x0004) +#define MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE (0x0008) +#define MP_TYPE_FLAG_EQ_HAS_NEQ_TEST (0x0010) +#define MP_TYPE_FLAG_BINDS_SELF (0x0020) +#define MP_TYPE_FLAG_BUILTIN_FUN (0x0040) + typedef enum { PRINT_STR = 0, PRINT_REPR = 1, @@ -492,18 +547,9 @@ typedef mp_obj_t (*mp_getiter_fun_t)(mp_obj_t self_in, mp_obj_iter_buf_t *iter_b // Buffer protocol typedef struct _mp_buffer_info_t { - // if we'd bother to support various versions of structure - // (with different number of fields), we can distinguish - // them with ver = sizeof(struct). Cons: overkill for *micro*? - // int ver; // ? - void *buf; // can be NULL if len == 0 size_t len; // in bytes int typecode; // as per binary.h - - // Rationale: to load arbitrary-sized sprites directly to LCD - // Cons: a bit adhoc usecase - // int stride; } mp_buffer_info_t; #define MP_BUFFER_READ (1) #define MP_BUFFER_WRITE (2) @@ -670,17 +716,27 @@ extern const mp_obj_type_t mp_type_DeepSleepRequest; #endif -// Constant objects, globally accessible -// The macros are for convenience only +// Constant objects, globally accessible: None, False, True +// These should always be accessed via the below macros. +#if MICROPY_OBJ_IMMEDIATE_OBJS +// None is even while False/True are odd so their types can be distinguished with 1 bit. +#define mp_const_none MP_OBJ_NEW_IMMEDIATE_OBJ(0) +#define mp_const_false MP_OBJ_NEW_IMMEDIATE_OBJ(1) +#define mp_const_true MP_OBJ_NEW_IMMEDIATE_OBJ(3) +#else #define mp_const_none (MP_OBJ_FROM_PTR(&mp_const_none_obj)) #define mp_const_false (MP_OBJ_FROM_PTR(&mp_const_false_obj)) #define mp_const_true (MP_OBJ_FROM_PTR(&mp_const_true_obj)) -#define mp_const_empty_bytes (MP_OBJ_FROM_PTR(&mp_const_empty_bytes_obj)) -#define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj)) -#define mp_const_notimplemented (MP_OBJ_FROM_PTR(&mp_const_notimplemented_obj)) extern const struct _mp_obj_none_t mp_const_none_obj; extern const struct _mp_obj_bool_t mp_const_false_obj; extern const struct _mp_obj_bool_t mp_const_true_obj; +#endif + +// Constant objects, globally accessible: b'', (), Ellipsis, NotImplemented, GeneratorExit() +// The below macros are for convenience only. +#define mp_const_empty_bytes (MP_OBJ_FROM_PTR(&mp_const_empty_bytes_obj)) +#define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj)) +#define mp_const_notimplemented (MP_OBJ_FROM_PTR(&mp_const_notimplemented_obj)) extern const struct _mp_obj_str_t mp_const_empty_bytes_obj; extern const struct _mp_obj_tuple_t mp_const_empty_tuple_obj; extern const struct _mp_obj_singleton_t mp_const_ellipsis_obj; @@ -694,9 +750,16 @@ extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; // Note: these are kept as macros because inline functions sometimes use much // more code space than the equivalent macros, depending on the compiler. #define mp_obj_is_type(o, t) (mp_obj_is_obj(o) && (((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type == (t))) // this does not work for checking int, str or fun; use below macros for that +#if MICROPY_OBJ_IMMEDIATE_OBJS +// bool's are immediates, not real objects, so test for the 2 possible values. +#define mp_obj_is_bool(o) ((o) == mp_const_false || (o) == mp_const_true) +#else +#define mp_obj_is_bool(o) mp_obj_is_type(o, &mp_type_bool) +#endif #define mp_obj_is_int(o) (mp_obj_is_small_int(o) || mp_obj_is_type(o, &mp_type_int)) #define mp_obj_is_str(o) (mp_obj_is_qstr(o) || mp_obj_is_type(o, &mp_type_str)) #define mp_obj_is_str_or_bytes(o) (mp_obj_is_qstr(o) || (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->binary_op == mp_obj_str_binary_op)) +#define mp_obj_is_dict_or_ordereddict(o) (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->make_new == mp_obj_dict_make_new) #define mp_obj_is_fun(o) (mp_obj_is_obj(o) && (((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->name == MP_QSTR_function)) mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict); @@ -728,7 +791,9 @@ mp_obj_t mp_obj_new_exception_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg); mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, size_t n_args, const mp_obj_t *args); mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg); mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!) +#ifdef va_start mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, va_list ap); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!) +#endif // Only use this string version from native MPY files with static error strings. mp_obj_t mp_obj_new_exception_msg_str(const mp_obj_type_t *exc_type, const char *msg); mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args, mp_obj_t def_kw_args, const byte *code, const mp_uint_t *const_table); @@ -748,11 +813,11 @@ mp_obj_t mp_obj_new_getitem_iter(mp_obj_t *args, mp_obj_iter_buf_t *iter_buf); mp_obj_t mp_obj_new_module(qstr module_name); mp_obj_t mp_obj_new_memoryview(byte typecode, size_t nitems, void *items); -mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in); +const mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in); const char *mp_obj_get_type_str(mp_const_obj_t o_in); #define mp_obj_get_type_qstr(o_in) (mp_obj_get_type((o_in))->name) bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo); // arguments should be type objects -mp_obj_t mp_instance_cast_to_native_base(mp_obj_t self_in, mp_const_obj_t native_type); +mp_obj_t mp_obj_cast_to_native_base(mp_obj_t self_in, mp_const_obj_t native_type); void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind); void mp_obj_print(mp_obj_t o, mp_print_kind_t kind); @@ -760,11 +825,12 @@ void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc); bool mp_obj_is_true(mp_obj_t arg); bool mp_obj_is_callable(mp_obj_t o_in); +mp_obj_t mp_obj_equal_not_equal(mp_binary_op_t op, mp_obj_t o1, mp_obj_t o2); bool mp_obj_equal(mp_obj_t o1, mp_obj_t o2); static inline bool mp_obj_is_integer(mp_const_obj_t o) { - return mp_obj_is_int(o) || mp_obj_is_type(o, &mp_type_bool); -} // returns true if o is bool, small int or long int + return mp_obj_is_int(o) || mp_obj_is_bool(o); +} // returns true if o is bool, small int or long int mp_int_t mp_obj_get_int(mp_const_obj_t arg); mp_int_t mp_obj_get_int_truncated(mp_const_obj_t arg); bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); @@ -772,6 +838,7 @@ bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); mp_float_t mp_obj_get_float(mp_obj_t self_in); bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value); void mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +bool mp_obj_get_complex_maybe(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); #endif void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items); // *items may point inside a GC block @@ -791,6 +858,8 @@ void mp_obj_cell_set(mp_obj_t self_in, mp_obj_t obj); mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in); // Will raise exception if value doesn't fit into mp_int_t mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in); +// Will raise exception if value is negative or doesn't fit into mp_uint_t +mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in); // exception #define mp_obj_is_native_exception_instance(o) (mp_obj_get_type(o)->make_new == mp_obj_exception_make_new) @@ -817,6 +886,39 @@ void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, size_t s #if MICROPY_PY_BUILTINS_FLOAT // float +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +static inline float mp_obj_get_float_to_f(mp_obj_t o) { + return mp_obj_get_float(o); +} + +static inline double mp_obj_get_float_to_d(mp_obj_t o) { + return (double)mp_obj_get_float(o); +} + +static inline mp_obj_t mp_obj_new_float_from_f(float o) { + return mp_obj_new_float(o); +} + +static inline mp_obj_t mp_obj_new_float_from_d(double o) { + return mp_obj_new_float((mp_float_t)o); +} +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +static inline float mp_obj_get_float_to_f(mp_obj_t o) { + return (float)mp_obj_get_float(o); +} + +static inline double mp_obj_get_float_to_d(mp_obj_t o) { + return mp_obj_get_float(o); +} + +static inline mp_obj_t mp_obj_new_float_from_f(float o) { + return mp_obj_new_float((mp_float_t)o); +} + +static inline mp_obj_t mp_obj_new_float_from_d(double o) { + return mp_obj_new_float(o); +} +#endif #if MICROPY_FLOAT_HIGH_QUALITY_HASH mp_int_t mp_float_hash(mp_float_t val); #else @@ -852,11 +954,13 @@ typedef struct _mp_obj_dict_t { mp_obj_base_t base; mp_map_t map; } mp_obj_dict_t; +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); void mp_obj_dict_init(mp_obj_dict_t *dict, size_t n_args); size_t mp_obj_dict_len(mp_obj_t self_in); mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index); mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value); mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key); +mp_obj_t mp_obj_dict_copy(mp_obj_t self_in); static inline mp_map_t *mp_obj_dict_get_map(mp_obj_t dict) { return &((mp_obj_dict_t *)MP_OBJ_TO_PTR(dict))->map; } @@ -864,8 +968,21 @@ static inline mp_map_t *mp_obj_dict_get_map(mp_obj_t dict) { // set void mp_obj_set_store(mp_obj_t self_in, mp_obj_t item); +// slice indexes resolved to particular sequence +typedef struct { + mp_int_t start; + mp_int_t stop; + mp_int_t step; +} mp_bound_slice_t; + // slice -void mp_obj_slice_get(mp_obj_t self_in, mp_obj_t *start, mp_obj_t *stop, mp_obj_t *step); +typedef struct _mp_obj_slice_t { + mp_obj_base_t base; + mp_obj_t start; + mp_obj_t stop; + mp_obj_t step; +} mp_obj_slice_t; +void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result); // functions @@ -925,14 +1042,6 @@ const mp_obj_t *mp_obj_property_get(mp_obj_t self_in); // sequence helpers -// slice indexes resolved to particular sequence -typedef struct { - mp_uint_t start; - mp_uint_t stop; - mp_int_t step; -} mp_bound_slice_t; -void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result); - // Compute the new length of a sequence and ensure an exception is thrown on overflow. size_t mp_seq_multiply_len(size_t item_sz, size_t len); void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times, void *dest); @@ -946,17 +1055,17 @@ bool mp_seq_cmp_objs(mp_uint_t op, const mp_obj_t *items1, size_t len1, const mp mp_obj_t mp_seq_index_obj(const mp_obj_t *items, size_t len, size_t n_args, const mp_obj_t *args); mp_obj_t mp_seq_count_obj(const mp_obj_t *items, size_t len, mp_obj_t value); mp_obj_t mp_seq_extract_slice(size_t len, const mp_obj_t *seq, mp_bound_slice_t *indexes); + // Helper to clear stale pointers from allocated, but unused memory, to preclude GC problems #define mp_seq_clear(start, len, alloc_len, item_sz) memset((byte *)(start) + (len) * (item_sz), 0, ((alloc_len) - (len)) * (item_sz)) + +// Note: dest and slice regions may overlap #define mp_seq_replace_slice_no_grow(dest, dest_len, beg, end, slice, slice_len, item_sz) \ - /*printf("memcpy(%p, %p, %d)\n", dest + beg, slice, slice_len * (item_sz));*/ \ - memcpy(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); \ - /*printf("memmove(%p, %p, %d)\n", dest + (beg + slice_len), dest + end, (dest_len - end) * (item_sz));*/ \ + memmove(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); \ memmove(((char *)dest) + (beg + slice_len) * (item_sz), ((char *)dest) + (end) * (item_sz), (dest_len - end) * (item_sz)); // Note: dest and slice regions may overlap #define mp_seq_replace_slice_grow_inplace(dest, dest_len, beg, end, slice, slice_len, len_adj, item_sz) \ - /*printf("memmove(%p, %p, %d)\n", dest + beg + len_adj, dest + beg, (dest_len - beg) * (item_sz));*/ \ memmove(((char *)dest) + (beg + slice_len) * (item_sz), ((char *)dest) + (end) * (item_sz), ((dest_len) + (len_adj) - ((beg) + (slice_len))) * (item_sz)); \ memmove(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); diff --git a/py/objarray.c b/py/objarray.c index 7bc615137c..12e66edd3c 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -519,7 +519,7 @@ STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value 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(translate("only slices with step=1 (aka None) are supported")); + 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 @@ -532,7 +532,7 @@ STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value 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(translate("lhs and rhs should be compatible")); + mp_raise_ValueError(MP_ERROR_TEXT("lhs and rhs should be compatible")); } src_len = src_slice->len; src_items = src_slice->items; @@ -550,7 +550,7 @@ STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value src_len = bufinfo.len; src_items = bufinfo.buf; } else { - mp_raise_NotImplementedError(translate("array/bytes required on right side")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("array/bytes required on right side")); } // TODO: check src/dst compat @@ -569,7 +569,7 @@ STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value } #endif if (len_adj > 0) { - if ((mp_uint_t)len_adj > o->free) { + 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; @@ -722,6 +722,7 @@ const mp_obj_type_t mp_type_array = { #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, @@ -746,6 +747,7 @@ STATIC MP_DEFINE_CONST_DICT(memoryview_locals_dict, memoryview_locals_dict_table 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, diff --git a/py/objbool.c b/py/objbool.c index 5e8f630ff1..ce9075d2fb 100644 --- a/py/objbool.c +++ b/py/objbool.c @@ -28,21 +28,31 @@ #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) { - mp_obj_bool_t *self = MP_OBJ_TO_PTR(self_in); + bool value = BOOL_VALUE(self_in); if (MICROPY_PY_UJSON && kind == PRINT_JSON) { - if (self->value) { + if (value) { mp_print_str(print, "true"); } else { mp_print_str(print, "false"); } } else { - if (self->value) { + if (value) { mp_print_str(print, "True"); } else { mp_print_str(print, "False"); @@ -65,17 +75,18 @@ 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; } - mp_obj_bool_t *self = MP_OBJ_TO_PTR(o_in); - return mp_unary_op(op, MP_OBJ_NEW_SMALL_INT(self->value)); + 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) { - mp_obj_bool_t *self = MP_OBJ_TO_PTR(lhs_in); - return mp_binary_op(op, MP_OBJ_NEW_SMALL_INT(self->value), 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, @@ -83,5 +94,7 @@ const mp_obj_type_t mp_type_bool = { .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 diff --git a/py/objclosure.c b/py/objclosure.c index e4e3b87bbb..3e23520791 100644 --- a/py/objclosure.c +++ b/py/objclosure.c @@ -80,6 +80,7 @@ STATIC void closure_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_ const mp_obj_type_t closure_type = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_closure, #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED .print = closure_print, diff --git a/py/objcomplex.c b/py/objcomplex.c index 5f2c4105a9..5c4800e844 100644 --- a/py/objcomplex.c +++ b/py/objcomplex.c @@ -157,6 +157,7 @@ STATIC void complex_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { 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, @@ -182,7 +183,10 @@ void mp_obj_complex_get(mp_obj_t self_in, mp_float_t *real, mp_float_t *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; - mp_obj_get_complex(rhs_in, &rhs_real, &rhs_imag); // can be any type, this function will convert to float (if possible) + 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: @@ -205,13 +209,13 @@ mp_obj_t mp_obj_complex_binary_op(mp_binary_op_t op, mp_float_t lhs_real, mp_flo } case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: - mp_raise_TypeError(translate("can't do truncated division of a complex number")); + mp_raise_TypeError(MP_ERROR_TEXT("can't do truncated division of 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, translate("complex division by zero")); + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("complex division by zero")); } lhs_real /= rhs_real; lhs_imag /= rhs_real; @@ -239,7 +243,7 @@ mp_obj_t mp_obj_complex_binary_op(mp_binary_op_t op, mp_float_t lhs_real, mp_flo if (rhs_imag == 0 && rhs_real >= 0) { lhs_real = (rhs_real == 0); } else { - mp_raise_msg(&mp_type_ZeroDivisionError, translate("0.0 to a complex power")); + 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); diff --git a/py/objdeque.c b/py/objdeque.c index d9d8c0415d..e6e05cb507 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -104,7 +104,7 @@ STATIC mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) { } if (self->flags & FLAG_CHECK_OVERFLOW && new_i_put == self->i_get) { - mp_raise_msg(&mp_type_IndexError, translate("full")); + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full")); } self->items[self->i_put] = arg; @@ -124,7 +124,7 @@ 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, translate("empty")); + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty")); } mp_obj_t ret = self->items[self->i_get]; diff --git a/py/objdict.c b/py/objdict.c index 4fbeccc885..cc5662d935 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -36,8 +36,6 @@ #include "supervisor/linker.h" #include "supervisor/shared/translate.h" -#define mp_obj_is_dict_type(o) (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->make_new == dict_make_new) - 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 @@ -47,13 +45,15 @@ 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; - for (size_t i = *cur; i < max; i++) { + 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; } @@ -91,7 +91,7 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ } } -STATIC mp_obj_t dict_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_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; @@ -148,7 +148,7 @@ STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_ } } return e1 == NULL && e2 == NULL ? mp_const_true : mp_const_false; - } else + } #endif if (mp_obj_is_type(rhs_in, &mp_type_dict)) { mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); @@ -218,7 +218,7 @@ STATIC void mp_ensure_not_fixed(const mp_obj_dict_t *dict) { } STATIC mp_obj_t dict_clear(mp_obj_t self_in) { - mp_check_self(mp_obj_is_dict_type(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); @@ -228,8 +228,8 @@ STATIC mp_obj_t dict_clear(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_clear_obj, dict_clear); -STATIC mp_obj_t dict_copy(mp_obj_t self_in) { - mp_check_self(mp_obj_is_dict_type(self_in)); +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); @@ -241,7 +241,7 @@ STATIC mp_obj_t dict_copy(mp_obj_t self_in) { 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, dict_copy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, mp_obj_dict_copy); #if MICROPY_PY_BUILTINS_DICT_FROMKEYS // this is a classmethod @@ -276,7 +276,7 @@ STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, MP_ROM_PTR(&dict_fromk #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_type(args[0])); + 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); @@ -321,14 +321,20 @@ STATIC mp_obj_t dict_setdefault(size_t n_args, const mp_obj_t *args) { 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_type(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); - size_t cur = 0; - mp_map_elem_t *next = dict_iter_next(self, &cur); - if (next == NULL) { - mp_raise_msg_varg(&mp_type_KeyError, translate("pop from empty %q"), MP_QSTR_dict); + if (self->map.used == 0) { + mp_raise_msg_varg(&mp_type_KeyError, MP_ERROR_TEXT("pop from empty %q"), MP_QSTR_dict); } + 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 @@ -339,8 +345,8 @@ STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_popitem_obj, dict_popitem); -STATIC mp_obj_t PLACE_IN_ITCM(dict_update)(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - mp_check_self(mp_obj_is_dict_type(args[0])); +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); @@ -349,7 +355,7 @@ STATIC mp_obj_t PLACE_IN_ITCM(dict_update)(size_t n_args, const mp_obj_t *args, if (n_args == 2) { // given a positional argument - if (mp_obj_is_dict_type(args[1])) { + 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; @@ -370,7 +376,7 @@ STATIC mp_obj_t PLACE_IN_ITCM(dict_update)(size_t n_args, const mp_obj_t *args, if (key == MP_OBJ_STOP_ITERATION || value == MP_OBJ_STOP_ITERATION || stop != MP_OBJ_STOP_ITERATION) { - mp_raise_ValueError(translate("dict update sequence has wrong length")); + 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; } @@ -507,7 +513,7 @@ STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { } STATIC mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); return mp_obj_new_dict_view(self_in, kind); } @@ -531,7 +537,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); 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_type(self_in)); + 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 = &dict_view_it_type; o->kind = MP_DICT_VIEW_KEYS; @@ -568,7 +574,7 @@ const mp_obj_type_t mp_type_dict = { { &mp_type_type }, .name = MP_QSTR_dict, .print = dict_print, - .make_new = dict_make_new, + .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, @@ -581,7 +587,7 @@ const mp_obj_type_t mp_type_ordereddict = { { &mp_type_type }, .name = MP_QSTR_OrderedDict, .print = dict_print, - .make_new = dict_make_new, + .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, @@ -607,8 +613,8 @@ size_t mp_obj_dict_len(mp_obj_t self_in) { return self->map.used; } -mp_obj_t PLACE_IN_ITCM(mp_obj_dict_store)(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) { - mp_check_self(mp_obj_is_dict_type(self_in)); +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; diff --git a/py/objexcept.c b/py/objexcept.c index e8d232c83b..8fe6da170c 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -60,6 +60,7 @@ #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 @@ -140,6 +141,7 @@ void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kin return; } } + mp_obj_tuple_print(print, MP_OBJ_FROM_PTR(o->args), kind); } @@ -258,6 +260,8 @@ const mp_obj_type_t mp_type_BaseException = { .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) @@ -305,6 +309,7 @@ MP_DEFINE_EXCEPTION(BrokenPipeError, ConnectionError) 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) @@ -344,13 +349,17 @@ MP_DEFINE_EXCEPTION(MpyError, ValueError) MP_DEFINE_EXCEPTION(ResourceWarning, Warning) */ +// *FORMAT-ON* + mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) { - return mp_obj_new_exception_args(exc_type, 0, NULL); + assert(exc_type->make_new == mp_obj_exception_make_new); + return mp_obj_exception_make_new(exc_type, 0, 0, NULL); } // "Optimized" version for common(?) case of having 1 exception arg mp_obj_t mp_obj_new_exception_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg) { - return mp_obj_new_exception_args(exc_type, 1, &arg); + assert(exc_type->make_new == mp_obj_exception_make_new); + return mp_obj_exception_make_new(exc_type, 1, &arg, 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) { @@ -432,7 +441,8 @@ mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, const com #endif if (o_str == NULL) { - // No memory for the string object so create the exception with no args + // 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); } @@ -441,7 +451,8 @@ mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, const com o_str->len = 0; o_str->data = NULL; } else { - // We have some memory to format the string + // 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}; char fmt_decompressed[decompress_length(fmt)]; @@ -454,7 +465,11 @@ mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, const com // 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, &arg, NULL); } diff --git a/py/objfloat.c b/py/objfloat.c index 67cb8514ee..670a052f1e 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -57,31 +57,17 @@ typedef struct _mp_obj_float_t { mp_float_t value; } mp_obj_float_t; -const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, M_E}; -const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, M_PI}; +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}; #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) { - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE - typedef uint64_t mp_float_uint_t; - #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - typedef uint32_t mp_float_uint_t; - #endif - union { - mp_float_t f; - #if MP_ENDIANNESS_LITTLE - struct { mp_float_uint_t frc : MP_FLOAT_FRAC_BITS, exp : MP_FLOAT_EXP_BITS, sgn : 1; - } p; - #else - struct { mp_float_uint_t sgn : 1, exp : MP_FLOAT_EXP_BITS, frc : MP_FLOAT_FRAC_BITS; - } p; - #endif - mp_float_uint_t i; - } u = {.f = 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) { @@ -188,15 +174,14 @@ STATIC mp_obj_t float_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs #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); - } else - #endif - { - return mp_obj_float_binary_op(op, lhs_val, 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, @@ -230,24 +215,24 @@ STATIC void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y) { mp_float_t div = (*x - mod) / *y; // Python specs require that mod has same sign as second operand - if (mod == 0.0) { - mod = MICROPY_FLOAT_C_FUN(copysign)(0.0, *y); + if (mod == MICROPY_FLOAT_ZERO) { + mod = MICROPY_FLOAT_C_FUN(copysign)(MICROPY_FLOAT_ZERO, *y); } else { - if ((mod < 0.0) != (*y < 0.0)) { + if ((mod < MICROPY_FLOAT_ZERO) != (*y < MICROPY_FLOAT_ZERO)) { mod += *y; - div -= 1.0; + div -= MICROPY_FLOAT_CONST(1.0); } } mp_float_t floordiv; - if (div == 0.0) { + if (div == MICROPY_FLOAT_ZERO) { // if division is zero, take the correct sign of zero - floordiv = MICROPY_FLOAT_C_FUN(copysign)(0.0, *x / *y); + 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 > 0.5) { - floordiv += 1.0; + if (div - floordiv > MICROPY_FLOAT_CONST(0.5)) { + floordiv += MICROPY_FLOAT_CONST(1.0); } } @@ -279,7 +264,7 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { zero_division_error: - mp_raise_msg(&mp_type_ZeroDivisionError, translate("division by zero")); + 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 @@ -295,15 +280,15 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t break; case MP_BINARY_OP_MODULO: case MP_BINARY_OP_INPLACE_MODULO: - if (rhs_val == 0) { + 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 == 0.0) { + if (lhs_val == MICROPY_FLOAT_ZERO) { lhs_val = MICROPY_FLOAT_C_FUN(copysign)(0.0, rhs_val); } else { - if ((lhs_val < 0.0) != (rhs_val < 0.0)) { + if ((lhs_val < MICROPY_FLOAT_ZERO) != (rhs_val < MICROPY_FLOAT_ZERO)) { lhs_val += rhs_val; } } @@ -317,7 +302,7 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t #if MICROPY_PY_BUILTINS_COMPLEX return mp_obj_complex_binary_op(MP_BINARY_OP_POWER, lhs_val, 0, rhs_in); #else - mp_raise_ValueError(translate("complex values not supported")); + mp_raise_ValueError(MP_ERROR_TEXT("complex values not supported")); #endif } lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); diff --git a/py/objfun.c b/py/objfun.c index 2388ba2998..3f6cfe0e20 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -60,6 +60,7 @@ STATIC mp_obj_t fun_builtin_0_call(mp_obj_t self_in, size_t n_args, size_t n_kw, 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, @@ -74,6 +75,7 @@ STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, 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, @@ -88,6 +90,7 @@ STATIC mp_obj_t fun_builtin_2_call(mp_obj_t self_in, size_t n_args, size_t n_kw, 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, @@ -102,6 +105,7 @@ STATIC mp_obj_t fun_builtin_3_call(mp_obj_t self_in, size_t n_args, size_t n_kw, 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, @@ -132,6 +136,7 @@ STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_k 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, @@ -186,7 +191,7 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) { // 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 (11 * sizeof(mp_uint_t)) +#define VM_MAX_STATE_ON_STACK (sizeof(mp_uint_t) * 11) #define DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ { \ @@ -353,6 +358,7 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { 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, @@ -404,6 +410,7 @@ STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, co 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, @@ -453,12 +460,12 @@ STATIC mp_uint_t convert_obj_for_inline_asm(mp_obj_t obj) { size_t l; return (mp_uint_t)mp_obj_str_get_data(obj, &l); } else { - mp_obj_type_t *type = mp_obj_get_type(obj); + 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); - } else + } #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) @@ -511,6 +518,7 @@ STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const 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, diff --git a/py/objgenerator.c b/py/objgenerator.c index 25f88212b9..3871dc462c 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -156,6 +156,7 @@ static void gen_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { 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, @@ -200,7 +201,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // Ensure the generator cannot be reentered during execution if (self->pend_exc == MP_OBJ_NULL) { - mp_raise_ValueError(translate("generator already executing")); + mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); } #if MICROPY_PY_GENERATOR_PEND_THROW @@ -213,7 +214,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // 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(translate("can't send non-None value to a just-started generator")); + mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); } } else { *self->code_state.sp = send_value; @@ -268,7 +269,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ *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, translate("generator raised StopIteration")); + *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); } break; } @@ -286,7 +287,7 @@ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_o if (ret == mp_const_none || ret == MP_OBJ_STOP_ITERATION) { return MP_OBJ_STOP_ITERATION; } else { - nlr_raise(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &ret)); + nlr_raise(mp_obj_new_exception_arg1(&mp_type_StopIteration, ret)); } case MP_VM_RETURN_YIELD: @@ -302,7 +303,7 @@ STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { // This translate is literally too much for m0 boards mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->coroutine_generator) { - mp_raise_TypeError(translate("'coroutine' object is not an iterator")); + mp_raise_TypeError(MP_ERROR_TEXT("'coroutine' object is not an iterator")); } #endif return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL); @@ -311,7 +312,7 @@ STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { mp_obj_t ret = gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL); if (ret == MP_OBJ_STOP_ITERATION) { - nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + mp_raise_type(&mp_type_StopIteration); } else { return ret; } @@ -354,7 +355,7 @@ STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) { mp_obj_t ret = gen_resume_and_raise(args[0], mp_const_none, exc); if (ret == MP_OBJ_STOP_ITERATION) { - nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + mp_raise_type(&mp_type_StopIteration); } else { return ret; } @@ -365,7 +366,7 @@ 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_RuntimeError(translate("generator ignored GeneratorExit")); + 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: @@ -386,7 +387,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close); 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(translate("generator already executing")); + mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); } mp_obj_t prev = self->pend_exc; self->pend_exc = exc_in; diff --git a/py/objint.c b/py/objint.c index 5921db6b31..187d1bfb22 100644 --- a/py/objint.c +++ b/py/objint.c @@ -137,11 +137,15 @@ STATIC mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { #undef MP_FLOAT_EXP_SHIFT_I32 mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { - int cl = fpclassify(val); - if (cl == FP_INFINITE) { - mp_raise_OverflowError_varg(translate("can't convert %q to %q"), MP_QSTR_inf, MP_QSTR_int); - } else if (cl == FP_NAN) { - mp_raise_ValueError_varg(translate("can't convert %q to %q"), MP_QSTR_NaN, MP_QSTR_int); + 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_OverflowError_varg(translate("can't convert %q to %q"), MP_QSTR_inf, MP_QSTR_int); + } else { + mp_raise_ValueError_varg(translate("can't convert %q to %q"), MP_QSTR_NaN, MP_QSTR_int); + } } else { mp_fp_as_int_class_t icl = mp_classify_fp_as_int(val); if (icl == MP_FP_CLASS_FIT_SMALLINT) { @@ -158,7 +162,7 @@ mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { return mp_obj_new_int_from_ll((long long)val); #endif } else { - mp_raise_ValueError(translate("float too big")); + mp_raise_ValueError(MP_ERROR_TEXT("float too big")); } #endif } @@ -392,19 +396,19 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i // 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, translate("long int not supported in this build")); + 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, translate("small int overflow")); + 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, translate("small int overflow")); + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } @@ -414,7 +418,7 @@ mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { if ((value & ~MP_SMALL_INT_POSITIVE_MASK) == 0) { return MP_OBJ_NEW_SMALL_INT(value); } - mp_raise_msg(&mp_type_OverflowError, translate("small int overflow")); + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } @@ -422,7 +426,7 @@ 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, translate("small int overflow")); + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("small int overflow")); return mp_const_none; } diff --git a/py/objint.h b/py/objint.h index 9595c1b3a7..fb491914f2 100644 --- a/py/objint.h +++ b/py/objint.h @@ -38,7 +38,7 @@ typedef struct _mp_obj_int_t { #endif } mp_obj_int_t; -extern const mp_obj_int_t mp_maxsize_obj; +extern const mp_obj_int_t mp_sys_maxsize_obj; #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in); diff --git a/py/objint_longlong.c b/py/objint_longlong.c index aa452a4bcf..368c74ec1d 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -42,7 +42,7 @@ #if MICROPY_PY_SYS_MAXSIZE // Export value for sys.maxsize -const mp_obj_int_t mp_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; +const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; #endif mp_obj_t mp_obj_int_bit_length_impl(mp_obj_t self_in) { @@ -203,7 +203,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i #if MICROPY_PY_BUILTINS_FLOAT return mp_obj_float_binary_op(op, lhs_val, rhs_in); #else - mp_raise_ValueError(translate("negative power with no float support")); + mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } long long ans = 1; @@ -236,7 +236,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i } zero_division: - mp_raise_msg(&mp_type_ZeroDivisionError, translate("division by zero")); + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("division by zero")); } mp_obj_t mp_obj_new_int(mp_int_t value) { @@ -265,7 +265,7 @@ mp_obj_t mp_obj_new_int_from_ll(long long val) { 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, translate("ulonglong too large")); + 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; diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 80564cf095..b5fce8ac4b 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -43,6 +43,7 @@ #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 @@ -66,7 +67,8 @@ STATIC const mpz_dig_t maxsize_dig[] = { #endif #endif }; -const mp_obj_int_t mp_maxsize_obj = { +// *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} }; @@ -202,11 +204,11 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i #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 - #endif } else { // delegate to generic function to check for extra cases return mp_obj_int_binary_op_extra_cases(op, lhs_in, rhs_in); @@ -220,7 +222,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i mp_float_t flhs = mpz_as_float(zlhs); mp_float_t frhs = mpz_as_float(zrhs); return mp_obj_new_float(flhs / frhs); - } else + } #endif if (op >= MP_BINARY_OP_INPLACE_OR && op < MP_BINARY_OP_CONTAINS) { @@ -243,7 +245,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: { if (mpz_is_zero(zrhs)) { zero_division_error: - mp_raise_msg(&mp_type_ZeroDivisionError, translate("division by zero")); + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } mpz_t rem; mpz_init_zero(&rem); @@ -282,7 +284,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i case MP_BINARY_OP_INPLACE_RSHIFT: { mp_int_t irhs = mp_obj_int_get_checked(rhs_in); if (irhs < 0) { - mp_raise_ValueError(translate("negative shift count")); + 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); @@ -298,7 +300,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i #if MICROPY_PY_BUILTINS_FLOAT return mp_obj_float_binary_op(op, mpz_as_float(zlhs), rhs_in); #else - mp_raise_ValueError(translate("negative power with no float support")); + mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } mpz_pow_inpl(&res->mpz, zlhs, zrhs); @@ -351,7 +353,7 @@ STATIC mpz_t *mp_mpz_for_int(mp_obj_t arg, mpz_t *temp) { 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(translate("pow() with 3 arguments requires integers")); + 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); @@ -436,11 +438,27 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return value; } else { // overflow - mp_raise_msg(&mp_type_OverflowError, translate("overflow converting long int to machine word")); + 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)); diff --git a/py/objlist.c b/py/objlist.c index 8a7a6509b4..e5f2a9ab91 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -93,7 +93,7 @@ STATIC mp_obj_t list_make_new(const mp_obj_type_t *type_in, size_t n_args, const } STATIC mp_obj_t list_unary_op(mp_unary_op_t op, mp_obj_t self_in) { - mp_obj_list_t *self = mp_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->len != 0); @@ -111,7 +111,7 @@ STATIC mp_obj_t list_unary_op(mp_unary_op_t op, mp_obj_t self_in) { } 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_instance_cast_to_native_base(lhs, &mp_type_list); + mp_obj_list_t *o = mp_obj_cast_to_native_base(lhs, &mp_type_list); switch (op) { case MP_BINARY_OP_ADD: { if (!mp_obj_is_type(rhs, &mp_type_list)) { @@ -162,7 +162,7 @@ STATIC mp_obj_t list_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { } STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { - mp_obj_list_t *self = mp_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); if (value == MP_OBJ_NULL) { // delete #if MICROPY_PY_BUILTINS_SLICE @@ -173,7 +173,6 @@ STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } mp_int_t len_adj = slice.start - slice.stop; - // printf("Len adj: %d\n", len_adj); 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 @@ -211,7 +210,6 @@ STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { mp_raise_NotImplementedError(NULL); } mp_int_t len_adj = value_len - (slice_out.stop - slice_out.start); - // printf("Len adj: %d\n", len_adj); if (len_adj > 0) { if (self->len + len_adj > self->alloc) { // TODO: Might optimize memory copies here by checking if block can @@ -243,7 +241,7 @@ STATIC mp_obj_t list_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); if (self->len >= self->alloc) { self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc * 2); self->alloc *= 2; @@ -256,8 +254,8 @@ mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg) { 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_instance_cast_to_native_base(self_in, &mp_type_list); - mp_obj_list_t *arg = mp_instance_cast_to_native_base(arg_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *arg = mp_obj_cast_to_native_base(arg_in, &mp_type_list); if (self->len + arg->len > self->alloc) { // TODO: use alloc policy for "4" @@ -276,7 +274,7 @@ STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in) { inline mp_obj_t mp_obj_list_pop(mp_obj_list_t *self, size_t index) { if (self->len == 0) { - mp_raise_IndexError_varg(translate("pop from empty %q"), MP_QSTR_list); + mp_raise_IndexError_varg(MP_ERROR_TEXT("pop from empty %q"), MP_QSTR_list); } mp_obj_t ret = self->items[index]; self->len -= 1; @@ -292,7 +290,7 @@ inline mp_obj_t mp_obj_list_pop(mp_obj_list_t *self, size_t index) { 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_instance_cast_to_native_base(args[0], &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(args[0], &mp_type_list); size_t index = mp_get_index(self->base.type, self->len, n_args == 1 ? MP_OBJ_NEW_SMALL_INT(-1) : args[1], false); return mp_obj_list_pop(self, index); } @@ -332,7 +330,7 @@ STATIC void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj // 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_PTR(&mp_const_none_obj)} }, + { 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} }, }; @@ -344,7 +342,7 @@ mp_obj_t mp_obj_list_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ 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_instance_cast_to_native_base(pos_args[0], &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(pos_args[0], &mp_type_list); if (self->len > 1) { mp_quicksort(self->items, self->items + self->len - 1, @@ -357,7 +355,7 @@ mp_obj_t mp_obj_list_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ mp_obj_t mp_obj_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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); self->len = 0; self->items = m_renew(mp_obj_t, self->items, self->alloc, LIST_MIN_ALLOC); self->alloc = LIST_MIN_ALLOC; @@ -367,19 +365,19 @@ mp_obj_t mp_obj_list_clear(mp_obj_t self_in) { 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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); 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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); 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_instance_cast_to_native_base(args[0], &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(args[0], &mp_type_list); return mp_seq_index_obj(self->items, self->len, n_args, args); } @@ -394,7 +392,7 @@ inline void mp_obj_list_insert(mp_obj_list_t *self, size_t index, mp_obj_t obj) 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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); // insert has its own strange index logic mp_int_t index = MP_OBJ_SMALL_INT_VALUE(idx); if (index < 0) { @@ -421,7 +419,7 @@ mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value) { 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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); mp_int_t len = self->len; for (mp_int_t i = 0; i < len / 2; i++) { @@ -498,7 +496,7 @@ mp_obj_t mp_obj_new_list(size_t n, mp_obj_t *items) { } void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items) { - mp_obj_list_t *self = mp_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); *len = self->len; *items = self->items; } @@ -511,7 +509,7 @@ void mp_obj_list_set_len(mp_obj_t self_in, size_t 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_instance_cast_to_native_base(self_in, &mp_type_list); + mp_obj_list_t *self = mp_obj_cast_to_native_base(self_in, &mp_type_list); size_t i = mp_get_index(self->base.type, self->len, index, false); self->items[i] = value; } diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c index 3a94d58a32..b1f1f0ba4d 100644 --- a/py/objnamedtuple.c +++ b/py/objnamedtuple.c @@ -96,7 +96,7 @@ void namedtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else { // delete/store attribute // provide more detailed error message than we'd get by just returning - mp_raise_AttributeError(translate("can't set attribute")); + mp_raise_AttributeError(MP_ERROR_TEXT("can't set attribute")); } } @@ -112,11 +112,11 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const mp_arg_error_terse_mismatch(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_raise_TypeError_varg( - translate("function takes %d positional arguments but %d were given"), + MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), num_fields, n_args + n_kw); #else mp_raise_TypeError_varg( - translate("%q() takes %d positional arguments but %d were given"), + MP_ERROR_TEXT("%q() takes %d positional arguments but %d were given"), type->base.name, num_fields, n_args + n_kw); #endif } @@ -138,7 +138,7 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( - translate("unexpected keyword argument '%q'"), kw); + MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw); #endif } if (tuple->items[id] != MP_OBJ_NULL) { @@ -146,7 +146,7 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( - translate("function got multiple values for argument '%q'"), kw); + MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw); #endif } tuple->items[id] = kw_args->table[i].value; @@ -168,6 +168,7 @@ mp_obj_namedtuple_type_t *mp_obj_new_namedtuple_base(size_t n_fields, mp_obj_t * 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; diff --git a/py/objnone.c b/py/objnone.c index b1fbd48f75..bbb7837090 100644 --- a/py/objnone.c +++ b/py/objnone.c @@ -28,9 +28,11 @@ #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; @@ -48,4 +50,6 @@ const mp_obj_type_t mp_type_NoneType = { .unary_op = mp_generic_unary_op, }; +#if !MICROPY_OBJ_IMMEDIATE_OBJS const mp_obj_none_t mp_const_none_obj = {{&mp_type_NoneType}}; +#endif diff --git a/py/objobject.c b/py/objobject.c index 8ad49d5188..0fdaf0f2b3 100644 --- a/py/objobject.c +++ b/py/objobject.c @@ -52,7 +52,7 @@ 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(translate("__new__ arg must be a user-type")); + mp_raise_TypeError(MP_ERROR_TEXT("__new__ arg must be a user-type")); } // This executes only "__new__" part of instance creation. // TODO: This won't work well for classes with native bases. @@ -64,6 +64,40 @@ STATIC mp_obj_t object___new__(mp_obj_t cls) { 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) }, @@ -71,6 +105,10 @@ STATIC const mp_rom_map_elem_t object_locals_dict_table[] = { #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); diff --git a/py/objproperty.c b/py/objproperty.c index 06ecfc8837..32c50f5b94 100644 --- a/py/objproperty.c +++ b/py/objproperty.c @@ -36,10 +36,10 @@ STATIC mp_obj_t property_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_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_PTR(&mp_const_none_obj)} }, - { MP_QSTR_, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, - { MP_QSTR_, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, - { MP_QSTR_doc, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, + { 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(n_args, args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); diff --git a/py/objrange.c b/py/objrange.c index a3e05ce446..1c28954d29 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -107,7 +107,7 @@ STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, const m if (n_args == 3) { o->step = mp_obj_get_int(args[2]); if (o->step == 0) { - mp_raise_ValueError(translate("zero step")); + mp_raise_ValueError(MP_ERROR_TEXT("zero step")); } } } diff --git a/py/objset.c b/py/objset.c index f2ead23914..9539781ff0 100644 --- a/py/objset.c +++ b/py/objset.c @@ -364,7 +364,7 @@ STATIC mp_obj_t set_pop(mp_obj_t 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_varg(&mp_type_KeyError, translate("pop from empty %q"), MP_QSTR_set); + mp_raise_msg_varg(&mp_type_KeyError, MP_ERROR_TEXT("pop from empty %q"), MP_QSTR_set); } return obj; } @@ -569,6 +569,7 @@ 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, diff --git a/py/objsingleton.c b/py/objsingleton.c index 1c27573343..d519a15a52 100644 --- a/py/objsingleton.c +++ b/py/objsingleton.c @@ -47,6 +47,7 @@ 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}; diff --git a/py/objslice.c b/py/objslice.c index a5d21578a1..2201e01751 100644 --- a/py/objslice.c +++ b/py/objslice.c @@ -29,7 +29,6 @@ #include "py/obj.h" #include "py/runtime.h" -#include "py/runtime0.h" #include "supervisor/shared/translate.h" @@ -38,13 +37,6 @@ #if MICROPY_PY_BUILTINS_SLICE -typedef struct _mp_obj_slice_t { - mp_obj_base_t base; - mp_obj_t start; - mp_obj_t stop; - mp_obj_t step; -} mp_obj_slice_t; - 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); @@ -57,78 +49,50 @@ STATIC void slice_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t mp_print_str(print, ")"); } -#if MICROPY_PY_BUILTINS_SLICE_ATTRS +#if MICROPY_PY_BUILTINS_SLICE_INDICES STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { - mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); - if (!mp_obj_is_small_int(length_obj)) { - mp_raise_TypeError(translate("Length must be an int")); - } + 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); - int length = MP_OBJ_SMALL_INT_VALUE(length_obj); - if (length < 0) { - mp_raise_ValueError(translate("Length must be non-negative")); - } - - mp_obj_t indices[3] = {MP_OBJ_NEW_SMALL_INT(0), length_obj, MP_OBJ_NEW_SMALL_INT(1)}; - mp_obj_t slice[2] = {self->start, self->stop}; - - int step = 1; - if (self->step != mp_const_none) { - indices[2] = self->step; - step = MP_OBJ_SMALL_INT_VALUE(self->step); - if (step < 0) { - indices[0] = MP_OBJ_NEW_SMALL_INT(length - 1); - indices[1] = MP_OBJ_NEW_SMALL_INT(-1); - } - if (step == 0) { - mp_raise_ValueError(translate("slice step cannot be zero")); - } - } - for (int i = 0; i < 2; i++) { - if (slice[i] == mp_const_none) { - continue; - } - int value = MP_OBJ_SMALL_INT_VALUE(slice[i]); - if (value < 0) { - value += length; - } - if (value < 0) { - if (step > 0) { - value = 0; - } else if (step < 0) { - value = -1; - } - } else if (value > length) { - value = length; - } - indices[i] = MP_OBJ_NEW_SMALL_INT(value); - } - - mp_obj_t tuple = mp_obj_new_tuple(3, indices); - - return tuple; + 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); } -MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices); +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) { - mp_convert_member_lookup(self_in, self->base.type, (mp_obj_t)&slice_indices_obj, dest); + dest[0] = MP_OBJ_FROM_PTR(&slice_indices_obj); + dest[1] = self_in; + #endif } } +#endif -STATIC mp_obj_t slice_make_new(const mp_obj_type_t *type, - size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); +#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 = { @@ -136,8 +100,9 @@ const mp_obj_type_t mp_type_slice = { .name = MP_QSTR_slice, .print = slice_print, #if MICROPY_PY_BUILTINS_SLICE_ATTRS - .make_new = slice_make_new, .attr = slice_attr, + #elif MICROPY_PY_BUILTINS_SLICE_INDICES + .locals_dict = (mp_obj_dict_t *)&slice_locals_dict, #endif }; @@ -162,7 +127,7 @@ void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *r } else { step = mp_obj_get_int(self->step); if (step == 0) { - mp_raise_ValueError(translate("slice step cannot be zero")); + mp_raise_ValueError(MP_ERROR_TEXT("slice step can't be zero")); } } @@ -215,39 +180,4 @@ void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *r result->step = step; } -#if MICROPY_PY_BUILTINS_SLICE_ATTRS -STATIC mp_obj_t slice_make_new(const mp_obj_type_t *type, - size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - if (type != &mp_type_slice) { - mp_raise_NotImplementedError(translate("Cannot subclass slice")); - } - // check number of arguments - mp_arg_check_num(n_args, kw_args, 1, 3, false); - - // 1st argument is the pin - mp_obj_t start = mp_const_none; - mp_obj_t stop = mp_const_none; - mp_obj_t step = mp_const_none; - if (n_args == 1) { - stop = args[0]; - } else { - start = args[0]; - stop = args[1]; - if (n_args == 3) { - step = args[2]; - } - } - - return mp_obj_new_slice(start, stop, step); -} -#endif - -void mp_obj_slice_get(mp_obj_t self_in, mp_obj_t *start, mp_obj_t *stop, mp_obj_t *step) { - assert(mp_obj_is_type(self_in, &mp_type_slice)); - mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); - *start = self->start; - *stop = self->stop; - *step = self->step; -} - #endif diff --git a/py/objstr.c b/py/objstr.c index e9e0bfc764..b82b20e1d9 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -274,7 +274,7 @@ STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, cons mp_int_t val = mp_obj_get_int(item); #if MICROPY_FULL_CHECKS if (val < 0 || val > 255) { - mp_raise_ValueError(translate("bytes value out of range")); + mp_raise_ValueError(MP_ERROR_TEXT("bytes value out of range")); } #endif vstr_add_byte(&vstr, val); @@ -283,7 +283,7 @@ STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, cons return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); wrong_args: - mp_raise_TypeError(translate("wrong number of arguments")); + mp_raise_TypeError(MP_ERROR_TEXT("wrong number of arguments")); } // like strstr but with specified length and allows \0 bytes @@ -336,7 +336,7 @@ mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i } // from now on we need lhs type and data, so extract them - mp_obj_type_t *lhs_type = mp_obj_get_type(lhs_in); + 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 @@ -446,7 +446,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s // 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) { - mp_obj_type_t *type = mp_obj_get_type(self_in); + 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 @@ -454,7 +454,7 @@ STATIC mp_obj_t bytes_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { 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(translate("only slices with step=1 (aka None) are supported")); + 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); } @@ -494,7 +494,7 @@ STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) { for (size_t i = 0; i < seq_len; i++) { if (mp_obj_get_type(seq_items[i]) != self_type) { mp_raise_TypeError( - translate("join expects a list of str/bytes objects consistent with self object")); + MP_ERROR_TEXT("join expects a list of str/bytes objects consistent with self object")); } if (i > 0) { required_len += sep_len; @@ -575,7 +575,7 @@ mp_obj_t mp_obj_str_split(size_t n_args, const mp_obj_t *args) { const char *sep_str = mp_obj_str_get_data(sep, &sep_len); if (sep_len == 0) { - mp_raise_ValueError(translate("empty separator")); + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } for (;;) { @@ -674,13 +674,13 @@ STATIC mp_obj_t str_rsplit(size_t n_args, const mp_obj_t *args) { mp_int_t idx = splits; if (sep == mp_const_none) { - mp_raise_NotImplementedError(translate("rsplit(None,n)")); + 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(translate("empty separator")); + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } const byte *beg = s; @@ -746,7 +746,7 @@ STATIC mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, b out_error: // not found if (is_index) { - mp_raise_ValueError(translate("substring not found")); + mp_raise_ValueError(MP_ERROR_TEXT("substring not found")); } else { return MP_OBJ_NEW_SMALL_INT(-1); } @@ -803,7 +803,7 @@ STATIC mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { size_t suffix_len; const char *suffix = mp_obj_str_get_data(args[1], &suffix_len); if (n_args > 2) { - mp_raise_NotImplementedError(translate("start/end indices")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("start/end indices")); } if (suffix_len > str_len) { @@ -942,7 +942,7 @@ STATIC bool istype(char ch) { } STATIC bool arg_looks_integer(mp_obj_t arg) { - return mp_obj_is_type(arg, &mp_type_bool) || mp_obj_is_int(arg); + return mp_obj_is_bool(arg) || mp_obj_is_int(arg); } STATIC bool arg_looks_numeric(mp_obj_t arg) { @@ -966,7 +966,7 @@ STATIC mp_obj_t arg_as_int(mp_obj_t arg) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE STATIC NORETURN void terse_str_format_value_error(void) { - mp_raise_ValueError(translate("bad format string")); + mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); } #else // define to nothing to improve coverage @@ -988,7 +988,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("single '}' encountered in format string")); + mp_raise_ValueError(MP_ERROR_TEXT("single '}' encountered in format string")); #endif } if (*str != '{') { @@ -1027,13 +1027,14 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL - mp_raise_ValueError(translate("bad conversion specifier")); + mp_raise_ValueError(MP_ERROR_TEXT("bad conversion specifier")); #else if (str >= top) { mp_raise_ValueError( - translate("end of format while looking for conversion specifier")); + MP_ERROR_TEXT("end of format while looking for conversion specifier")); } else { - mp_raise_ValueError_varg(translate("unknown conversion specifier %c"), *str); + mp_raise_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown conversion specifier %c"), *str); } #endif } @@ -1064,14 +1065,14 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("unmatched '{' in format")); + mp_raise_ValueError(MP_ERROR_TEXT("unmatched '{' in format")); #endif } if (*str != '}') { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - : w + terse_str_format_value_error(); #else - mp_raise_ValueError(translate("expected ':' after format specifier")); + mp_raise_ValueError(MP_ERROR_TEXT("expected ':' after format specifier")); #endif } @@ -1085,12 +1086,12 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError( - translate("can't switch from automatic field numbering to manual field specification")); + 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_IndexError_varg(translate("%q index out of range"), MP_QSTR_tuple); + mp_raise_IndexError_varg(MP_ERROR_TEXT("%q index out of range"), MP_QSTR_tuple); } arg = args[index + 1]; *arg_i = -1; @@ -1107,7 +1108,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar arg = key_elem->value; } if (field_name < field_name_top) { - mp_raise_NotImplementedError(translate("attributes not supported yet")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("attributes not supported yet")); } } else { if (*arg_i < 0) { @@ -1115,11 +1116,11 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError( - translate("can't switch from manual field specification to automatic field numbering")); + MP_ERROR_TEXT("can't switch from manual field specification to automatic field numbering")); #endif } if ((uint)*arg_i >= n_args - 1) { - mp_raise_IndexError_varg(translate("%q index out of range"), MP_QSTR_tuple); + mp_raise_IndexError_varg(MP_ERROR_TEXT("%q index out of range"), MP_QSTR_tuple); } arg = args[(*arg_i) + 1]; (*arg_i)++; @@ -1207,7 +1208,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("invalid format specifier")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid format specifier")); #endif } vstr_clear(&format_spec_vstr); @@ -1228,7 +1229,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("sign not allowed in string format specifier")); + mp_raise_ValueError(MP_ERROR_TEXT("sign not allowed in string format specifier")); #endif } if (type == 'c') { @@ -1236,7 +1237,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError( - translate("sign not allowed with integer format specifier 'c'")); + MP_ERROR_TEXT("sign not allowed with integer format specifier 'c'")); #endif } } @@ -1300,7 +1301,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError_varg( - translate("unknown format code '%c' for object of type '%q'"), + MP_ERROR_TEXT("unknown format code '%c' for object of type '%q'"), type, mp_obj_get_type_qstr(arg)); #endif } @@ -1372,7 +1373,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError_varg( - translate("unknown format code '%c' for object of type '%q'"), + MP_ERROR_TEXT("unknown format code '%c' for object of type '%q'"), type, mp_obj_get_type_qstr(arg)); #endif } @@ -1384,7 +1385,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError( - translate("'=' alignment not allowed in string format specifier")); + MP_ERROR_TEXT("'=' alignment not allowed in string format specifier")); #endif } @@ -1408,7 +1409,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar terse_str_format_value_error(); #else mp_raise_ValueError_varg( - translate("unknown format code '%c' for object of type '%q'"), + MP_ERROR_TEXT("unknown format code '%c' for object of type '%q'"), type, mp_obj_get_type_qstr(arg)); #endif } @@ -1433,7 +1434,9 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ 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; @@ -1457,7 +1460,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ // Dictionary value lookup if (*str == '(') { if (dict == MP_OBJ_NULL) { - mp_raise_TypeError(translate("format requires a dict")); + mp_raise_TypeError(MP_ERROR_TEXT("format requires a dict")); } arg_i = 1; // we used up the single dict argument const byte *key = ++str; @@ -1466,7 +1469,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("incomplete format key")); + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format key")); #endif } ++str; @@ -1530,7 +1533,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else - mp_raise_ValueError(translate("incomplete format")); + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format")); #endif } @@ -1538,7 +1541,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ if (arg == MP_OBJ_NULL) { if (arg_i >= n_args) { not_enough_args: - mp_raise_TypeError(translate("not enough arguments for format string")); + mp_raise_TypeError(MP_ERROR_TEXT("not enough arguments for format string")); } arg = args[arg_i++]; } @@ -1548,14 +1551,14 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ size_t slen; const char *s = mp_obj_str_get_data(arg, &slen); if (slen != 1) { - mp_raise_TypeError(translate("%%c requires int or char")); + mp_raise_TypeError(MP_ERROR_TEXT("%%c requires 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(translate("integer required")); + mp_raise_TypeError(MP_ERROR_TEXT("integer required")); } break; @@ -1617,14 +1620,14 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ terse_str_format_value_error(); #else mp_raise_ValueError_varg( - translate("unsupported format character '%c' (0x%x) at index %d"), + 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(translate("not all arguments converted during string formatting")); + mp_raise_TypeError(MP_ERROR_TEXT("not all arguments converted during string formatting")); } return mp_obj_new_str_from_vstr(is_bytes ? &mp_type_bytes : &mp_type_str, &vstr); @@ -1785,7 +1788,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_count_obj, 2, 4, str_count); #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)); - mp_obj_type_t *self_type = mp_obj_get_type(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); } @@ -1794,7 +1797,7 @@ STATIC mp_obj_t str_partitioner(mp_obj_t self_in, mp_obj_t arg, int direction) { GET_STR_DATA_LEN(arg, sep, sep_len); if (sep_len == 0) { - mp_raise_ValueError(translate("empty separator")); + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); } mp_obj_t result[3]; @@ -2159,10 +2162,10 @@ bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { STATIC NORETURN void bad_implicit_conversion(mp_obj_t self_in) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("can't convert to str implicitly")); + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); #else const qstr src_name = mp_obj_get_type_qstr(self_in); - mp_raise_TypeError_varg(translate("can't convert '%q' object to %q implicitly"), + mp_raise_TypeError_varg(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 } @@ -2202,13 +2205,13 @@ const char *mp_obj_str_get_data(mp_obj_t self_in, size_t *len) { } } -#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#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 *)self_in)->len; - return ((mp_obj_str_t *)self_in)->data; + *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 diff --git a/py/objstr.h b/py/objstr.h index 3672fe5d8a..74b48c1c71 100644 --- a/py/objstr.h +++ b/py/objstr.h @@ -50,7 +50,7 @@ typedef struct _mp_obj_str_t { { str_len = qstr_len(MP_OBJ_QSTR_VALUE(str_obj_in)); } else { str_len = ((mp_obj_str_t *)MP_OBJ_TO_PTR(str_obj_in))->len; } // use this macro to extract the string data and length -#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#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); #define GET_STR_DATA_LEN(str_obj_in, str_data, str_len) \ size_t str_len; const byte *str_data = mp_obj_str_get_data_no_check(str_obj_in, &str_len); diff --git a/py/objstringio.c b/py/objstringio.c index e0ae79943b..1a40dec759 100644 --- a/py/objstringio.c +++ b/py/objstringio.c @@ -40,7 +40,7 @@ #if MICROPY_CPYTHON_COMPAT STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { if (o->vstr == NULL) { - mp_raise_ValueError(translate("I/O operation on closed file")); + mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file")); } } #else @@ -226,6 +226,7 @@ STATIC const mp_rom_map_elem_t stringio_locals_dict_table[] = { { 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) }, diff --git a/py/objstrunicode.c b/py/objstrunicode.c index 898339569e..cc8e199688 100644 --- a/py/objstrunicode.c +++ b/py/objstrunicode.c @@ -151,7 +151,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s 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_TypeError_varg(translate("string indices must be integers, not %q"), mp_obj_get_type_qstr(index)); + mp_raise_TypeError_varg(MP_ERROR_TEXT("string indices must be integers, not %q"), mp_obj_get_type_qstr(index)); } const byte *s, *top = self_data + self_len; if (i < 0) { @@ -161,7 +161,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s if (is_slice) { return self_data; } - mp_raise_IndexError_varg(translate("%q index out of range"), MP_QSTR_str); + mp_raise_IndexError_varg(MP_ERROR_TEXT("%q index out of range"), MP_QSTR_str); } if (!UTF8_IS_CONT(*s)) { ++i; @@ -180,7 +180,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s if (is_slice) { return top; } - mp_raise_IndexError_varg(translate("%q index out of range"), MP_QSTR_str); + mp_raise_IndexError_varg(MP_ERROR_TEXT("%q index out of range"), MP_QSTR_str); } // Then check completion if (i-- == 0) { @@ -197,7 +197,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s } STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { - mp_obj_type_t *type = mp_obj_get_type(self_in); + 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) { @@ -205,9 +205,13 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_obj_t ostart, ostop, ostep; - mp_obj_slice_get(index, &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(translate("only slices with step=1 (aka None) are supported")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); } const byte *pstart, *pstop; diff --git a/py/objtuple.c b/py/objtuple.c index 8edbf455fa..c16212d7ac 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -37,6 +37,9 @@ // 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) +// 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 */ @@ -108,15 +111,12 @@ STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg // 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)); - mp_obj_type_t *another_type = mp_obj_get_type(another_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_instance_cast_to_native_base(another_in, MP_OBJ_FROM_PTR(&mp_type_tuple)); + another_in = mp_obj_cast_to_native_base(another_in, MP_OBJ_FROM_PTR(&mp_type_tuple)); if (another_in == MP_OBJ_NULL) { - if (op == MP_BINARY_OP_EQUAL) { - return mp_const_false; - } return MP_OBJ_NULL; } } @@ -190,14 +190,14 @@ mp_obj_t mp_obj_tuple_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); // when called with a native type (eg namedtuple) using mp_obj_tuple_subscr, get the native self if (self->base.type->subscr != &mp_obj_tuple_subscr) { - self = mp_instance_cast_to_native_base(self_in, &mp_type_tuple); + self = mp_obj_cast_to_native_base(self_in, &mp_type_tuple); } #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(translate("only slices with step=1 (aka None) are supported")); + 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); diff --git a/py/objtype.c b/py/objtype.c index 7546c624bf..01cd25448e 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -48,9 +48,6 @@ #define ENABLE_SPECIAL_ACCESSORS \ (MICROPY_PY_DESCRIPTORS || MICROPY_PY_DELATTR_SETATTR || MICROPY_PY_BUILTINS_PROPERTY) -#define TYPE_FLAG_IS_SUBCLASSED (0x0001) -#define TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) - STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self_in, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); /******************************************************************************/ @@ -173,7 +170,7 @@ STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_t if (type->locals_dict != NULL) { // search locals_dict (the set of methods/attributes) - assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + 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) { @@ -377,13 +374,12 @@ mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, cons } if (init_ret != mp_const_none) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("__init__() should return None")); + mp_raise_TypeError(MP_ERROR_TEXT("__init__() should return None")); #else - mp_raise_TypeError_varg(translate("__init__() should return None, not '%q'"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("__init__() should return None, not '%q'"), mp_obj_get_type_qstr(init_ret)); #endif } - } // If the type had a native base that was not explicitly initialised @@ -495,7 +491,7 @@ const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = { [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, // a != b calls a == b and inverts result + [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 @@ -617,16 +613,13 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des #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__: modifying - // it will not result in modifications to the actual instance members. - mp_map_t *map = &self->members; - mp_obj_t attr_dict = mp_obj_new_dict(map->used); - for (size_t i = 0; i < map->alloc; ++i) { - if (mp_map_slot_is_filled(map, i)) { - mp_obj_dict_store(attr_dict, map->table[i].key, map->table[i].value); - } - } - dest[0] = attr_dict; + // 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 @@ -640,8 +633,7 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des mp_obj_class_lookup(&lookup, self->base.type); mp_obj_t member = dest[0]; if (member != MP_OBJ_NULL) { - // changes here may may require changes to super_attr, below - if (!(self->base.type->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + if (!(self->base.type->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { // Class doesn't have any special accessors to check so return straightaway return; } @@ -657,7 +649,7 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des // the code. const mp_obj_t *proxy = mp_obj_property_get(member); if (proxy[0] == mp_const_none) { - mp_raise_AttributeError(translate("unreadable attribute")); + mp_raise_AttributeError(MP_ERROR_TEXT("unreadable attribute")); } else { dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); } @@ -706,7 +698,7 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des 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 & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + 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; } @@ -857,7 +849,7 @@ STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value } mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_SENTINEL) { - mp_obj_type_t *subobj_type = mp_obj_get_type(self->subobj[0]); + const mp_obj_type_t *subobj_type = mp_obj_get_type(self->subobj[0]); mp_obj_t ret = subobj_type->subscr(self_in, index, value); // May have called port specific C code. Make sure it didn't mess up the heap. assert_heap_ok(); @@ -898,9 +890,9 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons 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(translate("object not callable")); + mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else - mp_raise_TypeError_varg(translate("'%q' object is not callable"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not callable"), mp_obj_get_type_qstr(self_in)); #endif } @@ -912,7 +904,8 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons return mp_call_method_self_n_kw(member[0], member[1], n_args, n_kw, args); } -STATIC mp_obj_t instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { +// 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 = { @@ -926,7 +919,10 @@ STATIC mp_obj_t instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) if (member[0] == MP_OBJ_NULL) { return MP_OBJ_NULL; } else if (member[0] == MP_OBJ_SENTINEL) { - mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + 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); @@ -945,7 +941,7 @@ STATIC mp_int_t instance_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, }; mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_SENTINEL) { - mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + 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 @@ -1023,7 +1019,7 @@ STATIC mp_obj_t type_make_new(const mp_obj_type_t *type_in, size_t n_args, const return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]); default: - mp_raise_TypeError(translate("type takes 1 or 3 arguments")); + mp_raise_TypeError(MP_ERROR_TEXT("type takes 1 or 3 arguments")); } } @@ -1034,9 +1030,9 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp if (self->make_new == NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("cannot create instance")); + mp_raise_TypeError(MP_ERROR_TEXT("cannot create instance")); #else - mp_raise_TypeError_varg(translate("cannot create '%q' instances"), self->name); + mp_raise_TypeError_varg(MP_ERROR_TEXT("cannot create '%q' instances"), self->name); #endif } @@ -1060,6 +1056,21 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { 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. + mp_obj_dict_t *dict = self->locals_dict; + if (dict->map.is_fixed) { + dest[0] = MP_OBJ_FROM_PTR(dict); + } else { + dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict)); + dict = MP_OBJ_TO_PTR(dest[0]); + dict->map.is_fixed = 1; + } + return; + } + #endif if (attr == MP_QSTR___bases__) { if (self == &mp_type_object) { dest[0] = mp_const_empty_tuple; @@ -1088,7 +1099,7 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { // delete/store attribute if (self->locals_dict != NULL) { - assert(self->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + 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 @@ -1103,13 +1114,13 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else { #if ENABLE_SPECIAL_ACCESSORS // Check if we add any special accessor methods with this store - if (!(self->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + if (!(self->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { - if (self->flags & TYPE_FLAG_IS_SUBCLASSED) { + 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, translate("can't add special method to already-subclassed class")); + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't add special method to already-subclassed class")); } - self->flags |= TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + self->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } } #endif @@ -1138,35 +1149,36 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { mp_raise_TypeError(NULL); } - if (!mp_obj_is_type(locals_dict, &mp_type_dict)) { + 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 = 0; + 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(translate("type is not an acceptable base type")); + mp_raise_TypeError(MP_ERROR_TEXT("type is not an acceptable base type")); } 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(translate("type is not an acceptable base type")); + mp_raise_TypeError(MP_ERROR_TEXT("type is not an acceptable base type")); #else mp_raise_TypeError_varg( - translate("type '%q' is not an acceptable base type"), t->name); + MP_ERROR_TEXT("type '%q' is not an acceptable base type"), t->name); #endif } #if ENABLE_SPECIAL_ACCESSORS if (mp_obj_is_instance_type(t)) { - t->flags |= TYPE_FLAG_IS_SUBCLASSED; - base_flags |= t->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + t->flags |= MP_TYPE_FLAG_IS_SUBCLASSED; + base_flags |= t->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } #endif } @@ -1182,7 +1194,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) o->binary_op = instance_binary_op; o->attr = mp_obj_instance_attr; o->subscr = instance_subscr; - o->getiter = instance_getiter; + o->getiter = mp_obj_instance_getiter; // o->iternext = ; not implemented o->buffer_p.get_buffer = instance_get_buffer; @@ -1197,7 +1209,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) #if MICROPY_MULTIPLE_INHERITANCE o->parent = MP_OBJ_TO_PTR(bases_tuple); #else - mp_raise_NotImplementedError(translate("multiple inheritance not supported")); + mp_raise_NotImplementedError(MP_ERROR_TEXT("multiple inheritance not supported")); #endif } else { o->parent = MP_OBJ_TO_PTR(bases_items[0]); @@ -1206,22 +1218,36 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) o->locals_dict = make_dict_long_lived(locals_dict, 10); + #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(translate("multiple bases have instance lay-out conflict")); + mp_raise_TypeError(MP_ERROR_TEXT("multiple bases have instance lay-out conflict")); } mp_map_t *locals_map = &o->locals_dict->map; #if ENABLE_SPECIAL_ACCESSORS // Check if the class has any special accessor methods - if (!(o->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS) && + if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) && (map_has_special_accessors(locals_map) || (num_native_bases == 1 && native_base->locals_dict != NULL && map_has_special_accessors(&native_base->locals_dict->map)))) { - o->flags |= TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } #endif @@ -1262,7 +1288,7 @@ STATIC mp_obj_t super_make_new(const mp_obj_type_t *type_in, size_t n_args, cons // 1 argument is not yet implemented mp_arg_check_num(n_args, kw_args, 2, 2, false); if (!mp_obj_is_type(args[0], &mp_type_type)) { - mp_raise_TypeError(translate("first argument to super() must be type")); + mp_raise_TypeError(MP_ERROR_TEXT("first argument to super() must be type")); } mp_obj_super_t *o = m_new_obj(mp_obj_super_t); *o = (mp_obj_super_t) {{type_in}, args[0], args[1]}; @@ -1427,7 +1453,7 @@ STATIC mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { } else if (mp_obj_is_type(classinfo, &mp_type_tuple)) { mp_obj_tuple_get(classinfo, &len, &items); } else { - mp_raise_TypeError(translate("issubclass() arg 2 must be a class or a tuple of classes")); + 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++) { @@ -1441,7 +1467,7 @@ STATIC mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { 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(translate("issubclass() arg 1 must be a class")); + mp_raise_TypeError(MP_ERROR_TEXT("issubclass() arg 1 must be a class")); } return mp_obj_is_subclass(object, classinfo); } @@ -1454,16 +1480,17 @@ STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) { MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance); -mp_obj_t mp_instance_cast_to_native_base(mp_obj_t self_in, mp_const_obj_t native_type) { - mp_obj_type_t *self_type = mp_obj_get_type(self_in); - if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(self_type), native_type)) { - return MP_OBJ_NULL; - } +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]; } - mp_obj_instance_t *self = (mp_obj_instance_t *)MP_OBJ_TO_PTR(self_in); - return self->subobj[0]; } /******************************************************************************/ diff --git a/py/objtype.h b/py/objtype.h index a44622ffeb..072c39d3e7 100644 --- a/py/objtype.h +++ b/py/objtype.h @@ -53,4 +53,7 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons // this needs to be exposed for the above macros to work correctly mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self_in, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); +// this needs to be exposed for mp_getiter +mp_obj_t mp_obj_instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf); + #endif // MICROPY_INCLUDED_PY_OBJTYPE_H diff --git a/py/opmethods.c b/py/opmethods.c index 07d1e340de..bd1ff829e2 100644 --- a/py/opmethods.c +++ b/py/opmethods.c @@ -28,25 +28,25 @@ #include "py/builtin.h" STATIC mp_obj_t op_getitem(mp_obj_t self_in, mp_obj_t key_in) { - mp_obj_type_t *type = mp_obj_get_type(self_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) { - mp_obj_type_t *type = mp_obj_get_type(self_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) { - mp_obj_type_t *type = mp_obj_get_type(self_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) { - mp_obj_type_t *type = mp_obj_get_type(lhs_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); diff --git a/py/pairheap.c b/py/pairheap.c new file mode 100644 index 0000000000..d3a011c4ae --- /dev/null +++ b/py/pairheap.c @@ -0,0 +1,147 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#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; +} diff --git a/py/pairheap.h b/py/pairheap.h new file mode 100644 index 0000000000..68b8b0f758 --- /dev/null +++ b/py/pairheap.h @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_PY_PAIRHEAP_H +#define MICROPY_INCLUDED_PY_PAIRHEAP_H + +// This is an implementation of a pairing heap. It is stable and has deletion +// support. Only the less-than operation needs to be defined on elements. +// +// See original paper for details: +// Michael L. Fredman, Robert Sedjewick, Daniel D. Sleator, and Robert E. Tarjan. +// The Pairing Heap: A New Form of Self-Adjusting Heap. +// Algorithmica 1:111-129, 1986. +// https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf + +#include +#include "py/obj.h" + +// This struct forms the nodes of the heap and is intended to be extended, by +// placing it first in another struct, to include additional information for the +// element stored in the heap. It includes "base" so it can be a MicroPython +// object allocated on the heap and the GC can automatically trace all nodes by +// following the tree structure. +typedef struct _mp_pairheap_t { + mp_obj_base_t base; + struct _mp_pairheap_t *child; + struct _mp_pairheap_t *child_last; + struct _mp_pairheap_t *next; +} mp_pairheap_t; + +// This is the function for the less-than operation on nodes/elements. +typedef int (*mp_pairheap_lt_t)(mp_pairheap_t *, mp_pairheap_t *); + +// Core functions. +mp_pairheap_t *mp_pairheap_meld(mp_pairheap_lt_t lt, mp_pairheap_t *heap1, mp_pairheap_t *heap2); +mp_pairheap_t *mp_pairheap_pairing(mp_pairheap_lt_t lt, mp_pairheap_t *child); +mp_pairheap_t *mp_pairheap_delete(mp_pairheap_lt_t lt, mp_pairheap_t *heap, mp_pairheap_t *node); + +// Create a new heap. +static inline mp_pairheap_t *mp_pairheap_new(mp_pairheap_lt_t lt) { + (void)lt; + return NULL; +} + +// Initialise a single pairing-heap node so it is ready to push on to a heap. +static inline void mp_pairheap_init_node(mp_pairheap_lt_t lt, mp_pairheap_t *node) { + (void)lt; + node->child = NULL; + node->next = NULL; +} + +// Test if the heap is empty. +static inline bool mp_pairheap_is_empty(mp_pairheap_lt_t lt, mp_pairheap_t *heap) { + (void)lt; + return heap == NULL; +} + +// Peek at the top of the heap. Will return NULL if empty. +static inline mp_pairheap_t *mp_pairheap_peek(mp_pairheap_lt_t lt, mp_pairheap_t *heap) { + (void)lt; + return heap; +} + +// Push new node onto existing heap. Returns the new heap. +static inline mp_pairheap_t *mp_pairheap_push(mp_pairheap_lt_t lt, mp_pairheap_t *heap, mp_pairheap_t *node) { + assert(node->child == NULL && node->next == NULL); + return mp_pairheap_meld(lt, node, heap); // node is first to be stable +} + +// Pop the top off the heap, which must not be empty. Returns the new heap. +static inline mp_pairheap_t *mp_pairheap_pop(mp_pairheap_lt_t lt, mp_pairheap_t *heap) { + assert(heap->next == NULL); + mp_pairheap_t *child = heap->child; + heap->child = NULL; + return mp_pairheap_pairing(lt, child); +} + +#endif // MICROPY_INCLUDED_PY_PAIRHEAP_H diff --git a/py/parse.c b/py/parse.c index 6fce3d905d..2b45787088 100644 --- a/py/parse.c +++ b/py/parse.c @@ -60,6 +60,8 @@ // (un)comment to use rule names; for debugging // #define USE_RULE_NAME (1) +// *FORMAT-OFF* + enum { // define rules with a compile function #define DEF_RULE(rule, comp, kind, ...) RULE_##rule, @@ -209,8 +211,10 @@ STATIC const char *const rule_name_table[] = { }; #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 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; @@ -350,7 +354,7 @@ bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) { } } -int mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes) { +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; @@ -620,8 +624,9 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { mp_obj_t arg0; if (rule_id == RULE_expr || rule_id == RULE_xor_expr - || rule_id == RULE_and_expr) { - // folding for binary ops: | ^ & + || 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; @@ -631,8 +636,10 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { op = MP_BINARY_OP_OR; } else if (rule_id == RULE_xor_expr) { op = MP_BINARY_OP_XOR; - } else { + } 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); @@ -640,6 +647,10 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { 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 @@ -657,8 +668,8 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { 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 || tok == MP_TOKEN_OP_DBL_STAR) { - // Can't fold @ or / or ** + 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); @@ -716,7 +727,7 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { 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, - translate("constant must be an integer")); + 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); @@ -1167,37 +1178,37 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { switch (lex->tok_kind) { case MP_TOKEN_INDENT: exc = mp_obj_new_exception_msg(&mp_type_IndentationError, - translate("unexpected indent")); + MP_ERROR_TEXT("unexpected indent")); break; case MP_TOKEN_DEDENT_MISMATCH: exc = mp_obj_new_exception_msg(&mp_type_IndentationError, - translate("unindent does not match any outer indentation level")); + MP_ERROR_TEXT("unindent does not match any outer indentation level")); break; #if MICROPY_COMP_FSTRING_LITERAL #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED case MP_TOKEN_FSTRING_BACKSLASH: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("f-string expression part cannot include a backslash")); + MP_ERROR_TEXT("f-string expression part cannot include a backslash")); break; case MP_TOKEN_FSTRING_COMMENT: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("f-string expression part cannot include a '#'")); + MP_ERROR_TEXT("f-string expression part cannot include a '#'")); break; case MP_TOKEN_FSTRING_UNCLOSED: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("f-string: expecting '}'")); + MP_ERROR_TEXT("f-string: expecting '}'")); break; case MP_TOKEN_FSTRING_UNOPENED: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("f-string: single '}' is not allowed")); + MP_ERROR_TEXT("f-string: single '}' is not allowed")); break; case MP_TOKEN_FSTRING_EMPTY_EXP: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("f-string: empty expression not allowed")); + MP_ERROR_TEXT("f-string: empty expression not allowed")); break; case MP_TOKEN_FSTRING_RAW: exc = mp_obj_new_exception_msg(&mp_type_NotImplementedError, - translate("raw f-strings are not implemented")); + MP_ERROR_TEXT("raw f-strings are not implemented")); break; #else case MP_TOKEN_FSTRING_BACKSLASH: @@ -1207,13 +1218,13 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { case MP_TOKEN_FSTRING_EMPTY_EXP: case MP_TOKEN_FSTRING_RAW: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("malformed f-string")); + MP_ERROR_TEXT("malformed f-string")); break; #endif #endif default: exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - translate("invalid syntax")); + MP_ERROR_TEXT("invalid syntax")); break; } // add traceback to give info about file name and location diff --git a/py/parse.h b/py/parse.h index 1e6a5888eb..1239e6e5eb 100644 --- a/py/parse.h +++ b/py/parse.h @@ -85,7 +85,7 @@ static inline mp_parse_node_t mp_parse_node_new_leaf(size_t kind, mp_int_t arg) bool mp_parse_node_is_const_false(mp_parse_node_t pn); bool mp_parse_node_is_const_true(mp_parse_node_t pn); bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o); -int mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes); +size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes); void mp_parse_node_print(mp_parse_node_t pn, size_t indent); typedef enum { diff --git a/py/parsenum.c b/py/parsenum.c index dc4dd4e81f..b3b79cad09 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -57,7 +57,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m // check radix base if ((base != 0 && base < 2) || base > 36) { // this won't be reached if lex!=NULL - mp_raise_ValueError(translate("int() arg 2 must be >= 2 and <= 36")); + mp_raise_ValueError(MP_ERROR_TEXT("int() arg 2 must be >= 2 and <= 36")); } // skip leading space @@ -145,25 +145,27 @@ overflow: 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, - translate("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, - translate("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 +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 { @@ -344,7 +346,7 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } #else if (imag || force_complex) { - raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, translate("complex values not supported")), lex); + raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("complex values not supported")), lex); } #endif else { @@ -352,9 +354,9 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } value_error: - raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, translate("invalid syntax for number")), lex); + 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, translate("decimal numbers not supported")), lex); + raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("decimal numbers not supported")), lex); #endif } diff --git a/py/persistentcode.c b/py/persistentcode.c index e7423745f6..ae212bbbef 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * SPDX-FileCopyrightText: Copyright (c) 2013-2016 Damien P. George + * SPDX-FileCopyrightText: Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,6 +34,7 @@ #include "py/persistentcode.h" #include "py/bc0.h" #include "py/objstr.h" +#include "py/mpthread.h" #include "supervisor/shared/translate.h" @@ -381,7 +382,7 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { #if !MICROPY_EMIT_MACHINE_CODE if (kind != MP_CODE_BYTECODE) { - mp_raise_ValueError(translate("incompatible .mpy file")); + } #endif @@ -543,6 +544,18 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else if (prelude.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 @@ -569,12 +582,12 @@ mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader) { || MPY_FEATURE_DECODE_FLAGS(header[2]) != MPY_FEATURE_FLAGS || header[3] > mp_small_int_bits() || read_uint(reader, NULL) > QSTR_WINDOW_SIZE) { - mp_raise_MpyError(translate("Incompatible .mpy file. Please update all .mpy files. See http://adafru.it/mpy-update for more info.")); + mp_raise_MpyError(MP_ERROR_TEXT("Incompatible .mpy file. Please update all .mpy files. See http://adafru.it/mpy-update for more info.")); } 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(translate("incompatible native .mpy architecture")); + mp_raise_ValueError(MP_ERROR_TEXT("incompatible native .mpy architecture")); } } qstr_window_t qw; @@ -851,15 +864,21 @@ void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print) { 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_raw_code_t *rc, 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(rc, &fd_print); + MP_THREAD_GIL_EXIT(); close(fd); + MP_THREAD_GIL_ENTER(); } #else diff --git a/py/proto.c b/py/proto.c index ad9684f3c5..c2a6e1e5f6 100644 --- a/py/proto.c +++ b/py/proto.c @@ -30,7 +30,7 @@ #ifndef MICROPY_UNSAFE_PROTO const void *mp_proto_get(uint16_t name, mp_const_obj_t obj) { - mp_obj_type_t *type = mp_obj_get_type(obj); + const mp_obj_type_t *type = mp_obj_get_type(obj); if (!type->protocol) { return NULL; } diff --git a/py/py.mk b/py/py.mk index 5e23b68885..0a8bd4cf06 100644 --- a/py/py.mk +++ b/py/py.mk @@ -23,32 +23,29 @@ QSTR_GLOBAL_REQUIREMENTS += $(HEADER_BUILD)/mpversion.h # some code is performance bottleneck and compiled with other optimization options CSUPEROPT = -O3 -ifeq ($(MICROPY_PY_BTREE),1) -BTREE_DIR = lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error=printf -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -INC += -I$(TOP)/$(BTREE_DIR)/PORT/include -SRC_MOD += extmod/modbtree.c -SRC_MOD += $(addprefix $(BTREE_DIR)/,\ -btree/bt_close.c \ -btree/bt_conv.c \ -btree/bt_debug.c \ -btree/bt_delete.c \ -btree/bt_get.c \ -btree/bt_open.c \ -btree/bt_overflow.c \ -btree/bt_page.c \ -btree/bt_put.c \ -btree/bt_search.c \ -btree/bt_seq.c \ -btree/bt_split.c \ -btree/bt_utils.c \ -mpool/mpool.c \ - ) -CFLAGS_MOD += -DMICROPY_PY_BTREE=1 -# we need to suppress certain warnings to get berkeley-db to compile cleanly -# and we have separate BTREE_DEFS so the definitions don't interfere with other source code -$(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter $(BTREE_DEFS) -$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS) +# Enable building 32-bit code on 64-bit host. +ifeq ($(MICROPY_FORCE_32BIT),1) +CC += -m32 +CXX += -m32 +LD += -m32 +endif + +# External modules written in C. +ifneq ($(USER_C_MODULES),) +# pre-define USERMOD variables as expanded so that variables are immediate +# expanded as they're added to them +SRC_USERMOD := +CFLAGS_USERMOD := +LDFLAGS_USERMOD := +$(foreach module, $(wildcard $(USER_C_MODULES)/*/micropython.mk), \ + $(eval USERMOD_DIR = $(patsubst %/,%,$(dir $(module))))\ + $(info Including User C Module from $(USERMOD_DIR))\ + $(eval include $(module))\ +) + +SRC_MOD += $(patsubst $(USER_C_MODULES)/%.c,%.c,$(SRC_USERMOD)) +CFLAGS_MOD += $(CFLAGS_USERMOD) +LDFLAGS_MOD += $(LDFLAGS_USERMOD) endif ifeq ($(CIRCUITPY_ULAB),1) @@ -125,6 +122,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ runtime_utils.o \ scheduler.o \ nativeglue.o \ + pairheap.o \ ringbuf.o \ stackctrl.o \ argcheck.o \ @@ -201,6 +199,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ ) PY_EXTMOD_O_BASENAME = \ + extmod/moduasyncio.o \ extmod/moductypes.o \ extmod/modujson.o \ extmod/modure.o \ @@ -304,6 +303,11 @@ $(PY_BUILD)/qstr.o: $(HEADER_BUILD)/qstrdefs.generated.h CFLAGS_BUILTIN ?= -ffreestanding -fno-builtin -fno-lto $(BUILD)/lib/libc/string0.o: CFLAGS += $(CFLAGS_BUILTIN) +# Standard C functions like memset need to be compiled with special flags so +# the compiler does not optimise these functions in terms of themselves. +CFLAGS_BUILTIN ?= -ffreestanding -fno-builtin -fno-lto +$(BUILD)/lib/libc/string0.o: CFLAGS += $(CFLAGS_BUILTIN) + # Force nlr code to always be compiled with space-saving optimisation so # that the function preludes are of a minimal and predictable form. $(PY_BUILD)/nlr%.o: CFLAGS += -Os diff --git a/py/qstr.c b/py/qstr.c index 879fe30f22..a44e2b068b 100644 --- a/py/qstr.c +++ b/py/qstr.c @@ -115,7 +115,7 @@ 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 + #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(qstr_mutex)); #endif } @@ -207,7 +207,7 @@ qstr qstr_from_strn(const char *str, size_t len) { // check that len is not too big if (len >= (1 << (8 * MICROPY_QSTR_BYTES_IN_LEN))) { QSTR_EXIT(); - mp_raise_msg(&mp_type_RuntimeError, translate("Name too long")); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Name too long")); } // compute number of bytes needed to intern this string @@ -318,3 +318,78 @@ void qstr_dump_data(void) { 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 diff --git a/py/qstr.h b/py/qstr.h index bf9e30231b..a866e056bc 100644 --- a/py/qstr.h +++ b/py/qstr.h @@ -92,4 +92,9 @@ const byte *qstr_data(qstr q, size_t *len); void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, size_t *n_total_bytes); void qstr_dump_data(void); +#if MICROPY_ROM_TEXT_COMPRESSION +void mp_decompress_rom_string(byte *dst, mp_rom_error_text_t src); +#define MP_IS_COMPRESSED_ROM_STRING(s) (*(byte *)(s) == 0xff) +#endif + #endif // MICROPY_INCLUDED_PY_QSTR_H diff --git a/py/reader.c b/py/reader.c index dfd5daf63e..ecc8515662 100644 --- a/py/reader.c +++ b/py/reader.c @@ -29,6 +29,7 @@ #include "py/runtime.h" #include "py/mperrno.h" +#include "py/mpthread.h" #include "py/reader.h" typedef struct _mp_reader_mem_t { @@ -86,7 +87,9 @@ STATIC mp_uint_t mp_reader_posix_readbyte(void *data) { 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; @@ -101,7 +104,9 @@ STATIC mp_uint_t mp_reader_posix_readbyte(void *data) { 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); } @@ -110,13 +115,16 @@ 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; @@ -127,7 +135,9 @@ void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { #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); } diff --git a/py/runtime.c b/py/runtime.c index 95210ce8b3..8a60b90713 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -40,6 +40,7 @@ #include "py/objtuple.h" #include "py/objtype.h" #include "py/objlist.h" +#include "py/objtype.h" #include "py/objmodule.h" #include "py/objgenerator.h" #include "py/smallint.h" @@ -123,6 +124,10 @@ void mp_init(void) { 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 + #ifdef MICROPY_FSUSERMOUNT // zero out the pointers to the user-mounted devices memset(MP_STATE_VM(fs_user_mount) + MICROPY_FATFS_NUM_PERSISTENT, 0, @@ -143,19 +148,24 @@ void mp_init(void) { 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(); - // mp_obj_dict_free(&dict_main); - // mp_map_deinit(&MP_STATE_VM(mp_loaded_modules_map)); - // call port specific deinitialization if any #ifdef MICROPY_PORT_DEINIT_FUNC MICROPY_PORT_DEINIT_FUNC; #endif + + // mp_obj_dict_free(&dict_main); + // mp_map_deinit(&MP_STATE_VM(mp_loaded_modules_map)); } mp_obj_t mp_load_name(qstr qst) { @@ -188,10 +198,9 @@ mp_obj_t mp_load_global(qstr qst) { 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, translate("name not defined")); + mp_raise_msg(&mp_type_NameError, MP_ERROR_TEXT("name not defined")); #else - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_NameError, - translate("name '%q' is not defined"), qst)); + mp_raise_msg_varg(&mp_type_NameError, MP_ERROR_TEXT("name '%q' is not defined"), qst); #endif } } @@ -279,7 +288,7 @@ mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { } return MP_OBJ_NEW_SMALL_INT(h); } else { - mp_obj_type_t *type = mp_obj_get_type(arg); + 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) { @@ -290,15 +299,15 @@ mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { // 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(translate("can't convert to int")); + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); } else { - mp_raise_TypeError(translate("unsupported type for operator")); + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); } } else { if (op == MP_UNARY_OP_INT) { - mp_raise_TypeError_varg(translate("can't convert %q to int"), mp_obj_get_type_qstr(arg)); + mp_raise_TypeError_varg(MP_ERROR_TEXT("can't convert %q to int"), mp_obj_get_type_qstr(arg)); } else { - mp_raise_TypeError_varg(translate("unsupported type for %q: '%q'"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("unsupported type for %q: '%q'"), mp_unary_op_method_name[op], mp_obj_get_type_qstr(arg)); } } @@ -324,19 +333,8 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r // deal with == and != for all types if (op == MP_BINARY_OP_EQUAL || op == MP_BINARY_OP_NOT_EQUAL) { - if (mp_obj_equal(lhs, rhs)) { - if (op == MP_BINARY_OP_EQUAL) { - return mp_const_true; - } else { - return mp_const_false; - } - } else { - if (op == MP_BINARY_OP_EQUAL) { - return mp_const_false; - } else { - return mp_const_true; - } - } + // 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 @@ -394,7 +392,7 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r case MP_BINARY_OP_INPLACE_LSHIFT: { if (rhs_val < 0) { // negative shift not allowed - mp_raise_ValueError(translate("negative shift count")); + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } else if (rhs_val >= (mp_int_t)BITS_PER_WORD || 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); @@ -409,7 +407,7 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r case MP_BINARY_OP_INPLACE_RSHIFT: if (rhs_val < 0) { // negative shift not allowed - mp_raise_ValueError(translate("negative shift count")); + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } else { // standard precision is enough for right-shift if (rhs_val >= (mp_int_t)BITS_PER_WORD) { @@ -485,9 +483,9 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r 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); + return mp_obj_float_binary_op(op, (mp_float_t)lhs_val, rhs); #else - mp_raise_ValueError(translate("negative power with no float support")); + mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } else { mp_int_t ans = 1; @@ -547,21 +545,21 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r } #if MICROPY_PY_BUILTINS_FLOAT } else if (mp_obj_is_float(rhs)) { - mp_obj_t res = mp_obj_float_binary_op(op, lhs_val, rhs); - if (res == MP_OBJ_NULL) { - goto unsupported_op; - } else { - return res; - } - #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, lhs_val, 0, 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 } } @@ -575,7 +573,7 @@ mp_obj_t PLACE_IN_ITCM(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t r } // generic binary_op supplied by type - mp_obj_type_t *type; + const mp_obj_type_t *type; generic_binary_op: type = mp_obj_get_type(lhs); if (type->binary_op != NULL) { @@ -617,15 +615,15 @@ generic_binary_op: unsupported_op: #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("unsupported type for operator")); + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); #else mp_raise_TypeError_varg( - translate("unsupported types for %q: '%q', '%q'"), + MP_ERROR_TEXT("unsupported types for %q: '%q', '%q'"), mp_binary_op_method_name[op], mp_obj_get_type_qstr(lhs), mp_obj_get_type_qstr(rhs)); #endif zero_division: - mp_raise_msg(&mp_type_ZeroDivisionError, translate("division by zero")); + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("division by zero")); } mp_obj_t mp_call_function_0(mp_obj_t fun) { @@ -651,7 +649,7 @@ mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, cons 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 - mp_obj_type_t *type = mp_obj_get_type(fun_in); + const mp_obj_type_t *type = mp_obj_get_type(fun_in); // do the call if (type->call != NULL) { @@ -659,9 +657,9 @@ mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, cons } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("object not callable")); + mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else - mp_raise_TypeError_varg(translate("'%q' object is not callable"), mp_obj_get_type_qstr(fun_in)); + mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not callable"), mp_obj_get_type_qstr(fun_in)); #endif } @@ -887,16 +885,16 @@ void mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) { too_short: #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_ValueError(translate("wrong number of values to unpack")); + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else - mp_raise_ValueError_varg(translate("need more than %d values to unpack"), + mp_raise_ValueError_varg(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(translate("wrong number of values to unpack")); + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else - mp_raise_ValueError_varg(translate("too many values to unpack (expected %d)"), + mp_raise_ValueError_varg(MP_ERROR_TEXT("too many values to unpack (expected %d)"), (int)num); #endif } @@ -956,9 +954,9 @@ void mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) { too_short: #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_ValueError(translate("wrong number of values to unpack")); + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else - mp_raise_ValueError_varg(translate("need more than %d values to unpack"), + mp_raise_ValueError_varg(MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len); #endif } @@ -995,9 +993,9 @@ STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, c 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(translate("argument has wrong type")); + mp_raise_TypeError(MP_ERROR_TEXT("argument has wrong type")); } else { - mp_raise_TypeError_varg(translate("argument should be a '%q' not a '%q'"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("argument should be a '%q' not a '%q'"), self->type->name, arg0_type->name); } } @@ -1007,6 +1005,7 @@ STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, c 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, }; @@ -1025,44 +1024,51 @@ STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) // 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_type(member, &mp_type_staticmethod)) { - // return just the function - dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; - } else if (mp_obj_is_type(member, &mp_type_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 if (mp_obj_is_type(member, &mp_type_type)) { - // Don't try to bind types (even though they're callable) - dest[0] = member; - } else if (mp_obj_is_fun(member) - || (mp_obj_is_obj(member) - && (((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_closure - || ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_generator))) { - // only functions, closures and generators objects can be bound to self - #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + if (mp_obj_is_obj(member)) { const mp_obj_type_t *m_type = ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type; - if (self == MP_OBJ_NULL - && (m_type == &mp_type_fun_builtin_0 - || m_type == &mp_type_fun_builtin_1 - || m_type == &mp_type_fun_builtin_2 - || m_type == &mp_type_fun_builtin_3 - || m_type == &mp_type_fun_builtin_var)) { - // we extracted a builtin method without a first argument, so we must - // wrap this function in a type checker - dest[0] = mp_obj_new_checked_fun(type, member); - } else - #endif - { - // return a bound method, with self being this object + 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; - dest[1] = self; } #if MICROPY_PY_BUILTINS_PROPERTY // If self is MP_OBJ_NULL, we looking at the class itself, not an instance. @@ -1082,7 +1088,7 @@ void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t } #endif } else { - // class member is a value, so just return that value + // `member` is a value, so just return that value. dest[0] = member; } } @@ -1096,15 +1102,17 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { dest[1] = MP_OBJ_NULL; // get the type - mp_obj_type_t *type = mp_obj_get_type(obj); + 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); - } else + return; + } #endif + if (attr == MP_QSTR___next__ && type->iternext != NULL) { dest[0] = MP_OBJ_FROM_PTR(&mp_builtin_next_obj); dest[1] = obj; @@ -1133,22 +1141,18 @@ void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { // no attribute/method called attr #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_AttributeError(translate("no such attribute")); - #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED + 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)) { - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, - translate("type object '%q' has no attribute '%q'"), - ((mp_obj_type_t *)MP_OBJ_TO_PTR(base))->name, attr)); + 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 { - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, - translate("'%q' object has no attribute '%q'"), - mp_obj_get_type_qstr(base), attr)); + mp_raise_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("'%s' object has no attribute '%q'"), + mp_obj_get_type_str(base), attr); } - #else - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, - translate("'%q' object has no attribute '%q'"), - mp_obj_get_type_qstr(base), attr)); #endif } } @@ -1171,7 +1175,7 @@ void mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catc 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); - mp_obj_type_t *type = mp_obj_get_type(base); + 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); @@ -1211,17 +1215,17 @@ void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) { #endif } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_AttributeError(translate("no such attribute")); + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); #else - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, - translate("'%q' object cannot assign attribute '%q'"), - mp_obj_get_type_qstr(base), attr)); + 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); - mp_obj_type_t *type = mp_obj_get_type(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. @@ -1229,13 +1233,13 @@ mp_obj_t mp_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { return o_in; } - // if caller did not provide a buffer then allocate one on the heap - if (iter_buf == NULL) { - iter_buf = m_new_obj(mp_obj_iter_buf_t); - } - // 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; @@ -1247,22 +1251,26 @@ mp_obj_t mp_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { 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(translate("object not iterable")); + mp_raise_TypeError(MP_ERROR_TEXT("object not iterable")); #else mp_raise_TypeError_varg( - translate("'%q' object is not iterable"), mp_obj_get_type_qstr(o_in)); + MP_ERROR_TEXT("'%q' object is not iterable"), mp_obj_get_type_qstr(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) { - mp_obj_type_t *type = mp_obj_get_type(o_in); + const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->iternext != NULL) { return type->iternext(o_in); } else { @@ -1274,9 +1282,9 @@ mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { return mp_call_method_n_kw(0, 0, dest); } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("object not an iterator")); + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else - mp_raise_TypeError_varg(translate("'%q' object is not an iterator"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not an iterator"), mp_obj_get_type_qstr(o_in)); #endif } @@ -1287,7 +1295,7 @@ mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { // 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 - mp_obj_type_t *type = mp_obj_get_type(o_in); + const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->iternext != NULL) { return type->iternext(o_in); } else { @@ -1310,9 +1318,9 @@ mp_obj_t mp_iternext(mp_obj_t o_in) { } } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(translate("object not an iterator")); + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else - mp_raise_TypeError_varg(translate("'%q' object is not an iterator"), + mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not an iterator"), mp_obj_get_type_qstr(o_in)); #endif } @@ -1322,7 +1330,7 @@ mp_obj_t mp_iternext(mp_obj_t o_in) { // TODO: Unclear what to do with StopIterarion exception here. 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)); - mp_obj_type_t *type = mp_obj_get_type(self_in); + 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); @@ -1388,7 +1396,7 @@ mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t th // 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, translate("generator raised StopIteration")); + *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); } @@ -1409,7 +1417,7 @@ mp_obj_t mp_make_raise_obj(mp_obj_t o) { 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, translate("exceptions must derive from BaseException")); + return mp_obj_new_exception_msg(&mp_type_TypeError, MP_ERROR_TEXT("exceptions must derive from BaseException")); } } @@ -1447,7 +1455,7 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { if (dest[1] != MP_OBJ_NULL) { // Hopefully we can't import bound method from an object - mp_raise_msg_varg(&mp_type_ImportError, translate("cannot import name %q"), name); + mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("cannot import name %q"), name); } if (dest[0] != MP_OBJ_NULL) { @@ -1518,7 +1526,7 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i 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, false); + 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) { @@ -1548,11 +1556,11 @@ 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, translate("memory allocation failed, heap 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, - translate("memory allocation failed, allocating %u bytes"), (uint)num_bytes); + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes); } NORETURN void mp_raise_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg) { diff --git a/py/runtime.h b/py/runtime.h index c20d311320..dcfd48e8ee 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_PY_RUNTIME_H #define MICROPY_INCLUDED_PY_RUNTIME_H +#include + #include "py/mpstate.h" #include "py/pystack.h" @@ -66,15 +68,14 @@ extern const byte mp_binary_op_method_name[]; void mp_init(void); void mp_deinit(void); -void mp_handle_pending(void); +void mp_keyboard_interrupt(void); +void mp_handle_pending(bool raise_exc); void mp_handle_pending_tail(mp_uint_t atomic_state); #if MICROPY_ENABLE_SCHEDULER void mp_sched_lock(void); void mp_sched_unlock(void); -static inline unsigned int mp_sched_num_pending(void) { - return MP_STATE_VM(sched_len); -} +#define mp_sched_num_pending() (MP_STATE_VM(sched_len)) bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); #endif @@ -161,6 +162,7 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level); mp_obj_t mp_import_from(mp_obj_t module, qstr name); void mp_import_all(mp_obj_t module); +#define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) NORETURN void mp_raise_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg); NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg); NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, ...); diff --git a/py/scheduler.c b/py/scheduler.c index 3140295e11..8329010417 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -28,39 +28,59 @@ #include "py/runtime.h" +#if MICROPY_KBD_EXCEPTION +// This function may be called asynchronously at any time so only do the bare minimum. +void MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(mp_keyboard_interrupt)(void) { + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)); + #if MICROPY_ENABLE_SCHEDULER + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } + #endif +} +#endif + #if MICROPY_ENABLE_SCHEDULER #define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1)) -static inline bool mp_sched_full(void) { +// 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() == MICROPY_SCHEDULER_DEPTH; -} - -static inline bool mp_sched_empty(void) { return mp_sched_num_pending() == 0; } // A variant of this is inlined in the VM at the pending exception check -void mp_handle_pending(void) { +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(); - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - if (obj != MP_OBJ_NULL) { - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - if (!mp_sched_num_pending()) { - MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + // 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_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(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); - nlr_raise(obj); } - mp_handle_pending_tail(atomic_state); } } -// This function should only be called be mp_sched_handle_pending, +// 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; @@ -88,6 +108,7 @@ void mp_sched_lock(void) { 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_VM(mp_pending_exception) != MP_OBJ_NULL || mp_sched_num_pending()) { @@ -99,7 +120,7 @@ void mp_sched_unlock(void) { MICROPY_END_ATOMIC_SECTION(atomic_state); } -bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg) { +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()) { @@ -121,11 +142,13 @@ bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg) { #else // MICROPY_ENABLE_SCHEDULER // A variant of this is inlined in the VM at the pending exception check -void mp_handle_pending(void) { +void mp_handle_pending(bool raise_exc) { if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { mp_obj_t obj = MP_STATE_VM(mp_pending_exception); MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); + if (raise_exc) { + nlr_raise(obj); + } } } diff --git a/py/scope.c b/py/scope.c index 1244240c8d..1b2cb9fe82 100644 --- a/py/scope.c +++ b/py/scope.c @@ -30,7 +30,7 @@ #if MICROPY_ENABLE_COMPILER -// these low numbered qstrs should fit in 8 bits +// 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_, @@ -41,6 +41,14 @@ STATIC const uint8_t scope_simple_name_table[] = { }; scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, 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; diff --git a/py/scope.h b/py/scope.h index 485a964abe..d9efd5711f 100644 --- a/py/scope.h +++ b/py/scope.h @@ -55,6 +55,7 @@ typedef struct _id_info_t { } id_info_t; #define SCOPE_IS_FUNC_LIKE(s) ((s) >= SCOPE_LAMBDA) +#define SCOPE_IS_COMP_LIKE(s) (SCOPE_LIST_COMP <= (s) && (s) <= SCOPE_GEN_EXPR) // scope is a "block" in Python parlance typedef enum { diff --git a/py/sequence.c b/py/sequence.c index 471325a593..274ac14b98 100644 --- a/py/sequence.c +++ b/py/sequence.c @@ -61,78 +61,20 @@ void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times #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_t ostart, ostop, ostep; - mp_int_t start, stop; - mp_obj_slice_get(slice, &ostart, &ostop, &ostep); + mp_obj_slice_indices(slice, len, indexes); - if (ostep != mp_const_none && ostep != MP_OBJ_NEW_SMALL_INT(1)) { - indexes->step = mp_obj_get_int(ostep); - if (indexes->step == 0) { - mp_raise_ValueError(translate("slice step cannot be zero")); - } - } else { - indexes->step = 1; - } - - if (ostart == mp_const_none) { - if (indexes->step > 0) { - start = 0; - } else { - start = len - 1; - } - } else { - start = mp_obj_get_int(ostart); - } - if (ostop == mp_const_none) { - if (indexes->step > 0) { - stop = len; - } else { - stop = 0; - } - } else { - stop = mp_obj_get_int(ostop); - if (stop >= 0 && indexes->step < 0) { - stop += 1; - } - } - - // Unlike subscription, out-of-bounds slice indexes are never error - if (start < 0) { - start = len + start; - if (start < 0) { - if (indexes->step < 0) { - start = -1; - } else { - start = 0; - } - } - } else if (indexes->step > 0 && (mp_uint_t)start > len) { - start = len; - } else if (indexes->step < 0 && (mp_uint_t)start >= len) { - start = len - 1; - } - if (stop < 0) { - stop = len + stop; - if (stop < 0) { - stop = -1; - } - if (indexes->step < 0) { - stop += 1; - } - } else if ((mp_uint_t)stop > len) { - stop = len; + // 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 && start > stop) { - stop = start; - } else if (indexes->step < 0 && start < stop) { - stop = start + 1; + 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; } - indexes->start = start; - indexes->stop = stop; - return indexes->step == 1; } @@ -256,7 +198,7 @@ bool mp_seq_cmp_objs(mp_uint_t op, const mp_obj_t *items1, size_t len1, const mp // 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) { - mp_obj_type_t *type = mp_obj_get_type(args[0]); + 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; @@ -275,7 +217,7 @@ mp_obj_t mp_seq_index_obj(const mp_obj_t *items, size_t len, size_t n_args, cons } } - mp_raise_ValueError(translate("object not in sequence")); + 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) { diff --git a/py/stream.c b/py/stream.c index 0b01c3d8c5..bf3bf07fcb 100644 --- a/py/stream.c +++ b/py/stream.c @@ -92,7 +92,7 @@ const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) { || ((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, translate("stream operation not supported")); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); } return stream_p; } @@ -529,14 +529,12 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_ioctl_obj, 2, 3, stream_ioctl); * POSIX-compatible software to work with MicroPython streams. */ -// errno-like variable. If any of the functions below returned with error -// status, this variable will contain error no. -int mp_stream_errno; +#include ssize_t mp_stream_posix_write(mp_obj_t stream, const void *buf, size_t len) { mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(stream); const mp_stream_p_t *stream_p = mp_get_stream(o); - mp_uint_t out_sz = stream_p->write(stream, buf, len, &mp_stream_errno); + mp_uint_t out_sz = stream_p->write(stream, buf, len, &errno); if (out_sz == MP_STREAM_ERROR) { return -1; } else { @@ -547,7 +545,7 @@ ssize_t mp_stream_posix_write(mp_obj_t stream, const void *buf, size_t len) { ssize_t mp_stream_posix_read(mp_obj_t stream, void *buf, size_t len) { mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(stream); const mp_stream_p_t *stream_p = mp_get_stream(o); - mp_uint_t out_sz = stream_p->read(stream, buf, len, &mp_stream_errno); + mp_uint_t out_sz = stream_p->read(stream, buf, len, &errno); if (out_sz == MP_STREAM_ERROR) { return -1; } else { @@ -561,7 +559,7 @@ off_t mp_stream_posix_lseek(mp_obj_t stream, off_t offset, int whence) { 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, &mp_stream_errno); + 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; } @@ -571,7 +569,7 @@ off_t mp_stream_posix_lseek(mp_obj_t stream, off_t offset, int whence) { int mp_stream_posix_fsync(mp_obj_t stream) { mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(stream); const mp_stream_p_t *stream_p = mp_get_stream(o); - mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_FLUSH, 0, &mp_stream_errno); + mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_FLUSH, 0, &errno); if (res == MP_STREAM_ERROR) { return -1; } diff --git a/py/stream.h b/py/stream.h index 4499b655a6..7b392a3558 100644 --- a/py/stream.h +++ b/py/stream.h @@ -123,6 +123,7 @@ void mp_stream_write_adaptor(void *self, const char *buf, size_t len); mp_obj_t mp_stream_flush(mp_obj_t self); #if MICROPY_STREAMS_POSIX_API +#include // Functions with POSIX-compatible signatures // "stream" is assumed to be a pointer to a concrete object with the stream protocol ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len); diff --git a/py/unicode.c b/py/unicode.c index 1e41821d51..4028af5ce0 100644 --- a/py/unicode.c +++ b/py/unicode.c @@ -142,6 +142,10 @@ 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; } diff --git a/py/vm.c b/py/vm.c index 6b698fe658..05d0744e37 100644 --- a/py/vm.c +++ b/py/vm.c @@ -38,6 +38,8 @@ #include "supervisor/linker.h" +// *FORMAT-OFF* + #if 0 #define TRACE(ip) printf("sp=%d ", (int)(sp - &code_state->state[0] + 1)); mp_bytecode_print2(ip, 1, code_state->fun_bc->const_table); #else @@ -66,7 +68,7 @@ ip += 2; #define DECODE_PTR \ DECODE_UINT; \ - void *ptr = (void *)(uintptr_t)code_state->fun_bc->const_table[unum] + void *ptr = (void*)(uintptr_t)code_state->fun_bc->const_table[unum] #define DECODE_OBJ \ DECODE_UINT; \ mp_obj_t obj = (mp_obj_t)code_state->fun_bc->const_table[unum] @@ -78,12 +80,12 @@ qst = (qst << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0) #define DECODE_PTR \ - ip = (byte *)MP_ALIGN(ip, sizeof(void *)); \ - void *ptr = *(void **)ip; \ - ip += sizeof(void *) + ip = (byte*)MP_ALIGN(ip, sizeof(void*)); \ + void *ptr = *(void**)ip; \ + ip += sizeof(void*) #define DECODE_OBJ \ - ip = (byte *)MP_ALIGN(ip, sizeof(mp_obj_t)); \ - mp_obj_t obj = *(mp_obj_t *)ip; \ + ip = (byte*)MP_ALIGN(ip, sizeof(mp_obj_t)); \ + mp_obj_t obj = *(mp_obj_t*)ip; \ ip += sizeof(mp_obj_t) #endif @@ -100,11 +102,11 @@ #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; \ + 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() \ @@ -112,60 +114,60 @@ 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; \ - } \ + 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) + 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) + 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) + 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) + 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) + 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() @@ -198,16 +200,16 @@ static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8 // 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 PLACE_IN_ITCM(mp_execute_bytecode)(mp_code_state_t * code_state, volatile mp_obj_t inject_exc) { +mp_vm_return_kind_t PLACE_IN_ITCM(mp_execute_bytecode)(mp_code_state_t *code_state, volatile mp_obj_t inject_exc) { #define SELECTIVE_EXC_IP (0) - #if SELECTIVE_EXC_IP +#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 +#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 +#endif +#if MICROPY_OPT_COMPUTED_GOTO #include "py/vmentrytable.h" #if MICROPY_OPT_COMPUTED_GOTO_SAVE_SPACE #define ONE_TRUE_DISPATCH() one_true_dispatch : do { \ @@ -228,12 +230,12 @@ mp_vm_return_kind_t PLACE_IN_ITCM(mp_execute_bytecode)(mp_code_state_t * code_st #define DISPATCH_WITH_PEND_EXC_CHECK() goto pending_exception_check #define ENTRY(op) entry_##op #define ENTRY_DEFAULT entry_default - #else +#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 +#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 @@ -241,16 +243,15 @@ mp_vm_return_kind_t PLACE_IN_ITCM(mp_execute_bytecode)(mp_code_state_t * code_st // 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: ; +#endif +FRAME_ENTER(); - #if MICROPY_STACKLESS -run_code_state_from_return:; - #endif - FRAME_SETUP(); +#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; @@ -258,7 +259,7 @@ run_code_state_from_return:; { 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); + exc_stack = (mp_exc_stack_t*)(code_state->state + n_state); } // variables that are visible to the exception handler (declared volatile) @@ -273,7 +274,7 @@ run_code_state_from_return:; // outer exception handling loop for (;;) { nlr_buf_t nlr; - outer_dispatch_loop: +outer_dispatch_loop: if (nlr_push(&nlr) == 0) { // local variables that are not visible to the exception handler const byte *ip = code_state->ip; @@ -295,29 +296,29 @@ run_code_state_from_return:; // loop to execute byte code for (;;) { - dispatch_loop: - #if MICROPY_OPT_COMPUTED_GOTO +dispatch_loop: +#if MICROPY_OPT_COMPUTED_GOTO ONE_TRUE_DISPATCH(); - #else +#else TRACE(ip); MARK_EXC_IP_GLOBAL(); TRACE_TICK(ip, sp, false); switch (*ip++) { - #endif +#endif - ENTRY(MP_BC_LOAD_CONST_FALSE) : + ENTRY(MP_BC_LOAD_CONST_FALSE): PUSH(mp_const_false); - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_LOAD_CONST_NONE) : + ENTRY(MP_BC_LOAD_CONST_NONE): PUSH(mp_const_none); - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_LOAD_CONST_TRUE) : + ENTRY(MP_BC_LOAD_CONST_TRUE): PUSH(mp_const_true); - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_LOAD_CONST_SMALL_INT) : { + ENTRY(MP_BC_LOAD_CONST_SMALL_INT): { mp_int_t num = 0; if ((ip[0] & 0x40) != 0) { // Number is negative @@ -330,30 +331,30 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_LOAD_CONST_STRING) : { + ENTRY(MP_BC_LOAD_CONST_STRING): { DECODE_QSTR; PUSH(MP_OBJ_NEW_QSTR(qst)); DISPATCH(); } - ENTRY(MP_BC_LOAD_CONST_OBJ) : { + ENTRY(MP_BC_LOAD_CONST_OBJ): { DECODE_OBJ; PUSH(obj); DISPATCH(); } - ENTRY(MP_BC_LOAD_NULL) : + ENTRY(MP_BC_LOAD_NULL): PUSH(MP_OBJ_NULL); - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_LOAD_FAST_N) : { + ENTRY(MP_BC_LOAD_FAST_N): { DECODE_UINT; obj_shared = fastn[-unum]; - load_check: + load_check: if (obj_shared == MP_OBJ_NULL) { - local_name_error: { + local_name_error: { MARK_EXC_IP_SELECTIVE(); - mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NameError, translate("local variable referenced before assignment")); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NameError, MP_ERROR_TEXT("local variable referenced before assignment")); RAISE(obj); } } @@ -361,24 +362,24 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_LOAD_DEREF) : { + ENTRY(MP_BC_LOAD_DEREF): { DECODE_UINT; obj_shared = mp_obj_cell_get(fastn[-unum]); goto load_check; } #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE - ENTRY(MP_BC_LOAD_NAME) : { + ENTRY(MP_BC_LOAD_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); DISPATCH(); } #else - ENTRY(MP_BC_LOAD_NAME) : { + ENTRY(MP_BC_LOAD_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_map_elem_t *elem = mp_map_cached_lookup(&mp_locals_get()->map, qst, (uint8_t *)ip); + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_locals_get()->map, qst, (uint8_t*)ip); mp_obj_t obj; if (elem != NULL) { obj = elem->value; @@ -392,17 +393,17 @@ run_code_state_from_return:; #endif #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE - ENTRY(MP_BC_LOAD_GLOBAL) : { + ENTRY(MP_BC_LOAD_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_global(qst)); DISPATCH(); } #else - ENTRY(MP_BC_LOAD_GLOBAL) : { + ENTRY(MP_BC_LOAD_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_map_elem_t *elem = mp_map_cached_lookup(&mp_globals_get()->map, qst, (uint8_t *)ip); + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_globals_get()->map, qst, (uint8_t*)ip); mp_obj_t obj; if (elem != NULL) { obj = elem->value; @@ -416,7 +417,7 @@ run_code_state_from_return:; #endif #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE - ENTRY(MP_BC_LOAD_ATTR) : { + ENTRY(MP_BC_LOAD_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -424,7 +425,7 @@ run_code_state_from_return:; DISPATCH(); } #else - ENTRY(MP_BC_LOAD_ATTR) : { + ENTRY(MP_BC_LOAD_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -432,7 +433,7 @@ run_code_state_from_return:; 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_cached_lookup(&self->members, qst, (uint8_t *)ip); + elem = mp_map_cached_lookup(&self->members, qst, (uint8_t*)ip); } mp_obj_t obj; if (elem != NULL) { @@ -446,7 +447,7 @@ run_code_state_from_return:; } #endif - ENTRY(MP_BC_LOAD_METHOD) : { + ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_load_method(*sp, qst, sp); @@ -454,7 +455,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_LOAD_SUPER_METHOD) : { + ENTRY(MP_BC_LOAD_SUPER_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; sp -= 1; @@ -462,38 +463,38 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_LOAD_BUILD_CLASS) : + ENTRY(MP_BC_LOAD_BUILD_CLASS): MARK_EXC_IP_SELECTIVE(); - PUSH(mp_load_build_class()); - DISPATCH(); + PUSH(mp_load_build_class()); + DISPATCH(); - ENTRY(MP_BC_LOAD_SUBSCR) : { + 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) : { + ENTRY(MP_BC_STORE_FAST_N): { DECODE_UINT; fastn[-unum] = POP(); DISPATCH(); } - ENTRY(MP_BC_STORE_DEREF) : { + ENTRY(MP_BC_STORE_DEREF): { DECODE_UINT; mp_obj_cell_set(fastn[-unum], POP()); DISPATCH(); } - ENTRY(MP_BC_STORE_NAME) : { + ENTRY(MP_BC_STORE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_name(qst, POP()); DISPATCH(); } - ENTRY(MP_BC_STORE_GLOBAL) : { + ENTRY(MP_BC_STORE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_global(qst, POP()); @@ -501,7 +502,7 @@ run_code_state_from_return:; } #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE - ENTRY(MP_BC_STORE_ATTR) : { + ENTRY(MP_BC_STORE_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -515,7 +516,7 @@ run_code_state_from_return:; // self->members then it can't be a property or have descriptors. A // consequence of this is that we can't use MP_MAP_LOOKUP_ADD_IF_NOT_FOUND // in the fast-path below, because that store could override a property. - ENTRY(MP_BC_STORE_ATTR) : { + ENTRY(MP_BC_STORE_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -523,7 +524,7 @@ run_code_state_from_return:; mp_obj_t top = TOP(); if (mp_obj_is_instance_type(mp_obj_get_type(top)) && sp[-1] != MP_OBJ_NULL) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); - elem = mp_map_cached_lookup(&self->members, qst, (uint8_t *)ip); + elem = mp_map_cached_lookup(&self->members, qst, (uint8_t*)ip); } if (elem != NULL) { elem->value = sp[-1]; @@ -536,13 +537,13 @@ run_code_state_from_return:; } #endif - ENTRY(MP_BC_STORE_SUBSCR) : + ENTRY(MP_BC_STORE_SUBSCR): MARK_EXC_IP_SELECTIVE(); - mp_obj_subscr(sp[-1], sp[0], sp[-2]); - sp -= 3; - DISPATCH(); + mp_obj_subscr(sp[-1], sp[0], sp[-2]); + sp -= 3; + DISPATCH(); - ENTRY(MP_BC_DELETE_FAST) : { + ENTRY(MP_BC_DELETE_FAST): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; if (fastn[-unum] == MP_OBJ_NULL) { @@ -552,7 +553,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_DELETE_DEREF) : { + ENTRY(MP_BC_DELETE_DEREF): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; if (mp_obj_cell_get(fastn[-unum]) == MP_OBJ_NULL) { @@ -562,44 +563,44 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_DELETE_NAME) : { + ENTRY(MP_BC_DELETE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_delete_name(qst); DISPATCH(); } - ENTRY(MP_BC_DELETE_GLOBAL) : { + ENTRY(MP_BC_DELETE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_delete_global(qst); DISPATCH(); } - ENTRY(MP_BC_DUP_TOP) : { + ENTRY(MP_BC_DUP_TOP): { mp_obj_t top = TOP(); PUSH(top); DISPATCH(); } - ENTRY(MP_BC_DUP_TOP_TWO) : + ENTRY(MP_BC_DUP_TOP_TWO): sp += 2; - sp[0] = sp[-2]; - sp[-1] = sp[-3]; - DISPATCH(); + sp[0] = sp[-2]; + sp[-1] = sp[-3]; + DISPATCH(); - ENTRY(MP_BC_POP_TOP) : + ENTRY(MP_BC_POP_TOP): sp -= 1; - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_ROT_TWO) : { + ENTRY(MP_BC_ROT_TWO): { mp_obj_t top = sp[0]; sp[0] = sp[-1]; sp[-1] = top; DISPATCH(); } - ENTRY(MP_BC_ROT_THREE) : { + ENTRY(MP_BC_ROT_THREE): { mp_obj_t top = sp[0]; sp[0] = sp[-1]; sp[-1] = sp[-2]; @@ -607,13 +608,13 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_JUMP) : { + ENTRY(MP_BC_JUMP): { DECODE_SLABEL; ip += slab; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_POP_JUMP_IF_TRUE) : { + ENTRY(MP_BC_POP_JUMP_IF_TRUE): { DECODE_SLABEL; if (mp_obj_is_true(POP())) { ip += slab; @@ -621,7 +622,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_POP_JUMP_IF_FALSE) : { + ENTRY(MP_BC_POP_JUMP_IF_FALSE): { DECODE_SLABEL; if (!mp_obj_is_true(POP())) { ip += slab; @@ -629,7 +630,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP) : { + ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP): { DECODE_SLABEL; if (mp_obj_is_true(TOP())) { ip += slab; @@ -639,7 +640,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP) : { + ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP): { DECODE_SLABEL; if (mp_obj_is_true(TOP())) { sp--; @@ -649,7 +650,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_SETUP_WITH) : { + ENTRY(MP_BC_SETUP_WITH): { MARK_EXC_IP_SELECTIVE(); // stack: (..., ctx_mgr) mp_obj_t obj = TOP(); @@ -663,7 +664,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_WITH_CLEANUP) : { + 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__, @@ -716,12 +717,12 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_UNWIND_JUMP) : { + 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:; +unwind_jump:; mp_uint_t unum = (mp_uint_t)POP(); // get number of exception handlers to unwind while ((unum & 0x7f) > 0) { unum -= 1; @@ -751,7 +752,7 @@ run_code_state_from_return:; } POP_EXC_BLOCK(); } - ip = (const byte *)MP_OBJ_TO_PTR(POP()); // pop destination ip for jump + 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; @@ -759,8 +760,8 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_SETUP_EXCEPT) : - ENTRY(MP_BC_SETUP_FINALLY) : { + 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); @@ -770,47 +771,47 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_END_FINALLY) : + 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; + // 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 { - // 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; + assert(mp_obj_is_exception_instance(TOP())); + RAISE(TOP()); } - } else { - assert(mp_obj_is_exception_instance(TOP())); - RAISE(TOP()); - } - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_GET_ITER) : + ENTRY(MP_BC_GET_ITER): MARK_EXC_IP_SELECTIVE(); - SET_TOP(mp_getiter(TOP(), NULL)); - DISPATCH(); + 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) : { + 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; + 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)) { @@ -821,7 +822,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_FOR_ITER) : { + ENTRY(MP_BC_FOR_ITER): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward @@ -848,7 +849,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_POP_EXCEPT_JUMP) : { + ENTRY(MP_BC_POP_EXCEPT_JUMP): { assert(exc_sp >= exc_stack); POP_EXC_BLOCK(); DECODE_ULABEL; @@ -856,7 +857,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_BUILD_TUPLE) : { + ENTRY(MP_BC_BUILD_TUPLE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; sp -= unum - 1; @@ -864,7 +865,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_BUILD_LIST) : { + ENTRY(MP_BC_BUILD_LIST): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; sp -= unum - 1; @@ -872,31 +873,31 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_BUILD_MAP) : { + ENTRY(MP_BC_BUILD_MAP): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; PUSH(mp_obj_new_dict(unum)); DISPATCH(); } - ENTRY(MP_BC_STORE_MAP) : + ENTRY(MP_BC_STORE_MAP): MARK_EXC_IP_SELECTIVE(); - sp -= 2; - mp_obj_dict_store(sp[0], sp[2], sp[1]); - DISPATCH(); + sp -= 2; + mp_obj_dict_store(sp[0], sp[2], sp[1]); + DISPATCH(); - #if MICROPY_PY_BUILTINS_SET - ENTRY(MP_BC_BUILD_SET) : { +#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 +#endif - #if MICROPY_PY_BUILTINS_SLICE - ENTRY(MP_BC_BUILD_SLICE) : { +#if MICROPY_PY_BUILTINS_SLICE + ENTRY(MP_BC_BUILD_SLICE): { MARK_EXC_IP_SELECTIVE(); mp_obj_t step = mp_const_none; if (*ip++ == 3) { @@ -908,9 +909,9 @@ run_code_state_from_return:; SET_TOP(mp_obj_new_slice(start, stop, step)); DISPATCH(); } - #endif +#endif - ENTRY(MP_BC_STORE_COMP) : { + ENTRY(MP_BC_STORE_COMP): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_obj_t obj = sp[-(unum >> 2)]; @@ -929,7 +930,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_UNPACK_SEQUENCE) : { + ENTRY(MP_BC_UNPACK_SEQUENCE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_unpack_sequence(sp[0], unum, sp); @@ -937,7 +938,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_UNPACK_EX) : { + ENTRY(MP_BC_UNPACK_EX): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; mp_unpack_ex(sp[0], unum, sp); @@ -945,13 +946,13 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_MAKE_FUNCTION) : { + ENTRY(MP_BC_MAKE_FUNCTION): { DECODE_PTR; PUSH(mp_make_function_from_raw_code(ptr, MP_OBJ_NULL, MP_OBJ_NULL)); DISPATCH(); } - ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS) : { + ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS): { DECODE_PTR; // Stack layout: def_tuple def_dict <- TOS mp_obj_t def_dict = POP(); @@ -959,7 +960,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_MAKE_CLOSURE) : { + ENTRY(MP_BC_MAKE_CLOSURE): { DECODE_PTR; size_t n_closed_over = *ip++; // Stack layout: closed_overs <- TOS @@ -968,7 +969,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_MAKE_CLOSURE_DEFARGS) : { + ENTRY(MP_BC_MAKE_CLOSURE_DEFARGS): { DECODE_PTR; size_t n_closed_over = *ip++; // Stack layout: def_tuple def_dict closed_overs <- TOS @@ -977,7 +978,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_CALL_FUNCTION) : { + ENTRY(MP_BC_CALL_FUNCTION): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; @@ -1012,7 +1013,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_CALL_FUNCTION_VAR_KW) : { + ENTRY(MP_BC_CALL_FUNCTION_VAR_KW): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; @@ -1058,7 +1059,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_CALL_METHOD) : { + ENTRY(MP_BC_CALL_METHOD): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; @@ -1097,7 +1098,7 @@ run_code_state_from_return:; DISPATCH_WITH_PEND_EXC_CHECK(); } - ENTRY(MP_BC_CALL_METHOD_VAR_KW) : { + ENTRY(MP_BC_CALL_METHOD_VAR_KW): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; @@ -1143,65 +1144,65 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_RETURN_VALUE) : + 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); +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; } - 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; + FRAME_LEAVE(); + return MP_VM_RETURN_NORMAL; - ENTRY(MP_BC_RAISE_LAST) : { + 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; @@ -1212,18 +1213,18 @@ run_code_state_from_return:; } } if (obj == MP_OBJ_NULL) { - obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, translate("no active exception to reraise")); + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("no active exception to reraise")); } RAISE(obj); } - ENTRY(MP_BC_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) : { + ENTRY(MP_BC_RAISE_FROM): { MARK_EXC_IP_SELECTIVE(); mp_warning(NULL, "exception chaining not supported"); sp--; // ignore (pop) "from" argument @@ -1231,21 +1232,20 @@ run_code_state_from_return:; RAISE(obj); } - ENTRY(MP_BC_YIELD_VALUE) : - yield: + 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; + 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) : { + 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_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); \ -} +#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; @@ -1287,7 +1287,7 @@ run_code_state_from_return:; } } - ENTRY(MP_BC_IMPORT_NAME) : { + ENTRY(MP_BC_IMPORT_NAME): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -1296,7 +1296,7 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_IMPORT_FROM) : { + ENTRY(MP_BC_IMPORT_FROM): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; @@ -1305,30 +1305,30 @@ run_code_state_from_return:; DISPATCH(); } - ENTRY(MP_BC_IMPORT_STAR) : + ENTRY(MP_BC_IMPORT_STAR): MARK_EXC_IP_SELECTIVE(); - mp_import_all(POP()); - DISPATCH(); + mp_import_all(POP()); + DISPATCH(); - #if MICROPY_OPT_COMPUTED_GOTO - ENTRY(MP_BC_LOAD_CONST_SMALL_INT_MULTI) : +#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(); + DISPATCH(); - ENTRY(MP_BC_LOAD_FAST_MULTI) : + ENTRY(MP_BC_LOAD_FAST_MULTI): obj_shared = fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)ip[-1]]; - goto load_check; + goto load_check; - ENTRY(MP_BC_STORE_FAST_MULTI) : + ENTRY(MP_BC_STORE_FAST_MULTI): fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)ip[-1]] = POP(); - DISPATCH(); + DISPATCH(); - ENTRY(MP_BC_UNARY_OP_MULTI) : + ENTRY(MP_BC_UNARY_OP_MULTI): MARK_EXC_IP_SELECTIVE(); - SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); - DISPATCH(); + SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); + DISPATCH(); - ENTRY(MP_BC_BINARY_OP_MULTI) : { + ENTRY(MP_BC_BINARY_OP_MULTI): { MARK_EXC_IP_SELECTIVE(); mp_obj_t rhs = POP(); mp_obj_t lhs = TOP(); @@ -1336,60 +1336,65 @@ run_code_state_from_return:; 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 + 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, translate("opcode")); + 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 +#if !MICROPY_OPT_COMPUTED_GOTO + } // switch +#endif - pending_exception_check: +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) { - MARK_EXC_IP_SELECTIVE(); mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - if (obj != MP_OBJ_NULL) { - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - if (!mp_sched_num_pending()) { - MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + // 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_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(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); - RAISE(obj); } - mp_handle_pending_tail(atomic_state); } #else // This is an inlined variant of mp_handle_pending @@ -1423,7 +1428,7 @@ run_code_state_from_return:; } // for loop } else { - exception_handler: +exception_handler: // exception occurred #if MICROPY_PY_SYS_EXC_INFO @@ -1435,7 +1440,7 @@ run_code_state_from_return:; 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 (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) { @@ -1457,14 +1462,14 @@ run_code_state_from_return:; #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))) { + 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 +#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 @@ -1532,7 +1537,7 @@ run_code_state_from_return:; 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); + 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; diff --git a/py/vmentrytable.h b/py/vmentrytable.h index 01822f203e..8c57b38b65 100644 --- a/py/vmentrytable.h +++ b/py/vmentrytable.h @@ -24,7 +24,9 @@ * THE SOFTWARE. */ -#ifdef __clang__ +// *FORMAT-OFF* + +#if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winitializer-overrides" #endif // __clang__ diff --git a/shared-bindings/microcontroller/__init__.c b/shared-bindings/microcontroller/__init__.c index fcd19c10b7..a500239ce3 100644 --- a/shared-bindings/microcontroller/__init__.c +++ b/shared-bindings/microcontroller/__init__.c @@ -44,7 +44,7 @@ //| """Pin references and cpu functionality //| //| The `microcontroller` module defines the pins from the perspective of the -//| microcontroller. See `board` for board-specific pin mappings.""" +//| microcontroller. See :py:mod:`board` for board-specific pin mappings.""" //| //| from nvm import ByteArray //| from watchdog import WatchDogTimer diff --git a/shared-bindings/support_matrix.rst b/shared-bindings/support_matrix.rst index 2d2172122a..2385183c98 100644 --- a/shared-bindings/support_matrix.rst +++ b/shared-bindings/support_matrix.rst @@ -21,6 +21,6 @@ capable board. {% for key, value in support_matrix|dictsort %} {{ '.. _' ~ key|replace(" ", "-") ~ ':' }} * - {{ key }} - - {{ '`' ~ value|join("`, `") ~ '`' }} + - {{ ':py:mod:`' ~ value|join("`, :py:mod:`") ~ '`' }} {% endfor %} diff --git a/supervisor/shared/translate.h b/supervisor/shared/translate.h index 2c440acc14..f6e3be8cfb 100644 --- a/supervisor/shared/translate.h +++ b/supervisor/shared/translate.h @@ -79,4 +79,8 @@ void serial_write_compressed(const compressed_string_t *compressed); char *decompress(const compressed_string_t *compressed, char *decompressed); uint16_t decompress_length(const compressed_string_t *compressed); + +// Map MicroPython's error messages to our translations. +#define MP_ERROR_TEXT(x) translate(x) + #endif // MICROPY_INCLUDED_SUPERVISOR_TRANSLATE_H diff --git a/tests/README b/tests/README index 3458f36a80..f2cd89bb93 100644 --- a/tests/README +++ b/tests/README @@ -13,6 +13,15 @@ condition a test. The run-tests script uses small scripts in the feature_check directory to check whether each such feature is present, and skips the relevant tests if not. +Tests are generally verified by running the test both in MicroPython and +in CPython and comparing the outputs. If the output differs the test fails +and the outputs are saved in a .out and a .exp file respectively. +For tests that cannot be run in CPython, for example because they use +the machine module, a .exp file can be provided next to the test's .py +file. A convenient way to generate that is to run the test, let it fail +(because CPython cannot run it) and then copy the .out file (but not +before checking it manually!) + When creating new tests, anything that relies on float support should go in the float/ subdirectory. Anything that relies on import x, where x is not a built-in module, should go in the import/ subdirectory. diff --git a/tests/basics/annotate_var.py b/tests/basics/annotate_var.py new file mode 100644 index 0000000000..3f767e4a73 --- /dev/null +++ b/tests/basics/annotate_var.py @@ -0,0 +1,25 @@ +# test PEP 526, varible annotations + +x: int +print("x" in globals()) + +x: int = 1 +print(x) + +t: tuple = 1, 2 +print(t) + +# a pure annotation in a function makes that variable local +def f(): + x: int + try: + print(x) + except NameError: + print("NameError") +f() + +# here, "x" should remain a global +def f(): + x.y: int + print(x) +f() diff --git a/tests/basics/annotate_var.py.exp b/tests/basics/annotate_var.py.exp new file mode 100644 index 0000000000..9b6536e966 --- /dev/null +++ b/tests/basics/annotate_var.py.exp @@ -0,0 +1,5 @@ +False +1 +(1, 2) +NameError +1 diff --git a/tests/basics/array1.py b/tests/basics/array1.py index 3370c240d3..5b3f475786 100644 --- a/tests/basics/array1.py +++ b/tests/basics/array1.py @@ -37,3 +37,23 @@ try: array.array('X') except ValueError: print("ValueError") + +# equality (CPython requires both sides are array) +print(bytes(array.array('b', [0x61, 0x62, 0x63])) == b'abc') +print(array.array('b', [0x61, 0x62, 0x63]) == b'abc') +print(array.array('b', [0x61, 0x62, 0x63]) != b'abc') +print(array.array('b', [0x61, 0x62, 0x63]) == b'xyz') +print(array.array('b', [0x61, 0x62, 0x63]) != b'xyz') +print(b'abc' == array.array('b', [0x61, 0x62, 0x63])) +print(b'abc' != array.array('b', [0x61, 0x62, 0x63])) +print(b'xyz' == array.array('b', [0x61, 0x62, 0x63])) +print(b'xyz' != array.array('b', [0x61, 0x62, 0x63])) + +class X(array.array): + pass + +print(bytes(X('b', [0x61, 0x62, 0x63])) == b'abc') +print(X('b', [0x61, 0x62, 0x63]) == b'abc') +print(X('b', [0x61, 0x62, 0x63]) != b'abc') +print(X('b', [0x61, 0x62, 0x63]) == array.array('b', [0x61, 0x62, 0x63])) +print(X('b', [0x61, 0x62, 0x63]) != array.array('b', [0x61, 0x62, 0x63])) diff --git a/tests/basics/array_micropython.py b/tests/basics/array_micropython.py index 125c49349e..6b3dc7a93b 100644 --- a/tests/basics/array_micropython.py +++ b/tests/basics/array_micropython.py @@ -8,12 +8,6 @@ except ImportError: print("SKIP") raise SystemExit -try: - array.array('O') -except ValueError: - print("SKIP") - raise SystemExit - # arrays of objects a = array.array('O') a.append(1) diff --git a/tests/basics/assign_expr.py b/tests/basics/assign_expr.py new file mode 100644 index 0000000000..f243905dc2 --- /dev/null +++ b/tests/basics/assign_expr.py @@ -0,0 +1,29 @@ +(x := 4) +print(x) + +if x := 2: + print(True) +print(x) + +print(4, x := 5) +print(x) + +x = 1 +print(x, x := 5, x) +print(x) + + +def foo(): + print("any", any((hit := i) % 5 == 3 and (hit % 2) == 0 for i in range(10))) + return hit + + +hit = 123 +print(foo()) +print(hit) # shouldn't be changed by foo + +print("any", any((hit := i) % 5 == 3 and (hit % 2) == 0 for i in range(10))) +print(hit) # should be changed by above + +print([((m := k + 1), k * m) for k in range(4)]) +print(m) diff --git a/tests/basics/assign_expr.py.exp b/tests/basics/assign_expr.py.exp new file mode 100644 index 0000000000..e38e1ae7a6 --- /dev/null +++ b/tests/basics/assign_expr.py.exp @@ -0,0 +1,14 @@ +4 +True +2 +4 5 +5 +1 5 5 +5 +any True +8 +123 +any True +8 +[(1, 0), (2, 2), (3, 6), (4, 12)] +4 diff --git a/tests/basics/assign_expr_syntaxerror.py b/tests/basics/assign_expr_syntaxerror.py new file mode 100644 index 0000000000..11b3501292 --- /dev/null +++ b/tests/basics/assign_expr_syntaxerror.py @@ -0,0 +1,16 @@ +# test SyntaxError with := operator + +def test(code): + try: + print(eval(code)) + except SyntaxError: + print('SyntaxError') + +test("x := 1") +test("((x, y) := 1)") + +# these are currently all allowed in MicroPython, but not in CPython +test("([i := i + 1 for i in range(4)])") +test("([i := -1 for i, j in [(1, 2)]])") +test("([[(i := j) for i in range(2)] for j in range(2)])") +test("([[(j := i) for i in range(2)] for j in range(2)])") diff --git a/tests/basics/assign_expr_syntaxerror.py.exp b/tests/basics/assign_expr_syntaxerror.py.exp new file mode 100644 index 0000000000..2ba7d7df86 --- /dev/null +++ b/tests/basics/assign_expr_syntaxerror.py.exp @@ -0,0 +1,6 @@ +SyntaxError +SyntaxError +[1, 2, 3, 4] +[-1] +[[0, 0], [1, 1]] +[[0, 1], [0, 1]] diff --git a/tests/basics/bool1.py b/tests/basics/bool1.py index 35d9ed8ccc..daabfb17d8 100644 --- a/tests/basics/bool1.py +++ b/tests/basics/bool1.py @@ -10,6 +10,30 @@ print(False or True) print(+True) print(-True) +# comparison with itself +print(False == False) +print(False == True) +print(True == False) +print(True == True) +print(False != False) +print(False != True) +print(True != False) +print(True != True) + +# comparison with integers +print(False == 0) +print(0 == False) +print(True == 1) +print(1 == True) +print(True == 2) +print(2 == True) +print(False != 0) +print(0 != False) +print(True != 1) +print(1 != True) +print(True != 2) +print(2 != True) + # unsupported unary op try: len(False) diff --git a/tests/basics/builtin_compile.py b/tests/basics/builtin_compile.py index bf272aca15..a2f2cbe550 100644 --- a/tests/basics/builtin_compile.py +++ b/tests/basics/builtin_compile.py @@ -1,11 +1,10 @@ # test compile builtin -def have_compile(): - try: - compile - return True - except NameError: - return False +try: + compile +except NameError: + print("SKIP") + raise SystemExit def test(): global x @@ -26,8 +25,9 @@ def test(): exec(c, {}, {"x":3}) # single/eval mode - exec(compile('print(1 + 1)', 'file', 'single')) - print(eval(compile('1 + 1', 'file', 'eval'))) + exec(compile("if 1: 10 + 1\n", "file", "single")) + exec(compile("print(10 + 2)", "file", "single")) + print(eval(compile("10 + 3", "file", "eval"))) # bad mode try: @@ -42,8 +42,4 @@ def test(): print("NameError") print(x) # check 'x' still exists as a global -if have_compile(): - test() -else: - print("SKIP") - raise SystemExit +test() diff --git a/tests/basics/builtin_ellipsis.py b/tests/basics/builtin_ellipsis.py index d88647a89a..d66e86de4a 100644 --- a/tests/basics/builtin_ellipsis.py +++ b/tests/basics/builtin_ellipsis.py @@ -4,3 +4,6 @@ print(...) print(Ellipsis) print(... == Ellipsis) + +# Test that Ellipsis can be hashed +print(type(hash(Ellipsis))) diff --git a/tests/basics/builtin_eval_buffer.py b/tests/basics/builtin_eval_buffer.py new file mode 100644 index 0000000000..9eaf28204d --- /dev/null +++ b/tests/basics/builtin_eval_buffer.py @@ -0,0 +1,12 @@ +# test builtin eval with a buffer (bytearray/memoryview) input + +try: + eval + bytearray + memoryview +except: + print("SKIP") + raise SystemExit + +print(eval(bytearray(b'1 + 1'))) +print(eval(memoryview(b'2 + 2'))) diff --git a/tests/basics/builtin_exec_buffer.py b/tests/basics/builtin_exec_buffer.py new file mode 100644 index 0000000000..a875cfb1fa --- /dev/null +++ b/tests/basics/builtin_exec_buffer.py @@ -0,0 +1,12 @@ +# test builtin exec with a buffer (bytearray/memoryview) input + +try: + exec + bytearray + memoryview +except: + print("SKIP") + raise SystemExit + +exec(bytearray(b'print(1)')) +exec(memoryview(b'print(2)')) diff --git a/tests/basics/builtin_getattr.py b/tests/basics/builtin_getattr.py index 59cb7e7f7a..afa4ab3293 100644 --- a/tests/basics/builtin_getattr.py +++ b/tests/basics/builtin_getattr.py @@ -16,3 +16,15 @@ print(getattr(a, "meth")(5)) print(getattr(a, "_none_such", 123)) print(getattr(list, "foo", 456)) print(getattr(a, "va" + "r2")) + +# test a class that defines __getattr__ and may raise AttributeError +class B: + def __getattr__(self, attr): + if attr == "a": + return attr + else: + raise AttributeError +b = B() +print(getattr(b, "a")) +print(getattr(b, "a", "default")) +print(getattr(b, "b", "default")) diff --git a/tests/basics/class_bind_self.py b/tests/basics/class_bind_self.py index 16e4f5ac95..813f876446 100644 --- a/tests/basics/class_bind_self.py +++ b/tests/basics/class_bind_self.py @@ -40,11 +40,18 @@ print(c.f2(2)) print(c.f3()) print(next(c.f4(4))) print(c.f5(5)) -#print(c.f6(-6)) not working in uPy +print(c.f6(-6)) print(c.f7(7)) print(c.f8(8)) print(c.f9(9)) +# test calling the functions accessed via the class itself +print(C.f5(10)) +print(C.f6(-11)) +print(C.f7(12)) +print(C.f8(13)) +print(C.f9(14)) + # not working in uPy #class C(list): # # this acts like a method and binds self diff --git a/tests/basics/class_delattr_setattr.py b/tests/basics/class_delattr_setattr.py index 190b4875b9..3389c091ab 100644 --- a/tests/basics/class_delattr_setattr.py +++ b/tests/basics/class_delattr_setattr.py @@ -60,3 +60,54 @@ try: print(a.a) except AttributeError: print("AttributeError") + +# test object.__setattr__ +class C: + def __init__(self): + pass + + def __setattr__(self, attr, value): + print(attr, "=", value) + + def __delattr__(self, attr): + print("del", attr) + +c = C() +c.a = 5 +try: + print(c.a) +except AttributeError: + print("AttributeError") + +object.__setattr__(c, "a", 5) +super(C, c).__setattr__("b", 6) +print(c.a) +print(c.b) + +try: + # attribute name must be string + object.__setattr__(c, 5, 5) +except TypeError: + print("TypeError") + + +# test object.__delattr__ +del c.a +print(c.a) + +object.__delattr__(c, "a") +try: + print(c.a) +except AttributeError: + print("AttributeError") + +super(C, c).__delattr__("b") +try: + print(c.b) +except AttributeError: + print("AttributeError") + +try: + object.__delattr__(c, "c") +except AttributeError: + print("AttributeError") diff --git a/tests/basics/class_dict.py b/tests/basics/class_dict.py new file mode 100644 index 0000000000..f80ded678b --- /dev/null +++ b/tests/basics/class_dict.py @@ -0,0 +1,19 @@ +# test __dict__ attribute of a class + +if not hasattr(int, "__dict__"): + print("SKIP") + raise SystemExit + + +# dict of a built-in type +print("from_bytes" in int.__dict__) + + +# dict of a user class +class Foo: + a = 1 + b = "bar" + + +d = Foo.__dict__ +print(d["a"], d["b"]) diff --git a/tests/basics/class_notimpl.py b/tests/basics/class_notimpl.py index 308075f92f..58e790716b 100644 --- a/tests/basics/class_notimpl.py +++ b/tests/basics/class_notimpl.py @@ -48,3 +48,6 @@ except TypeError: # NotImplemented isn't handled specially in unary methods print(-c) + +# Test that NotImplemented can be hashed +print(type(hash(NotImplemented))) diff --git a/tests/basics/class_ordereddict.py b/tests/basics/class_ordereddict.py new file mode 100644 index 0000000000..4dd25eb6e1 --- /dev/null +++ b/tests/basics/class_ordereddict.py @@ -0,0 +1,18 @@ +# test using an OrderedDict as the locals to construct a class + +try: + from ucollections import OrderedDict +except ImportError: + try: + from collections import OrderedDict + except ImportError: + print("SKIP") + raise SystemExit + +if not hasattr(int, "__dict__"): + print("SKIP") + raise SystemExit + + +A = type("A", (), OrderedDict(a=1, b=2, c=3, d=4, e=5)) +print([k for k in A.__dict__.keys() if not k.startswith("_")]) diff --git a/tests/basics/class_ordereddict.py.exp b/tests/basics/class_ordereddict.py.exp new file mode 100644 index 0000000000..b723e32751 --- /dev/null +++ b/tests/basics/class_ordereddict.py.exp @@ -0,0 +1 @@ +['a', 'b', 'c', 'd', 'e'] diff --git a/tests/basics/dict1.py b/tests/basics/dict1.py index 0cec51173a..cd793c9ed2 100644 --- a/tests/basics/dict1.py +++ b/tests/basics/dict1.py @@ -23,6 +23,34 @@ print({} == {1:1}) # equality operator on dicts of same size but with different keys print({1:1} == {2:1}) +# 0 replacing False's item +d = {} +d[False] = 'false' +d[0] = 'zero' +print(d) + +# False replacing 0's item +d = {} +d[0] = 'zero' +d[False] = 'false' +print(d) + +# 1 replacing True's item +d = {} +d[True] = 'true' +d[1] = 'one' +print(d) + +# True replacing 1's item +d = {} +d[1] = 'one' +d[True] = 'true' +print(d) + +# mixed bools and integers +d = {False:10, True:11, 2:12} +print(d[0], d[1], d[2]) + # value not found try: {}[0] diff --git a/tests/basics/dict_pop.py b/tests/basics/dict_pop.py index 602560ce9d..454eb07cc0 100644 --- a/tests/basics/dict_pop.py +++ b/tests/basics/dict_pop.py @@ -5,8 +5,8 @@ print(d.pop(1, 42), d) print(d.pop(1, 42), d) print(d.pop(1, None), d) try: - print(d.pop(1), "!!!",) + print(d.pop(1), "!!!") except KeyError: print("Raised KeyError") else: - print("Did not rise KeyError!") + print("Did not raise KeyError!") diff --git a/tests/basics/gen_yield_from_pending.py b/tests/basics/gen_yield_from_pending.py new file mode 100644 index 0000000000..eb88ee6ca7 --- /dev/null +++ b/tests/basics/gen_yield_from_pending.py @@ -0,0 +1,23 @@ +# Tests that the pending exception state is managed correctly +# (previously failed on native emitter). + +def noop_task(): + print('noop task') + yield 1 + +def raise_task(): + print('raise task') + yield 2 + print('raising') + raise Exception + +def main(): + try: + yield from raise_task() + except: + print('main exception') + + yield from noop_task() + +for z in main(): + print('outer iter', z) diff --git a/tests/basics/generator_throw_nested.py b/tests/basics/generator_throw_nested.py new file mode 100644 index 0000000000..5ef1668788 --- /dev/null +++ b/tests/basics/generator_throw_nested.py @@ -0,0 +1,33 @@ +# Tests that the correct nested exception handler is used when +# throwing into a generator (previously failed on native emitter). + +def gen(): + try: + yield 1 + try: + yield 2 + try: + yield 3 + except Exception: + yield 4 + print(0) + yield 5 + except Exception: + yield 6 + print(1) + yield 7 + except Exception: + yield 8 + print(2) + yield 9 + +for i in range(1, 10): + g = gen() + try: + for _ in range(i): + print(next(g)) + print(g.throw(ValueError)) + except ValueError: + print('ValueError') + except StopIteration: + print('StopIteration') diff --git a/tests/basics/int_constfolding.py b/tests/basics/int_constfolding.py index 158897f553..3168685ff5 100644 --- a/tests/basics/int_constfolding.py +++ b/tests/basics/int_constfolding.py @@ -30,6 +30,10 @@ print(-123 // 7, -123 % 7) print(123 // -7, 123 % -7) print(-123 // -7, -123 % -7) +# power +print(2 ** 3) +print(3 ** 4) + # won't fold so an exception can be raised at runtime try: 1 << -1 diff --git a/tests/basics/io_stringio1.py b/tests/basics/io_stringio1.py index 6d84917547..c4ac9ec076 100644 --- a/tests/basics/io_stringio1.py +++ b/tests/basics/io_stringio1.py @@ -33,6 +33,11 @@ a = io.StringIO() a.write("foo") print(a.read()) +a = io.StringIO() +print(a.tell()) +a.write("foo") +print(a.tell()) + a = io.StringIO() a.close() for f in [a.read, a.getvalue, lambda: a.write("")]: diff --git a/tests/basics/memoryview1.py b/tests/basics/memoryview1.py index b5314f3e99..bcf1b68b00 100644 --- a/tests/basics/memoryview1.py +++ b/tests/basics/memoryview1.py @@ -54,42 +54,11 @@ print(list(m[1:-1])) m[2] = 6 print(a) -# test slice assignment between memoryviews -b1 = bytearray(b'1234') -b2 = bytearray(b'5678') -b3 = bytearray(b'5678') -m1 = memoryview(b1) -m2 = memoryview(b2) -m3 = memoryview(b3) -m2[1:3] = m1[0:2] -print(b2) -b3[1:3] = m1[0:2] -print(b3) -m1[2:4] = b3[1:3] -print(b1) - +# invalid attribute try: - m2[1:3] = b1[0:4] -except ValueError: - print("ValueError") - -try: - m2[1:3] = m1[0:4] -except ValueError: - print("ValueError") - -try: - m2[0:4] = m1[1:3] -except ValueError: - print("ValueError") - -# test memoryview of arrays with items sized larger than 1 -a1 = array.array('i', [0]*5) -m4 = memoryview(a1) -a2 = array.array('i', [3]*5) -m5 = memoryview(a2) -m4[1:3] = m5[1:3] -print(a1) + memoryview(b'a').noexist +except AttributeError: + print('AttributeError') try: m4[1:3] = m2[1:3] @@ -107,3 +76,21 @@ try: memoryview(b'a').noexist except AttributeError: print('AttributeError') + +# equality +print(memoryview(b'abc') == b'abc') +print(memoryview(b'abc') != b'abc') +print(memoryview(b'abc') == b'xyz') +print(memoryview(b'abc') != b'xyz') +print(b'abc' == memoryview(b'abc')) +print(b'abc' != memoryview(b'abc')) +print(b'abc' == memoryview(b'xyz')) +print(b'abc' != memoryview(b'xyz')) +print(memoryview(b'abcdef')[2:4] == b'cd') +print(memoryview(b'abcdef')[2:4] != b'cd') +print(memoryview(b'abcdef')[2:4] == b'xy') +print(memoryview(b'abcdef')[2:4] != b'xy') +print(b'cd' == memoryview(b'abcdef')[2:4]) +print(b'cd' != memoryview(b'abcdef')[2:4]) +print(b'xy' == memoryview(b'abcdef')[2:4]) +print(b'xy' != memoryview(b'abcdef')[2:4]) diff --git a/tests/basics/memoryview_slice_assign.py b/tests/basics/memoryview_slice_assign.py new file mode 100644 index 0000000000..74f6fae6f7 --- /dev/null +++ b/tests/basics/memoryview_slice_assign.py @@ -0,0 +1,87 @@ +# test slice assignment to memoryview + +try: + memoryview(bytearray(1))[:] = memoryview(bytearray(1)) +except (NameError, TypeError): + print("SKIP") + raise SystemExit + +try: + import uarray as array +except ImportError: + try: + import array + except ImportError: + print("SKIP") + raise SystemExit + +# test slice assignment between memoryviews +b1 = bytearray(b'1234') +b2 = bytearray(b'5678') +b3 = bytearray(b'5678') +m1 = memoryview(b1) +m2 = memoryview(b2) +m3 = memoryview(b3) +m2[1:3] = m1[0:2] +print(b2) +b3[1:3] = m1[0:2] +print(b3) +m1[2:4] = b3[1:3] +print(b1) + +# invalid slice assignments +try: + m2[1:3] = b1[0:4] +except ValueError: + print("ValueError") +try: + m2[1:3] = m1[0:4] +except ValueError: + print("ValueError") +try: + m2[0:4] = m1[1:3] +except ValueError: + print("ValueError") + +# test memoryview of arrays with items sized larger than 1 +a1 = array.array('i', [0]*5) +m4 = memoryview(a1) +a2 = array.array('i', [3]*5) +m5 = memoryview(a2) +m4[1:3] = m5[1:3] +print(a1) + +try: + m4[1:3] = m2[1:3] +except ValueError: + print("ValueError") + +# invalid assignment on RHS +try: + memoryview(array.array('i'))[0:2] = b'1234' +except ValueError: + print('ValueError') + +# test shift left of bytearray +b = bytearray(range(10)) +mv = memoryview(b) +mv[1:] = mv[:-1] +print(b) + +# test shift right of bytearray +b = bytearray(range(10)) +mv = memoryview(b) +mv[:-1] = mv[1:] +print(b) + +# test shift left of array +a = array.array('I', range(10)) +mv = memoryview(a) +mv[1:] = mv[:-1] +print(a) + +# test shift right of array +a = array.array('I', range(10)) +mv = memoryview(a) +mv[:-1] = mv[1:] +print(a) diff --git a/tests/basics/namedtuple1.py b/tests/basics/namedtuple1.py index 63d9eddd4e..f55a95625e 100644 --- a/tests/basics/namedtuple1.py +++ b/tests/basics/namedtuple1.py @@ -21,6 +21,9 @@ for t in T(1, 2), T(bar=1, foo=2): print(isinstance(t, tuple)) + # Check tuple can compare equal to namedtuple with same elements + print(t == (t[0], t[1]), (t[0], t[1]) == t) + # Create using positional and keyword args print(T(3, bar=4)) diff --git a/tests/basics/ordereddict1.py b/tests/basics/ordereddict1.py index 9c62b2d623..a6f305ff78 100644 --- a/tests/basics/ordereddict1.py +++ b/tests/basics/ordereddict1.py @@ -21,3 +21,23 @@ d["abc"] = 123 print(len(d)) print(list(d.keys())) print(list(d.values())) + +# pop an element +print(d.popitem()) +print(len(d)) +print(list(d.keys())) +print(list(d.values())) + +# add an element after popping +d["xyz"] = 321 +print(len(d)) +print(list(d.keys())) +print(list(d.values())) + +# pop until empty +print(d.popitem()) +print(d.popitem()) +try: + d.popitem() +except: + print('empty') diff --git a/tests/basics/set_add.py b/tests/basics/set_add.py index ac81f48491..d8cfdaad97 100644 --- a/tests/basics/set_add.py +++ b/tests/basics/set_add.py @@ -1,3 +1,23 @@ s = {1, 2, 3, 4} print(s.add(5)) print(sorted(s)) + +s = set() +s.add(0) +s.add(False) +print(s) + +s = set() +s.add(False) +s.add(0) +print(s) + +s = set() +s.add(1) +s.add(True) +print(s) + +s = set() +s.add(True) +s.add(1) +print(s) diff --git a/tests/basics/set_basic.py b/tests/basics/set_basic.py index 6ea69e4f05..2d1653622e 100644 --- a/tests/basics/set_basic.py +++ b/tests/basics/set_basic.py @@ -10,6 +10,10 @@ print(sorted(s)) s = {1 + len(s)} print(s) +# bools mixed with integers +s = {False, True, 0, 1, 2} +print(len(s)) + # Sets are not hashable try: {s: 1} diff --git a/tests/basics/slice_indices.py b/tests/basics/slice_indices.py new file mode 100644 index 0000000000..b7f439ccca --- /dev/null +++ b/tests/basics/slice_indices.py @@ -0,0 +1,27 @@ +# Test builtin slice indices resolution + +# A class that returns an item key +class A: + def __getitem__(self, idx): + return idx + +# Make sure that we have slices and .indices() +try: + A()[2:5].indices(10) +except: + print("SKIP") + raise SystemExit + +print(A()[:].indices(10)) +print(A()[2:].indices(10)) +print(A()[:7].indices(10)) +print(A()[2:7].indices(10)) +print(A()[2:7:2].indices(10)) +print(A()[2:7:-2].indices(10)) +print(A()[7:2:2].indices(10)) +print(A()[7:2:-2].indices(10)) + +print(A()[2:7:2].indices(5)) +print(A()[2:7:-2].indices(5)) +print(A()[7:2:2].indices(5)) +print(A()[7:2:-2].indices(5)) diff --git a/tests/basics/special_comparisons.py b/tests/basics/special_comparisons.py new file mode 100644 index 0000000000..2ebd7e224e --- /dev/null +++ b/tests/basics/special_comparisons.py @@ -0,0 +1,33 @@ +class A: + def __eq__(self, other): + print("A __eq__ called") + return True + +class B: + def __ne__(self, other): + print("B __ne__ called") + return True + +class C: + def __eq__(self, other): + print("C __eq__ called") + return False + +class D: + def __ne__(self, other): + print("D __ne__ called") + return False + +a = A() +b = B() +c = C() +d = D() + +def test(s): + print(s) + print(eval(s)) + +for x in 'abcd': + for y in 'abcd': + test('{} == {}'.format(x,y)) + test('{} != {}'.format(x,y)) diff --git a/tests/basics/special_comparisons2.py b/tests/basics/special_comparisons2.py new file mode 100644 index 0000000000..c29dc72ce6 --- /dev/null +++ b/tests/basics/special_comparisons2.py @@ -0,0 +1,31 @@ +class E: + def __repr__(self): + return "E" + + def __eq__(self, other): + print('E eq', other) + return 123 + +class F: + def __repr__(self): + return "F" + + def __ne__(self, other): + print('F ne', other) + return -456 + +print(E() != F()) +print(F() != E()) + +tests = (None, 0, 1, 'a') + +for val in tests: + print('==== testing', val) + print(E() == val) + print(val == E()) + print(E() != val) + print(val != E()) + print(F() == val) + print(val == F()) + print(F() != val) + print(val != F()) diff --git a/tests/basics/string_format.py b/tests/basics/string_format.py index 8b25924067..e8600f5836 100644 --- a/tests/basics/string_format.py +++ b/tests/basics/string_format.py @@ -63,6 +63,12 @@ test("{:>20}", "foo") test("{:^20}", "foo") test("{:<20}", "foo") +# formatting bool as int +test('{:d}', False) +test('{:20}', False) +test('{:d}', True) +test('{:20}', True) + # nested format specifiers print("{:{}}".format(123, '#>10')) print("{:{}{}{}}".format(123, '#', '>', '10')) diff --git a/tests/basics/string_format_modulo.py b/tests/basics/string_format_modulo.py index 021b5f08d2..01f8e7ed24 100644 --- a/tests/basics/string_format_modulo.py +++ b/tests/basics/string_format_modulo.py @@ -40,6 +40,9 @@ print("%c" % 'a') print("%10s" % 'abc') print("%-10s" % 'abc') +print('%c' % False) +print('%c' % True) + # Should be able to print dicts; in this case they aren't used # to lookup keywords in formats like %(foo)s print('%s' % {}) diff --git a/tests/basics/subclass_native2_tuple.py b/tests/basics/subclass_native2_tuple.py index 9eb69e1575..a02d3a66dc 100644 --- a/tests/basics/subclass_native2_tuple.py +++ b/tests/basics/subclass_native2_tuple.py @@ -19,3 +19,11 @@ a = Ctuple2() print(len(a)) a = Ctuple2([1, 2, 3]) print(len(a)) + +a = tuple([1,2,3]) +b = Ctuple1([1,2,3]) +c = Ctuple2([1,2,3]) + +print(a == b) +print(b == c) +print(c == a) diff --git a/tests/basics/subclass_native_cmp.py b/tests/basics/subclass_native_cmp.py index 1a095bfa1a..104b9f954a 100644 --- a/tests/basics/subclass_native_cmp.py +++ b/tests/basics/subclass_native_cmp.py @@ -7,3 +7,6 @@ t = mytuple((1, 2, 3)) print(t) print(t == (1, 2, 3)) print((1, 2, 3) == t) + +print(t < (1, 2, 3), t < (1, 2, 4)) +print((1, 2, 3) <= t, (1, 2, 4) < t) diff --git a/tests/basics/subclass_native_str.py b/tests/basics/subclass_native_str.py new file mode 100644 index 0000000000..32090ce270 --- /dev/null +++ b/tests/basics/subclass_native_str.py @@ -0,0 +1,10 @@ +# Test subclassing built-in str + +class S(str): + pass + +s = S('hello') +print(s == 'hello') +print('hello' == s) +print(s == 'Hello') +print('Hello' == s) diff --git a/tests/basics/syntaxerror.py b/tests/basics/syntaxerror.py index 8e706c6e23..c0702cb245 100644 --- a/tests/basics/syntaxerror.py +++ b/tests/basics/syntaxerror.py @@ -82,7 +82,6 @@ test_syntax("break") test_syntax("continue") # must be in a function -test_syntax("return") test_syntax("yield") test_syntax("nonlocal a") test_syntax("await 1") diff --git a/tests/basics/syntaxerror_return.py b/tests/basics/syntaxerror_return.py new file mode 100644 index 0000000000..a32bfbd33c --- /dev/null +++ b/tests/basics/syntaxerror_return.py @@ -0,0 +1,18 @@ +# With MICROPY_CPYTHON_COMPAT, the "return" statement can only appear in a +# function. +# Otherwise (in minimal builds), it ends execution of a module/class. + +try: + exec +except NameError: + print("SKIP") + raise SystemExit + +try: + exec('return; print("this should not be executed.")') + # if we get here then MICROPY_CPYTHON_COMPAT is disabled and test + # should be skipped. + print("SKIP") + raise SystemExit +except SyntaxError: + print('SyntaxError') diff --git a/tests/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index e5cb1f91fe..ce187a0701 100644 --- a/tests/cmdline/cmd_parsetree.py.exp +++ b/tests/cmdline/cmd_parsetree.py.exp @@ -3,7 +3,7 @@ tok(10) [ 4] rule(22) (n=4) id(i) -[ 4] rule(44) (n=1) +[ 4] rule(45) (n=1) NULL [ 5] rule(8) (n=0) NULL diff --git a/tests/cmdline/cmd_showbc.py b/tests/cmdline/cmd_showbc.py index a18f6e697d..3195d41e84 100644 --- a/tests/cmdline/cmd_showbc.py +++ b/tests/cmdline/cmd_showbc.py @@ -2,9 +2,6 @@ # test printing of all bytecodes # fmt: off - - - def f(): # constants a = None + False + True @@ -121,8 +118,7 @@ def f(): # import import a from a import b - - # from sys import * # tested at module scope + #from sys import * # tested at module scope # raise raise @@ -167,6 +163,5 @@ del Class def f(self): super().f() - # import * (needs to be in module scope) from sys import * diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp index 0d0a54bad4..7054df449e 100644 --- a/tests/cmdline/cmd_showbc.py.exp +++ b/tests/cmdline/cmd_showbc.py.exp @@ -7,7 +7,7 @@ arg names: (N_EXC_STACK 0) bc=0 line=1 ######## - bc=\\d\+ line=172 + bc=\\d\+ line=167 00 MAKE_FUNCTION \.\+ \\d\+ STORE_NAME f \\d\+ MAKE_FUNCTION \.\+ @@ -45,7 +45,7 @@ Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+): (INIT_CELL 16) bc=0 line=1 ######## - bc=\\d\+ line=133 + bc=\\d\+ line=129 00 LOAD_CONST_NONE 01 LOAD_CONST_FALSE 02 BINARY_OP 27 __add__ @@ -320,7 +320,7 @@ Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+): (N_EXC_STACK 0) bc=0 line=1 ######## - bc=\\d\+ line=140 + bc=\\d\+ line=136 00 LOAD_CONST_SMALL_INT 1 01 DUP_TOP 02 STORE_FAST 0 @@ -376,7 +376,7 @@ arg names: a (N_EXC_STACK 0) (INIT_CELL 0) ######## - bc=\\d\+ line=147 + bc=\\d\+ line=143 00 LOAD_CONST_SMALL_INT 2 01 BUILD_TUPLE 1 03 LOAD_NULL @@ -393,9 +393,9 @@ arg names: (N_STATE 2) (N_EXC_STACK 0) bc=0 line=1 - bc=0 line=153 - bc=3 line=154 - bc=6 line=155 + bc=0 line=149 + bc=3 line=150 + bc=6 line=151 00 LOAD_CONST_NONE 01 YIELD_VALUE 02 POP_TOP @@ -418,7 +418,7 @@ arg names: (N_EXC_STACK 0) bc=0 line=1 ######## - bc=13 line=160 + bc=13 line=156 00 LOAD_NAME __name__ (cache=0) 04 STORE_NAME __module__ 07 LOAD_CONST_STRING 'Class' @@ -433,7 +433,7 @@ arg names: self (N_STATE 4) (N_EXC_STACK 0) bc=0 line=1 - bc=0 line=168 + bc=0 line=164 00 LOAD_GLOBAL super (cache=0) \\d\+ LOAD_GLOBAL __class__ (cache=0) \\d\+ LOAD_FAST 0 @@ -450,7 +450,7 @@ arg names: * * * (N_STATE 9) (N_EXC_STACK 0) bc=0 line=1 - bc=0 line=63 + bc=0 line=60 ######## 00 LOAD_NULL 01 LOAD_FAST 2 @@ -474,7 +474,7 @@ arg names: * * * (N_STATE 10) (N_EXC_STACK 0) bc=0 line=1 - bc=0 line=64 + bc=0 line=61 ######## 00 BUILD_LIST 0 02 LOAD_FAST 2 @@ -517,7 +517,7 @@ arg names: * (N_EXC_STACK 0) bc=0 line=1 ######## - bc=\\d\+ line=119 + bc=\\d\+ line=116 00 LOAD_DEREF 0 02 LOAD_CONST_SMALL_INT 1 03 BINARY_OP 27 __add__ @@ -536,7 +536,7 @@ arg names: * b (N_EXC_STACK 0) bc=0 line=1 ######## - bc=\\d\+ line=148 + bc=\\d\+ line=144 00 LOAD_FAST 1 01 LOAD_DEREF 0 03 BINARY_OP 27 __add__ diff --git a/tests/cmdline/repl_inspect.py b/tests/cmdline/repl_inspect.py new file mode 100644 index 0000000000..5a7564a3c2 --- /dev/null +++ b/tests/cmdline/repl_inspect.py @@ -0,0 +1,2 @@ +# cmdline: -c print("test") -i +# -c option combined with -i option results in REPL diff --git a/tests/cmdline/repl_inspect.py.exp b/tests/cmdline/repl_inspect.py.exp new file mode 100644 index 0000000000..59f734b2f6 --- /dev/null +++ b/tests/cmdline/repl_inspect.py.exp @@ -0,0 +1,6 @@ +test +MicroPython \.\+ version +Use \.\+ +>>> # cmdline: -c print("test") -i +>>> # -c option combined with -i option results in REPL +>>> diff --git a/tests/cmdline/repl_micropyinspect b/tests/cmdline/repl_micropyinspect new file mode 100644 index 0000000000..0710f1a75b --- /dev/null +++ b/tests/cmdline/repl_micropyinspect @@ -0,0 +1,3 @@ +import uos + +uos.putenv('MICROPYINSPECT', '1') diff --git a/tests/cmdline/repl_micropyinspect.py b/tests/cmdline/repl_micropyinspect.py new file mode 100644 index 0000000000..220dd43d80 --- /dev/null +++ b/tests/cmdline/repl_micropyinspect.py @@ -0,0 +1,2 @@ +# cmdline: cmdline/repl_micropyinspect +# setting MICROPYINSPECT environment variable before program exit triggers REPL diff --git a/tests/cmdline/repl_micropyinspect.py.exp b/tests/cmdline/repl_micropyinspect.py.exp new file mode 100644 index 0000000000..93ff43546e --- /dev/null +++ b/tests/cmdline/repl_micropyinspect.py.exp @@ -0,0 +1,5 @@ +MicroPython \.\+ version +Use \.\+ +>>> # cmdline: cmdline/repl_micropyinspect +>>> # setting MICROPYINSPECT environment variable before program exit triggers REPL +>>> diff --git a/tests/cmdline/repl_words_move.py b/tests/cmdline/repl_words_move.py new file mode 100644 index 0000000000..e1eb529535 --- /dev/null +++ b/tests/cmdline/repl_words_move.py @@ -0,0 +1,31 @@ +# word movement +# backward-word, start in word +234b1 +# backward-word, don't start in word +234 b1 +# backward-word on start of line. if cursor is moved, this will result in a SyntaxError +1 2 + 3b+ +# forward-word, start in word +1+2 12+f+3 +# forward-word, don't start in word +1+ 12 3f+ +# forward-word on eol. if cursor is moved, this will result in a SyntaxError +1 + 2 3f+ + +# kill word +# backward-kill-word, start in word +100 + 45623 +# backward-kill-word, don't start in word +100 + 456231 +# forward-kill-word, start in word +100 + 256d3 +# forward-kill-word, don't start in word +1 + 256d2 + +# extra move/kill shortcuts +# ctrl-left +2341 +# ctrl-right +123 +# ctrl-w +1231 diff --git a/tests/cmdline/repl_words_move.py.exp b/tests/cmdline/repl_words_move.py.exp new file mode 100644 index 0000000000..86f6b77889 --- /dev/null +++ b/tests/cmdline/repl_words_move.py.exp @@ -0,0 +1,47 @@ +MicroPython \.\+ version +Use \.\+ +>>> # word movement +>>> # backward-word, start in word +>>> \.\+ +1234 +>>> # backward-word, don't start in word +>>> \.\+ +1234 +>>> # backward-word on start of line. if cursor is moved, this will result in a SyntaxError +>>> \.\+ +6 +>>> # forward-word, start in word +>>> \.\+ +18 +>>> # forward-word, don't start in word +>>> \.\+ +16 +>>> # forward-word on eol. if cursor is moved, this will result in a SyntaxError +>>> \.\+ +6 +>>> +>>> # kill word +>>> # backward-kill-word, start in word +>>> \.\+ +123 +>>> # backward-kill-word, don't start in word +>>> \.\+ +101 +>>> # forward-kill-word, start in word +>>> \.\+ +123 +>>> # forward-kill-word, don't start in word +>>> \.\+ +3 +>>> +>>> # extra move/kill shortcuts +>>> # ctrl-left +>>> \.\+ +1234 +>>> # ctrl-right +>>> \.\+ +123 +>>> # ctrl-w +>>> \.\+ +1 +>>> diff --git a/tests/cpydiff/core_import_all.py b/tests/cpydiff/core_import_all.py new file mode 100644 index 0000000000..5adf9ae3eb --- /dev/null +++ b/tests/cpydiff/core_import_all.py @@ -0,0 +1,9 @@ +""" +categories: Core,import +description: __all__ is unsupported in __init__.py in MicroPython. +cause: Not implemented. +workaround: Manually import the sub-modules directly in __init__.py using ``from . import foo, bar``. +""" +from modules3 import * + +foo.hello() diff --git a/tests/cpydiff/modules3/__init__.py b/tests/cpydiff/modules3/__init__.py new file mode 100644 index 0000000000..27a2bf2ad9 --- /dev/null +++ b/tests/cpydiff/modules3/__init__.py @@ -0,0 +1 @@ +__all__ = ["foo"] diff --git a/tests/cpydiff/modules3/foo.py b/tests/cpydiff/modules3/foo.py new file mode 100644 index 0000000000..dd9b9d4ddd --- /dev/null +++ b/tests/cpydiff/modules3/foo.py @@ -0,0 +1,2 @@ +def hello(): + print("hello") diff --git a/tests/cpydiff/modules_os_environ.py b/tests/cpydiff/modules_os_environ.py new file mode 100644 index 0000000000..491d8a3101 --- /dev/null +++ b/tests/cpydiff/modules_os_environ.py @@ -0,0 +1,17 @@ +""" +categories: Modules,os +description: ``environ`` attribute is not implemented +cause: Unknown +workaround: Use ``getenv``, ``putenv`` and ``unsetenv`` +""" +import os + +try: + print(os.environ.get("NEW_VARIABLE")) + os.environ["NEW_VARIABLE"] = "VALUE" + print(os.environ["NEW_VARIABLE"]) +except AttributeError: + print("should not get here") + print(os.getenv("NEW_VARIABLE")) + os.putenv("NEW_VARIABLE", "VALUE") + print(os.getenv("NEW_VARIABLE")) diff --git a/tests/cpydiff/modules_os_getenv.py b/tests/cpydiff/modules_os_getenv.py new file mode 100644 index 0000000000..d1e828438e --- /dev/null +++ b/tests/cpydiff/modules_os_getenv.py @@ -0,0 +1,11 @@ +""" +categories: Modules,os +description: ``getenv`` returns actual value instead of cached value +cause: The ``environ`` attribute is not implemented +workaround: Unknown +""" +import os + +print(os.getenv("NEW_VARIABLE")) +os.putenv("NEW_VARIABLE", "VALUE") +print(os.getenv("NEW_VARIABLE")) diff --git a/tests/cpydiff/modules_os_getenv_argcount.py b/tests/cpydiff/modules_os_getenv_argcount.py new file mode 100644 index 0000000000..d7838a92cc --- /dev/null +++ b/tests/cpydiff/modules_os_getenv_argcount.py @@ -0,0 +1,14 @@ +""" +categories: Modules,os +description: ``getenv`` only allows one argument +cause: Unknown +workaround: Test that the return value is ``None`` +""" +import os + +try: + print(os.getenv("NEW_VARIABLE", "DEFAULT")) +except TypeError: + print("should not get here") + # this assumes NEW_VARIABLE is never an empty variable + print(os.getenv("NEW_VARIABLE") or "DEFAULT") diff --git a/tests/cpydiff/syntax_assign_expr.py b/tests/cpydiff/syntax_assign_expr.py new file mode 100644 index 0000000000..d4ed063b39 --- /dev/null +++ b/tests/cpydiff/syntax_assign_expr.py @@ -0,0 +1,7 @@ +""" +categories: Syntax,Operators +description: MicroPython allows using := to assign to the variable of a comprehension, CPython raises a SyntaxError. +cause: MicroPython is optimised for code size and doesn't check this case. +workaround: Do not rely on this behaviour if writing CPython compatible code. +""" +print([i := -1 for i in range(4)]) diff --git a/tests/cpydiff/types_dict_keys_set.py b/tests/cpydiff/types_dict_keys_set.py new file mode 100644 index 0000000000..3a0849a355 --- /dev/null +++ b/tests/cpydiff/types_dict_keys_set.py @@ -0,0 +1,7 @@ +""" +categories: Types,dict +description: Dictionary keys view does not behave as a set. +cause: Not implemented. +workaround: Explicitly convert keys to a set before using set operations. +""" +print({1: 2, 3: 4}.keys() & {1}) diff --git a/tests/cpydiff/types_str_subclassequality.py b/tests/cpydiff/types_str_subclassequality.py deleted file mode 100644 index e44198fccf..0000000000 --- a/tests/cpydiff/types_str_subclassequality.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -categories: Types,str -description: Instance of a subclass of str cannot be compared for equality with an instance of a str -cause: Unknown -workaround: Unknown -""" - - -class S(str): - pass - - -s = S("hello") -print(s == "hello") diff --git a/tests/extmod/btree_error.py b/tests/extmod/btree_error.py new file mode 100644 index 0000000000..00e07ec8c7 --- /dev/null +++ b/tests/extmod/btree_error.py @@ -0,0 +1,42 @@ +# Test that errno's propagate correctly through btree module. + +try: + import btree, uio, uerrno + + uio.IOBase +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class Device(uio.IOBase): + def __init__(self, read_ret=0, ioctl_ret=0): + self.read_ret = read_ret + self.ioctl_ret = ioctl_ret + + def readinto(self, buf): + print("read", len(buf)) + return self.read_ret + + def ioctl(self, cmd, arg): + print("ioctl", cmd) + return self.ioctl_ret + + +# Invalid pagesize; errno comes from btree library +try: + db = btree.open(Device(), pagesize=511) +except OSError as er: + print("OSError", er.args[0] == uerrno.EINVAL) + +# Valid pagesize, device returns error on read; errno comes from Device.readinto +try: + db = btree.open(Device(-1000), pagesize=512) +except OSError as er: + print(repr(er)) + +# Valid pagesize, device returns error on seek; errno comes from Device.ioctl +try: + db = btree.open(Device(0, -1001), pagesize=512) +except OSError as er: + print(repr(er)) diff --git a/tests/extmod/btree_error.py.exp b/tests/extmod/btree_error.py.exp new file mode 100644 index 0000000000..168adb80c5 --- /dev/null +++ b/tests/extmod/btree_error.py.exp @@ -0,0 +1,6 @@ +OSError True +read 24 +OSError(1000,) +read 24 +ioctl 2 +OSError(1001,) diff --git a/tests/extmod/btree_gc.py b/tests/extmod/btree_gc.py new file mode 100644 index 0000000000..153f4e7d78 --- /dev/null +++ b/tests/extmod/btree_gc.py @@ -0,0 +1,23 @@ +# Test btree interaction with the garbage collector. + +try: + import btree, uio, gc +except ImportError: + print("SKIP") + raise SystemExit + +N = 80 + +# Create a BytesIO but don't keep a reference to it. +db = btree.open(uio.BytesIO(), pagesize=512) + +# Overwrite lots of the Python stack to make sure no reference to the BytesIO remains. +x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +# Write lots of key/value pairs, which fill up the DB and also allocate temporary heap +# memory due to the string addition, and do a GC collect to verify that the BytesIO +# is not collected. +for i in range(N): + db[b"thekey" + str(i)] = b"thelongvalue" + str(i) + print(db[b"thekey" + str(i)]) + gc.collect() diff --git a/tests/extmod/btree_gc.py.exp b/tests/extmod/btree_gc.py.exp new file mode 100644 index 0000000000..e7b8d56729 --- /dev/null +++ b/tests/extmod/btree_gc.py.exp @@ -0,0 +1,80 @@ +b'thelongvalue0' +b'thelongvalue1' +b'thelongvalue2' +b'thelongvalue3' +b'thelongvalue4' +b'thelongvalue5' +b'thelongvalue6' +b'thelongvalue7' +b'thelongvalue8' +b'thelongvalue9' +b'thelongvalue10' +b'thelongvalue11' +b'thelongvalue12' +b'thelongvalue13' +b'thelongvalue14' +b'thelongvalue15' +b'thelongvalue16' +b'thelongvalue17' +b'thelongvalue18' +b'thelongvalue19' +b'thelongvalue20' +b'thelongvalue21' +b'thelongvalue22' +b'thelongvalue23' +b'thelongvalue24' +b'thelongvalue25' +b'thelongvalue26' +b'thelongvalue27' +b'thelongvalue28' +b'thelongvalue29' +b'thelongvalue30' +b'thelongvalue31' +b'thelongvalue32' +b'thelongvalue33' +b'thelongvalue34' +b'thelongvalue35' +b'thelongvalue36' +b'thelongvalue37' +b'thelongvalue38' +b'thelongvalue39' +b'thelongvalue40' +b'thelongvalue41' +b'thelongvalue42' +b'thelongvalue43' +b'thelongvalue44' +b'thelongvalue45' +b'thelongvalue46' +b'thelongvalue47' +b'thelongvalue48' +b'thelongvalue49' +b'thelongvalue50' +b'thelongvalue51' +b'thelongvalue52' +b'thelongvalue53' +b'thelongvalue54' +b'thelongvalue55' +b'thelongvalue56' +b'thelongvalue57' +b'thelongvalue58' +b'thelongvalue59' +b'thelongvalue60' +b'thelongvalue61' +b'thelongvalue62' +b'thelongvalue63' +b'thelongvalue64' +b'thelongvalue65' +b'thelongvalue66' +b'thelongvalue67' +b'thelongvalue68' +b'thelongvalue69' +b'thelongvalue70' +b'thelongvalue71' +b'thelongvalue72' +b'thelongvalue73' +b'thelongvalue74' +b'thelongvalue75' +b'thelongvalue76' +b'thelongvalue77' +b'thelongvalue78' +b'thelongvalue79' diff --git a/tests/extmod/framebuf_subclass.py b/tests/extmod/framebuf_subclass.py index b429776bc2..aad5d2a1e9 100644 --- a/tests/extmod/framebuf_subclass.py +++ b/tests/extmod/framebuf_subclass.py @@ -20,3 +20,26 @@ fb = FB(n=3) fb.pixel(0, 0, 0x0102) fb.foo() print(bytes(fb)) + +# Test that blitting a subclass works. +fb2 = framebuf.FrameBuffer(bytearray(2 * 3 * 3), 3, 3, framebuf.RGB565) +fb.fill(0) +fb.pixel(0, 0, 0x0506) +fb.pixel(2, 2, 0x0708) +fb2.blit(fb, 0, 0) +print(bytes(fb2)) + +# Test that blitting something that isn't a subclass fails with TypeError. +class NotAFrameBuf: + pass + + +try: + fb.blit(NotAFrameBuf(), 0, 0) +except TypeError: + print("TypeError") + +try: + fb.blit(None, 0, 0) +except TypeError: + print("TypeError") diff --git a/tests/extmod/framebuf_subclass.py.exp b/tests/extmod/framebuf_subclass.py.exp index 23d53ccc62..58e311fee5 100644 --- a/tests/extmod/framebuf_subclass.py.exp +++ b/tests/extmod/framebuf_subclass.py.exp @@ -1 +1,4 @@ b'\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x03\x04\x03\x04\x03' +b'\x06\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x07' +TypeError +TypeError diff --git a/tests/extmod/ticks_diff.py b/tests/extmod/ticks_diff.py index 4d8df83cf9..de45a557af 100644 --- a/tests/extmod/ticks_diff.py +++ b/tests/extmod/ticks_diff.py @@ -1,4 +1,8 @@ -from utime import ticks_diff, ticks_add +try: + from utime import ticks_diff, ticks_add +except ImportError: + print("SKIP") + raise SystemExit MAX = ticks_add(0, -1) # Should be done like this to avoid small int overflow diff --git a/tests/extmod/time_ms_us.py b/tests/extmod/time_ms_us.py index 0e4749b898..ac2ed8be27 100644 --- a/tests/extmod/time_ms_us.py +++ b/tests/extmod/time_ms_us.py @@ -1,8 +1,8 @@ -import utime - try: - utime.sleep_ms -except AttributeError: + import utime + + utime.sleep_ms, utime.sleep_us, utime.ticks_diff, utime.ticks_ms, utime.ticks_us, utime.ticks_cpu +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/extmod/uasyncio_await_return.py b/tests/extmod/uasyncio_await_return.py new file mode 100644 index 0000000000..d375c9ea97 --- /dev/null +++ b/tests/extmod/uasyncio_await_return.py @@ -0,0 +1,26 @@ +# Test that tasks return their value correctly to the caller + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def foo(): + return 42 + + +async def main(): + # Call function directly via an await + print(await foo()) + + # Create a task and await on it + task = asyncio.create_task(foo()) + print(await task) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_await_return.py.exp b/tests/extmod/uasyncio_await_return.py.exp new file mode 100644 index 0000000000..daaac9e303 --- /dev/null +++ b/tests/extmod/uasyncio_await_return.py.exp @@ -0,0 +1,2 @@ +42 +42 diff --git a/tests/extmod/uasyncio_basic.py b/tests/extmod/uasyncio_basic.py new file mode 100644 index 0000000000..c88908d99b --- /dev/null +++ b/tests/extmod/uasyncio_basic.py @@ -0,0 +1,51 @@ +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +try: + import utime + + ticks = utime.ticks_ms + ticks_diff = utime.ticks_diff +except: + import time + + ticks = lambda: int(time.time() * 1000) + ticks_diff = lambda t1, t0: t1 - t0 + + +async def delay_print(t, s): + await asyncio.sleep(t) + print(s) + + +async def main(): + print("start") + + await asyncio.sleep(0.001) + print("after sleep") + + t0 = ticks() + await delay_print(0.02, "short") + t1 = ticks() + await delay_print(0.04, "long") + t2 = ticks() + await delay_print(-1, "negative") + t3 = ticks() + + print( + "took {} {} {}".format( + round(ticks_diff(t1, t0), -1), + round(ticks_diff(t2, t1), -1), + round(ticks_diff(t3, t2), -1), + ) + ) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_basic.py.exp b/tests/extmod/uasyncio_basic.py.exp new file mode 100644 index 0000000000..6673978769 --- /dev/null +++ b/tests/extmod/uasyncio_basic.py.exp @@ -0,0 +1,6 @@ +start +after sleep +short +long +negative +took 20 40 0 diff --git a/tests/extmod/uasyncio_basic2.py b/tests/extmod/uasyncio_basic2.py new file mode 100644 index 0000000000..a2167e48ee --- /dev/null +++ b/tests/extmod/uasyncio_basic2.py @@ -0,0 +1,24 @@ +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def forever(): + print("forever start") + await asyncio.sleep(10) + + +async def main(): + print("main start") + asyncio.create_task(forever()) + await asyncio.sleep(0.001) + print("main done") + return 42 + + +print(asyncio.run(main())) diff --git a/tests/extmod/uasyncio_basic2.py.exp b/tests/extmod/uasyncio_basic2.py.exp new file mode 100644 index 0000000000..3ca3521728 --- /dev/null +++ b/tests/extmod/uasyncio_basic2.py.exp @@ -0,0 +1,4 @@ +main start +forever start +main done +42 diff --git a/tests/extmod/uasyncio_cancel_fair.py b/tests/extmod/uasyncio_cancel_fair.py new file mode 100644 index 0000000000..9a7b35c161 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_fair.py @@ -0,0 +1,37 @@ +# Test fairness of cancelling a task +# That tasks which continuously cancel each other don't take over the scheduler + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, other): + for i in range(3): + try: + print("start", id) + await asyncio.sleep(0) + print("done", id) + except asyncio.CancelledError as er: + print("cancelled", id) + if other is not None: + print(id, "cancels", other) + tasks[other].cancel() + + +async def main(): + global tasks + tasks = [ + asyncio.create_task(task(0, 1)), + asyncio.create_task(task(1, 0)), + asyncio.create_task(task(2, None)), + ] + await tasks[2] + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_cancel_fair.py.exp b/tests/extmod/uasyncio_cancel_fair.py.exp new file mode 100644 index 0000000000..8f5da08e4c --- /dev/null +++ b/tests/extmod/uasyncio_cancel_fair.py.exp @@ -0,0 +1,24 @@ +start 0 +start 1 +start 2 +done 0 +0 cancels 1 +start 0 +cancelled 1 +1 cancels 0 +start 1 +done 2 +start 2 +cancelled 0 +0 cancels 1 +start 0 +cancelled 1 +1 cancels 0 +start 1 +done 2 +start 2 +cancelled 0 +0 cancels 1 +cancelled 1 +1 cancels 0 +done 2 diff --git a/tests/extmod/uasyncio_cancel_fair2.py b/tests/extmod/uasyncio_cancel_fair2.py new file mode 100644 index 0000000000..46e40f71b1 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_fair2.py @@ -0,0 +1,37 @@ +# Test fairness of cancelling a task +# That tasks which keeps being cancelled by multiple other tasks gets a chance to run + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task_a(): + try: + while True: + print("sleep a") + await asyncio.sleep(0) + except asyncio.CancelledError: + print("cancelled a") + + +async def task_b(id, other): + while other.cancel(): + print("sleep b", id) + await asyncio.sleep(0) + print("done b", id) + + +async def main(): + t = asyncio.create_task(task_a()) + for i in range(3): + asyncio.create_task(task_b(i, t)) + await t + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_cancel_fair2.py.exp b/tests/extmod/uasyncio_cancel_fair2.py.exp new file mode 100644 index 0000000000..e9dd649b45 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_fair2.py.exp @@ -0,0 +1,8 @@ +sleep a +sleep b 0 +sleep b 1 +sleep b 2 +cancelled a +done b 0 +done b 1 +done b 2 diff --git a/tests/extmod/uasyncio_cancel_self.py b/tests/extmod/uasyncio_cancel_self.py new file mode 100644 index 0000000000..660ae66389 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_self.py @@ -0,0 +1,31 @@ +# Test a task cancelling itself (currently unsupported) + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(): + print("task start") + global_task.cancel() + + +async def main(): + global global_task + global_task = asyncio.create_task(task()) + try: + await global_task + except asyncio.CancelledError: + print("main cancel") + print("main done") + + +try: + asyncio.run(main()) +except RuntimeError as er: + print(er) diff --git a/tests/extmod/uasyncio_cancel_self.py.exp b/tests/extmod/uasyncio_cancel_self.py.exp new file mode 100644 index 0000000000..a34c73460b --- /dev/null +++ b/tests/extmod/uasyncio_cancel_self.py.exp @@ -0,0 +1,2 @@ +task start +can't cancel self diff --git a/tests/extmod/uasyncio_cancel_task.py b/tests/extmod/uasyncio_cancel_task.py new file mode 100644 index 0000000000..ec60d85545 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_task.py @@ -0,0 +1,85 @@ +# Test cancelling a task + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(s, allow_cancel): + try: + print("task start") + await asyncio.sleep(s) + print("task done") + except asyncio.CancelledError as er: + print("task cancel") + if allow_cancel: + raise er + + +async def task2(allow_cancel): + print("task 2") + try: + await asyncio.create_task(task(0.05, allow_cancel)) + except asyncio.CancelledError as er: + print("task 2 cancel") + raise er + print("task 2 done") + + +async def main(): + # Cancel task immediately + t = asyncio.create_task(task(2, True)) + print(t.cancel()) + + # Cancel task after it has started + t = asyncio.create_task(task(2, True)) + await asyncio.sleep(0.01) + print(t.cancel()) + print("main sleep") + await asyncio.sleep(0.01) + + # Cancel task multiple times after it has started + t = asyncio.create_task(task(2, True)) + await asyncio.sleep(0.01) + for _ in range(4): + print(t.cancel()) + print("main sleep") + await asyncio.sleep(0.01) + + # Await on a cancelled task + print("main wait") + try: + await t + except asyncio.CancelledError: + print("main got CancelledError") + + # Cancel task after it has finished + t = asyncio.create_task(task(0.01, False)) + await asyncio.sleep(0.05) + print(t.cancel()) + + # Nested: task2 waits on task, task2 is cancelled (should cancel task then task2) + print("----") + t = asyncio.create_task(task2(True)) + await asyncio.sleep(0.01) + print("main cancel") + t.cancel() + print("main sleep") + await asyncio.sleep(0.1) + + # Nested: task2 waits on task, task2 is cancelled but task doesn't allow it (task2 should continue) + print("----") + t = asyncio.create_task(task2(False)) + await asyncio.sleep(0.01) + print("main cancel") + t.cancel() + print("main sleep") + await asyncio.sleep(0.1) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_cancel_task.py.exp b/tests/extmod/uasyncio_cancel_task.py.exp new file mode 100644 index 0000000000..031b94023f --- /dev/null +++ b/tests/extmod/uasyncio_cancel_task.py.exp @@ -0,0 +1,31 @@ +True +task start +True +main sleep +task cancel +task start +True +True +True +True +main sleep +task cancel +main wait +main got CancelledError +task start +task done +False +---- +task 2 +task start +main cancel +main sleep +task cancel +task 2 cancel +---- +task 2 +task start +main cancel +main sleep +task cancel +task 2 done diff --git a/tests/extmod/uasyncio_event.py b/tests/extmod/uasyncio_event.py new file mode 100644 index 0000000000..fb8eb9ffa4 --- /dev/null +++ b/tests/extmod/uasyncio_event.py @@ -0,0 +1,98 @@ +# Test Event class + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, ev): + print("start", id) + print(await ev.wait()) + print("end", id) + + +async def task_delay_set(t, ev): + await asyncio.sleep(t) + print("set event") + ev.set() + + +async def main(): + ev = asyncio.Event() + + # Set and clear without anything waiting, and test is_set() + print(ev.is_set()) + ev.set() + print(ev.is_set()) + ev.clear() + print(ev.is_set()) + + # Create 2 tasks waiting on the event + print("----") + asyncio.create_task(task(1, ev)) + asyncio.create_task(task(2, ev)) + print("yield") + await asyncio.sleep(0) + print("set event") + ev.set() + print("yield") + await asyncio.sleep(0) + + # Create a task waiting on the already-set event + print("----") + asyncio.create_task(task(3, ev)) + print("yield") + await asyncio.sleep(0) + + # Clear event, start a task, then set event again + print("----") + print("clear event") + ev.clear() + asyncio.create_task(task(4, ev)) + await asyncio.sleep(0) + print("set event") + ev.set() + await asyncio.sleep(0) + + # Cancel a task waiting on an event (set event then cancel task) + print("----") + ev = asyncio.Event() + t = asyncio.create_task(task(5, ev)) + await asyncio.sleep(0) + ev.set() + t.cancel() + await asyncio.sleep(0.1) + + # Cancel a task waiting on an event (cancel task then set event) + print("----") + ev = asyncio.Event() + t = asyncio.create_task(task(6, ev)) + await asyncio.sleep(0) + t.cancel() + ev.set() + await asyncio.sleep(0.1) + + # Wait for an event that does get set in time + print("----") + ev.clear() + asyncio.create_task(task_delay_set(0.01, ev)) + await asyncio.wait_for(ev.wait(), 0.1) + await asyncio.sleep(0) + + # Wait for an event that doesn't get set in time + print("----") + ev.clear() + asyncio.create_task(task_delay_set(0.1, ev)) + try: + await asyncio.wait_for(ev.wait(), 0.01) + except asyncio.TimeoutError: + print("TimeoutError") + await ev.wait() + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_event.py.exp b/tests/extmod/uasyncio_event.py.exp new file mode 100644 index 0000000000..3188f291e5 --- /dev/null +++ b/tests/extmod/uasyncio_event.py.exp @@ -0,0 +1,33 @@ +False +True +False +---- +yield +start 1 +start 2 +set event +yield +True +end 1 +True +end 2 +---- +yield +start 3 +True +end 3 +---- +clear event +start 4 +set event +True +end 4 +---- +start 5 +---- +start 6 +---- +set event +---- +TimeoutError +set event diff --git a/tests/extmod/uasyncio_event_fair.py b/tests/extmod/uasyncio_event_fair.py new file mode 100644 index 0000000000..37eca5faef --- /dev/null +++ b/tests/extmod/uasyncio_event_fair.py @@ -0,0 +1,40 @@ +# Test fairness of Event.set() +# That tasks which continuously wait on events don't take over the scheduler + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task1(id): + for i in range(4): + print("sleep", id) + await asyncio.sleep(0) + + +async def task2(id, ev): + for i in range(4): + ev.set() + ev.clear() + print("wait", id) + await ev.wait() + + +async def main(): + ev = asyncio.Event() + tasks = [ + asyncio.create_task(task1(0)), + asyncio.create_task(task2(2, ev)), + asyncio.create_task(task1(1)), + asyncio.create_task(task2(3, ev)), + ] + await tasks[1] + ev.set() + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_event_fair.py.exp b/tests/extmod/uasyncio_event_fair.py.exp new file mode 100644 index 0000000000..fe2e3d6e47 --- /dev/null +++ b/tests/extmod/uasyncio_event_fair.py.exp @@ -0,0 +1,16 @@ +sleep 0 +wait 2 +sleep 1 +wait 3 +sleep 0 +sleep 1 +wait 2 +sleep 0 +sleep 1 +wait 3 +sleep 0 +sleep 1 +wait 2 +wait 3 +wait 2 +wait 3 diff --git a/tests/extmod/uasyncio_exception.py b/tests/extmod/uasyncio_exception.py new file mode 100644 index 0000000000..aae55d6320 --- /dev/null +++ b/tests/extmod/uasyncio_exception.py @@ -0,0 +1,60 @@ +# Test general exception handling + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + +# main task raising an exception +async def main(): + print("main start") + raise ValueError(1) + print("main done") + + +try: + asyncio.run(main()) +except ValueError as er: + print("ValueError", er.args[0]) + +# sub-task raising an exception +async def task(): + print("task start") + raise ValueError(2) + print("task done") + + +async def main(): + print("main start") + t = asyncio.create_task(task()) + await t + print("main done") + + +try: + asyncio.run(main()) +except ValueError as er: + print("ValueError", er.args[0]) + +# main task raising an exception with sub-task not yet scheduled +# TODO not currently working, task is never scheduled +async def task(): + # print('task run') uncomment this line when it works + pass + + +async def main(): + print("main start") + asyncio.create_task(task()) + raise ValueError(3) + print("main done") + + +try: + asyncio.run(main()) +except ValueError as er: + print("ValueError", er.args[0]) diff --git a/tests/extmod/uasyncio_exception.py.exp b/tests/extmod/uasyncio_exception.py.exp new file mode 100644 index 0000000000..b2ee860170 --- /dev/null +++ b/tests/extmod/uasyncio_exception.py.exp @@ -0,0 +1,7 @@ +main start +ValueError 1 +main start +task start +ValueError 2 +main start +ValueError 3 diff --git a/tests/extmod/uasyncio_fair.py b/tests/extmod/uasyncio_fair.py new file mode 100644 index 0000000000..e225706054 --- /dev/null +++ b/tests/extmod/uasyncio_fair.py @@ -0,0 +1,34 @@ +# Test fairness of scheduler + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, t): + print("task start", id) + while True: + if t > 0: + print("task work", id) + await asyncio.sleep(t) + + +async def main(): + t1 = asyncio.create_task(task(1, -0.01)) + t2 = asyncio.create_task(task(2, 0.1)) + t3 = asyncio.create_task(task(3, 0.18)) + t4 = asyncio.create_task(task(4, -100)) + await asyncio.sleep(0.5) + t1.cancel() + t2.cancel() + t3.cancel() + t4.cancel() + print("finish") + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_fair.py.exp b/tests/extmod/uasyncio_fair.py.exp new file mode 100644 index 0000000000..b4b6481db0 --- /dev/null +++ b/tests/extmod/uasyncio_fair.py.exp @@ -0,0 +1,13 @@ +task start 1 +task start 2 +task work 2 +task start 3 +task work 3 +task start 4 +task work 2 +task work 3 +task work 2 +task work 2 +task work 3 +task work 2 +finish diff --git a/tests/extmod/uasyncio_gather.py b/tests/extmod/uasyncio_gather.py new file mode 100644 index 0000000000..0e2948b07c --- /dev/null +++ b/tests/extmod/uasyncio_gather.py @@ -0,0 +1,49 @@ +# test uasyncio.gather() function + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def factorial(name, number): + f = 1 + for i in range(2, number + 1): + print("Task {}: Compute factorial({})...".format(name, i)) + await asyncio.sleep(0.01) + f *= i + print("Task {}: factorial({}) = {}".format(name, number, f)) + return f + + +async def task(id): + print("start", id) + await asyncio.sleep(0.2) + print("end", id) + + +async def gather_task(): + print("gather_task") + await asyncio.gather(task(1), task(2)) + print("gather_task2") + + +async def main(): + # Simple gather with return values + print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4))) + + # Cancel a multi gather + # TODO doesn't work, Task should not forward cancellation from gather to sub-task + # but rather CancelledError should cancel the gather directly, which will then cancel + # all sub-tasks explicitly + # t = asyncio.create_task(gather_task()) + # await asyncio.sleep(0.1) + # t.cancel() + # await asyncio.sleep(0.01) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_gather.py.exp b/tests/extmod/uasyncio_gather.py.exp new file mode 100644 index 0000000000..a37578d7eb --- /dev/null +++ b/tests/extmod/uasyncio_gather.py.exp @@ -0,0 +1,10 @@ +Task A: Compute factorial(2)... +Task B: Compute factorial(2)... +Task C: Compute factorial(2)... +Task A: factorial(2) = 2 +Task B: Compute factorial(3)... +Task C: Compute factorial(3)... +Task B: factorial(3) = 6 +Task C: Compute factorial(4)... +Task C: factorial(4) = 24 +[2, 6, 24] diff --git a/tests/extmod/uasyncio_get_event_loop.py b/tests/extmod/uasyncio_get_event_loop.py new file mode 100644 index 0000000000..8ccbd6814e --- /dev/null +++ b/tests/extmod/uasyncio_get_event_loop.py @@ -0,0 +1,20 @@ +# Test get_event_loop() + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def main(): + print("start") + await asyncio.sleep(0.01) + print("end") + + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/tests/extmod/uasyncio_heaplock.py b/tests/extmod/uasyncio_heaplock.py new file mode 100644 index 0000000000..771d3f0d97 --- /dev/null +++ b/tests/extmod/uasyncio_heaplock.py @@ -0,0 +1,46 @@ +# test that basic scheduling of tasks, and uasyncio.sleep_ms, does not use the heap + +import micropython + +# strict stackless builds can't call functions without allocating a frame on the heap +try: + f = lambda: 0 + micropython.heap_lock() + f() + micropython.heap_unlock() +except RuntimeError: + print("SKIP") + raise SystemExit + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, n, t): + for i in range(n): + print(id, i) + await asyncio.sleep_ms(t) + + +async def main(): + t1 = asyncio.create_task(task(1, 4, 10)) + t2 = asyncio.create_task(task(2, 4, 25)) + + micropython.heap_lock() + + print("start") + await asyncio.sleep_ms(1) + print("sleep") + await asyncio.sleep_ms(100) + print("finish") + + micropython.heap_unlock() + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_heaplock.py.exp b/tests/extmod/uasyncio_heaplock.py.exp new file mode 100644 index 0000000000..a967cc3197 --- /dev/null +++ b/tests/extmod/uasyncio_heaplock.py.exp @@ -0,0 +1,11 @@ +start +1 0 +2 0 +sleep +1 1 +1 2 +2 1 +1 3 +2 2 +2 3 +finish diff --git a/tests/extmod/uasyncio_lock.py b/tests/extmod/uasyncio_lock.py new file mode 100644 index 0000000000..096a8c82be --- /dev/null +++ b/tests/extmod/uasyncio_lock.py @@ -0,0 +1,97 @@ +# Test Lock class + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task_loop(id, lock): + print("task start", id) + for i in range(3): + async with lock: + print("task have", id, i) + print("task end", id) + + +async def task_sleep(lock): + async with lock: + print("task have", lock.locked()) + await asyncio.sleep(0.2) + print("task release", lock.locked()) + await lock.acquire() + print("task have again") + lock.release() + + +async def task_cancel(id, lock, to_cancel=None): + try: + async with lock: + print("task got", id) + await asyncio.sleep(0.1) + print("task release", id) + if to_cancel: + to_cancel[0].cancel() + except asyncio.CancelledError: + print("task cancel", id) + + +async def main(): + lock = asyncio.Lock() + + # Basic acquire/release + print(lock.locked()) + await lock.acquire() + print(lock.locked()) + await asyncio.sleep(0) + lock.release() + print(lock.locked()) + await asyncio.sleep(0) + + # Use with "async with" + async with lock: + print("have lock") + + # 3 tasks wanting the lock + print("----") + asyncio.create_task(task_loop(1, lock)) + asyncio.create_task(task_loop(2, lock)) + t3 = asyncio.create_task(task_loop(3, lock)) + await lock.acquire() + await asyncio.sleep(0) + lock.release() + await t3 + + # 2 sleeping tasks both wanting the lock + print("----") + asyncio.create_task(task_sleep(lock)) + await asyncio.sleep(0.1) + await task_sleep(lock) + + # 3 tasks, the first cancelling the second, the third should still run + print("----") + ts = [None] + asyncio.create_task(task_cancel(0, lock, ts)) + ts[0] = asyncio.create_task(task_cancel(1, lock)) + asyncio.create_task(task_cancel(2, lock)) + await asyncio.sleep(0.3) + print(lock.locked()) + + # 3 tasks, the second and third being cancelled while waiting on the lock + print("----") + t0 = asyncio.create_task(task_cancel(0, lock)) + t1 = asyncio.create_task(task_cancel(1, lock)) + t2 = asyncio.create_task(task_cancel(2, lock)) + await asyncio.sleep(0.05) + t1.cancel() + await asyncio.sleep(0.1) + t2.cancel() + await asyncio.sleep(0.1) + print(lock.locked()) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_lock.py.exp b/tests/extmod/uasyncio_lock.py.exp new file mode 100644 index 0000000000..a37dfcbd2e --- /dev/null +++ b/tests/extmod/uasyncio_lock.py.exp @@ -0,0 +1,41 @@ +False +True +False +have lock +---- +task start 1 +task start 2 +task start 3 +task have 1 0 +task have 2 0 +task have 3 0 +task have 1 1 +task have 2 1 +task have 3 1 +task have 1 2 +task end 1 +task have 2 2 +task end 2 +task have 3 2 +task end 3 +---- +task have True +task release False +task have True +task release False +task have again +task have again +---- +task got 0 +task release 0 +task cancel 1 +task got 2 +task release 2 +False +---- +task got 0 +task cancel 1 +task release 0 +task got 2 +task cancel 2 +False diff --git a/tests/extmod/uasyncio_lock_cancel.py b/tests/extmod/uasyncio_lock_cancel.py new file mode 100644 index 0000000000..85b8df8483 --- /dev/null +++ b/tests/extmod/uasyncio_lock_cancel.py @@ -0,0 +1,55 @@ +# Test that locks work when cancelling multiple waiters on the lock + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(i, lock, lock_flag): + print("task", i, "start") + try: + await lock.acquire() + except asyncio.CancelledError: + print("task", i, "cancel") + return + print("task", i, "lock_flag", lock_flag[0]) + lock_flag[0] = True + await asyncio.sleep(0) + lock.release() + lock_flag[0] = False + print("task", i, "done") + + +async def main(): + # Create a lock and acquire it so the tasks below must wait + lock = asyncio.Lock() + await lock.acquire() + lock_flag = [True] + + # Create 4 tasks and let them all run + t0 = asyncio.create_task(task(0, lock, lock_flag)) + t1 = asyncio.create_task(task(1, lock, lock_flag)) + t2 = asyncio.create_task(task(2, lock, lock_flag)) + t3 = asyncio.create_task(task(3, lock, lock_flag)) + await asyncio.sleep(0) + + # Cancel 2 of the tasks (which are waiting on the lock) and release the lock + t1.cancel() + t2.cancel() + lock.release() + lock_flag[0] = False + + # Let the tasks run to completion + for _ in range(4): + await asyncio.sleep(0) + + # The locke should be unlocked + print(lock.locked()) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_lock_cancel.py.exp b/tests/extmod/uasyncio_lock_cancel.py.exp new file mode 100644 index 0000000000..49ae253887 --- /dev/null +++ b/tests/extmod/uasyncio_lock_cancel.py.exp @@ -0,0 +1,11 @@ +task 0 start +task 1 start +task 2 start +task 3 start +task 1 cancel +task 2 cancel +task 0 lock_flag False +task 0 done +task 3 lock_flag False +task 3 done +False diff --git a/tests/extmod/uasyncio_loop_stop.py b/tests/extmod/uasyncio_loop_stop.py new file mode 100644 index 0000000000..23507f9a7e --- /dev/null +++ b/tests/extmod/uasyncio_loop_stop.py @@ -0,0 +1,45 @@ +# Test Loop.stop() to stop the event loop + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(): + print("task") + + +async def main(): + print("start") + + # Stop the loop after next yield + loop.stop() + + # Check that calling stop() again doesn't do/break anything + loop.stop() + + # This await should stop + print("sleep") + await asyncio.sleep(0) + + # Schedule stop, then create a new task, then yield + loop.stop() + asyncio.create_task(task()) + await asyncio.sleep(0) + + # Final stop + print("end") + loop.stop() + + +loop = asyncio.get_event_loop() +loop.create_task(main()) + +for i in range(3): + print("run", i) + loop.run_forever() diff --git a/tests/extmod/uasyncio_loop_stop.py.exp b/tests/extmod/uasyncio_loop_stop.py.exp new file mode 100644 index 0000000000..bada5f0d84 --- /dev/null +++ b/tests/extmod/uasyncio_loop_stop.py.exp @@ -0,0 +1,7 @@ +run 0 +start +sleep +run 1 +run 2 +task +end diff --git a/tests/extmod/uasyncio_micropython.py b/tests/extmod/uasyncio_micropython.py new file mode 100644 index 0000000000..69e5fa3224 --- /dev/null +++ b/tests/extmod/uasyncio_micropython.py @@ -0,0 +1,37 @@ +# Test MicroPython extensions on CPython asyncio: +# - sleep_ms +# - wait_for_ms + +try: + import utime, uasyncio +except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, t): + print("task start", id) + await uasyncio.sleep_ms(t) + print("task end", id) + return id * 2 + + +async def main(): + # Simple sleep_ms + t0 = utime.ticks_ms() + await uasyncio.sleep_ms(1) + print(utime.ticks_diff(utime.ticks_ms(), t0) < 100) + + # When task finished before the timeout + print(await uasyncio.wait_for_ms(task(1, 5), 50)) + + # When timeout passes and task is cancelled + try: + print(await uasyncio.wait_for_ms(task(2, 50), 5)) + except uasyncio.TimeoutError: + print("timeout") + + print("finish") + + +uasyncio.run(main()) diff --git a/tests/extmod/uasyncio_micropython.py.exp b/tests/extmod/uasyncio_micropython.py.exp new file mode 100644 index 0000000000..f5be1dc75a --- /dev/null +++ b/tests/extmod/uasyncio_micropython.py.exp @@ -0,0 +1,7 @@ +True +task start 1 +task end 1 +2 +task start 2 +timeout +finish diff --git a/tests/extmod/uasyncio_new_event_loop.py b/tests/extmod/uasyncio_new_event_loop.py new file mode 100644 index 0000000000..b711befba9 --- /dev/null +++ b/tests/extmod/uasyncio_new_event_loop.py @@ -0,0 +1,36 @@ +# Test Loop.new_event_loop() + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(): + for i in range(4): + print("task", i) + await asyncio.sleep(0) + await asyncio.sleep(0) + + +async def main(): + print("start") + loop.create_task(task()) + await asyncio.sleep(0) + print("stop") + loop.stop() + + +# Use default event loop to run some tasks +loop = asyncio.get_event_loop() +loop.create_task(main()) +loop.run_forever() + +# Create new event loop, old one should not keep running +loop = asyncio.new_event_loop() +loop.create_task(main()) +loop.run_forever() diff --git a/tests/extmod/uasyncio_new_event_loop.py.exp b/tests/extmod/uasyncio_new_event_loop.py.exp new file mode 100644 index 0000000000..9e104fda39 --- /dev/null +++ b/tests/extmod/uasyncio_new_event_loop.py.exp @@ -0,0 +1,6 @@ +start +task 0 +stop +start +task 0 +stop diff --git a/tests/extmod/uasyncio_set_exception_handler.py b/tests/extmod/uasyncio_set_exception_handler.py new file mode 100644 index 0000000000..ad62a79b7b --- /dev/null +++ b/tests/extmod/uasyncio_set_exception_handler.py @@ -0,0 +1,46 @@ +# Test that tasks return their value correctly to the caller + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +def custom_handler(loop, context): + print("custom_handler", repr(context["exception"])) + + +async def task(i): + # Raise with 2 args so exception prints the same in uPy and CPython + raise ValueError(i, i + 1) + + +async def main(): + loop = asyncio.get_event_loop() + + # Check default exception handler, should be None + print(loop.get_exception_handler()) + + # Set exception handler and test it was set + loop.set_exception_handler(custom_handler) + print(loop.get_exception_handler() == custom_handler) + + # Create a task that raises and uses the custom exception handler + asyncio.create_task(task(0)) + print("sleep") + await asyncio.sleep(0) + + # Create 2 tasks to test order of printing exception + asyncio.create_task(task(1)) + asyncio.create_task(task(2)) + print("sleep") + await asyncio.sleep(0) + + print("done") + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_set_exception_handler.py.exp b/tests/extmod/uasyncio_set_exception_handler.py.exp new file mode 100644 index 0000000000..4744641e54 --- /dev/null +++ b/tests/extmod/uasyncio_set_exception_handler.py.exp @@ -0,0 +1,8 @@ +None +True +sleep +custom_handler ValueError(0, 1) +sleep +custom_handler ValueError(1, 2) +custom_handler ValueError(2, 3) +done diff --git a/tests/extmod/uasyncio_wait_for.py b/tests/extmod/uasyncio_wait_for.py new file mode 100644 index 0000000000..92fd174b84 --- /dev/null +++ b/tests/extmod/uasyncio_wait_for.py @@ -0,0 +1,62 @@ +# Test asyncio.wait_for + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, t): + print("task start", id) + await asyncio.sleep(t) + print("task end", id) + return id * 2 + + +async def task_catch(): + print("task_catch start") + try: + await asyncio.sleep(0.2) + except asyncio.CancelledError: + print("ignore cancel") + print("task_catch done") + + +async def task_raise(): + print("task start") + raise ValueError + + +async def main(): + # When task finished before the timeout + print(await asyncio.wait_for(task(1, 0.01), 10)) + + # When timeout passes and task is cancelled + try: + print(await asyncio.wait_for(task(2, 10), 0.01)) + except asyncio.TimeoutError: + print("timeout") + + # When timeout passes and task is cancelled, but task ignores the cancellation request + try: + print(await asyncio.wait_for(task_catch(), 0.1)) + except asyncio.TimeoutError: + print("TimeoutError") + + # When task raises an exception + try: + print(await asyncio.wait_for(task_raise(), 1)) + except ValueError: + print("ValueError") + + # Timeout of None means wait forever + print(await asyncio.wait_for(task(3, 0.1), None)) + + print("finish") + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_wait_for.py.exp b/tests/extmod/uasyncio_wait_for.py.exp new file mode 100644 index 0000000000..41a5f2481e --- /dev/null +++ b/tests/extmod/uasyncio_wait_for.py.exp @@ -0,0 +1,15 @@ +task start 1 +task end 1 +2 +task start 2 +timeout +task_catch start +ignore cancel +task_catch done +TimeoutError +task start +ValueError +task start 3 +task end 3 +6 +finish diff --git a/tests/extmod/uasyncio_wait_task.py b/tests/extmod/uasyncio_wait_task.py new file mode 100644 index 0000000000..3c79320c9f --- /dev/null +++ b/tests/extmod/uasyncio_wait_task.py @@ -0,0 +1,77 @@ +# Test waiting on a task + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +try: + import utime + + ticks = utime.ticks_ms + ticks_diff = utime.ticks_diff +except: + import time + + ticks = lambda: int(time.time() * 1000) + ticks_diff = lambda t1, t0: t1 - t0 + + +async def task(t): + print("task", t) + + +async def delay_print(t, s): + await asyncio.sleep(t) + print(s) + + +async def task_raise(): + print("task_raise") + raise ValueError + + +async def main(): + print("start") + + # Wait on a task + t = asyncio.create_task(task(1)) + await t + + # Wait on a task that's already done + t = asyncio.create_task(task(2)) + await asyncio.sleep(0.001) + await t + + # Wait again on same task + await t + + print("----") + + # Create 2 tasks + ts1 = asyncio.create_task(delay_print(0.04, "hello")) + ts2 = asyncio.create_task(delay_print(0.08, "world")) + + # Time how long the tasks take to finish, they should execute in parallel + print("start") + t0 = ticks() + await ts1 + t1 = ticks() + await ts2 + t2 = ticks() + print("took {} {}".format(round(ticks_diff(t1, t0), -1), round(ticks_diff(t2, t1), -1))) + + # Wait on a task that raises an exception + t = asyncio.create_task(task_raise()) + try: + await t + except ValueError: + print("ValueError") + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_wait_task.py.exp b/tests/extmod/uasyncio_wait_task.py.exp new file mode 100644 index 0000000000..ee4e70fb4e --- /dev/null +++ b/tests/extmod/uasyncio_wait_task.py.exp @@ -0,0 +1,10 @@ +start +task 1 +task 2 +---- +start +hello +world +took 40 40 +task_raise +ValueError diff --git a/tests/extmod/ure1.py b/tests/extmod/ure1.py index dc77ba928e..a75b2e8b70 100644 --- a/tests/extmod/ure1.py +++ b/tests/extmod/ure1.py @@ -138,3 +138,14 @@ print(re.compile(r"[ax\-]").split("foo-bar")) print(re.compile(r"[a\-x]").split("foo-bar")) print(re.compile(r"[\-ax]").split("foo-bar")) print("===") + +# Module functions take str/bytes/re. +for f in (re.match, re.search): + print(f(".", "foo").group(0)) + print(f(b".", b"foo").group(0)) + print(f(re.compile("."), "foo").group(0)) + try: + f(123, "a") + except TypeError: + print("TypeError") +print("===") diff --git a/tests/extmod/ure_sub.py b/tests/extmod/ure_sub.py index 6bb332077d..953e7bf62a 100644 --- a/tests/extmod/ure_sub.py +++ b/tests/extmod/ure_sub.py @@ -60,3 +60,12 @@ try: re.sub("(a)", "b\\199999999999999999999999999999999999999", "a") except: print("invalid group") + +# Module function takes str/bytes/re. +print(re.sub("a", "a", "a")) +print(re.sub(b".", b"a", b"a")) +print(re.sub(re.compile("a"), "a", "a")) +try: + re.sub(123, "a", "a") +except TypeError: + print("TypeError") diff --git a/tests/extmod/uselect_poll_udp.py b/tests/extmod/uselect_poll_udp.py new file mode 100644 index 0000000000..f6be262ee0 --- /dev/null +++ b/tests/extmod/uselect_poll_udp.py @@ -0,0 +1,28 @@ +# test select.poll on UDP sockets + +try: + import usocket as socket, uselect as select +except ImportError: + try: + import socket, select + except ImportError: + print("SKIP") + raise SystemExit + + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(socket.getaddrinfo("127.0.0.1", 8000)[0][-1]) +poll = select.poll() + +# UDP socket should not be readable +poll.register(s, select.POLLIN) +print(len(poll.poll(0))) + +# UDP socket should be writable +poll.modify(s, select.POLLOUT) +print(poll.poll(0)[0][1] == select.POLLOUT) + +# same test for select.select, but just skip it if the function isn't available +if hasattr(select, "select"): + r, w, e = select.select([s], [], [], 0) + assert not r and not w and not e diff --git a/tests/extmod/usocket_tcp_basic.py b/tests/extmod/usocket_tcp_basic.py new file mode 100644 index 0000000000..368dfe3c98 --- /dev/null +++ b/tests/extmod/usocket_tcp_basic.py @@ -0,0 +1,17 @@ +# Test basic, stand-alone TCP socket functionality + +try: + import usocket as socket, uerrno as errno +except ImportError: + try: + import socket, errno + except ImportError: + print("SKIP") + raise SystemExit + +# recv() on a fresh socket should raise ENOTCONN +s = socket.socket() +try: + s.recv(1) +except OSError as er: + print("ENOTCONN:", er.args[0] == errno.ENOTCONN) diff --git a/tests/extmod/usocket_udp_nonblock.py b/tests/extmod/usocket_udp_nonblock.py new file mode 100644 index 0000000000..7dc0e562a3 --- /dev/null +++ b/tests/extmod/usocket_udp_nonblock.py @@ -0,0 +1,20 @@ +# test non-blocking UDP sockets + +try: + import usocket as socket, uerrno as errno +except ImportError: + try: + import socket, errno + except ImportError: + print("SKIP") + raise SystemExit + + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(socket.getaddrinfo("127.0.0.1", 8000)[0][-1]) +s.settimeout(0) + +try: + s.recv(1) +except OSError as er: + print("EAGAIN:", er.args[0] == errno.EAGAIN) diff --git a/tests/extmod/ussl_basic.py b/tests/extmod/ussl_basic.py index b4e21c7dce..9e1821dca9 100644 --- a/tests/extmod/ussl_basic.py +++ b/tests/extmod/ussl_basic.py @@ -9,7 +9,7 @@ except ImportError: # create in client mode try: - ss = ssl.wrap_socket(io.BytesIO()) + ss = ssl.wrap_socket(io.BytesIO(), server_hostname="test.example.com") except OSError as er: print("wrap_socket:", repr(er)) diff --git a/tests/extmod/ussl_basic.py.exp b/tests/extmod/ussl_basic.py.exp index 5282338319..eb7df855aa 100644 --- a/tests/extmod/ussl_basic.py.exp +++ b/tests/extmod/ussl_basic.py.exp @@ -1,5 +1,4 @@ -ssl_handshake_status: -256 -wrap_socket: OSError(5,) +wrap_socket: OSError(-256, 'CONN_LOST') <_SSLSocket 4 b'' diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index c8e203b3df..62b2a27738 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -10,8 +10,9 @@ except (ImportError, AttributeError): class Filesystem: - def __init__(self, id): + def __init__(self, id, fail=0): self.id = id + self.fail = fail def mount(self, readonly, mkfs): print(self.id, "mount", readonly, mkfs) @@ -25,6 +26,8 @@ class Filesystem: def chdir(self, dir): print(self.id, "chdir", dir) + if self.fail: + raise OSError(self.fail) def getcwd(self): print(self.id, "getcwd") @@ -158,3 +161,18 @@ uos.chdir("/") uos.umount("/") print(uos.listdir("/")) uos.umount("/mnt") + +# chdir to a non-existent mount point (current directory should remain unchanged) +try: + uos.chdir("/foo") +except OSError: + print("OSError") +print(uos.getcwd()) + +# chdir to a non-existent subdirectory in a mounted filesystem +uos.mount(Filesystem(5, 1), "/mnt") +try: + uos.chdir("/mnt/subdir") +except OSError: + print("OSError") +print(uos.getcwd()) diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index 0ae2c2cc97..ebca310304 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -58,3 +58,9 @@ OSError 3 umount ['mnt'] 4 umount +OSError +/ +5 mount False False +5 chdir /subdir +OSError +/ diff --git a/tests/extmod/vfs_fat_fileio1.py b/tests/extmod/vfs_fat_fileio1.py index a553641cd4..18a9690d41 100644 --- a/tests/extmod/vfs_fat_fileio1.py +++ b/tests/extmod/vfs_fat_fileio1.py @@ -110,31 +110,3 @@ except OSError as e: vfs.remove("foo_file.txt") print(list(vfs.ilistdir())) - -# Here we test that opening a file with the heap locked fails correctly. This -# is a special case because file objects use a finaliser and allocating with a -# finaliser is a different path to normal allocation. It would be better to -# test this in the core tests but there are no core objects that use finaliser. -import micropython - -micropython.heap_lock() -try: - vfs.open("x", "r") -except MemoryError: - print("MemoryError") -micropython.heap_unlock() - -# Here we test that the finaliser is actually called during a garbage collection. -import gc - -N = 4 -for i in range(N): - n = "x%d" % i - f = vfs.open(n, "w") - f.write(n) - f = None # release f without closing - [0, 1, 2, 3, 4, 5] # use up Python stack so f is really gone -gc.collect() # should finalise all N files by closing them -for i in range(N): - with vfs.open("x%d" % i, "r") as f: - print(f.read()) diff --git a/tests/extmod/vfs_fat_fileio1.py.exp b/tests/extmod/vfs_fat_fileio1.py.exp index 4eb50402c4..8da96e16bd 100644 --- a/tests/extmod/vfs_fat_fileio1.py.exp +++ b/tests/extmod/vfs_fat_fileio1.py.exp @@ -11,8 +11,3 @@ o d True [('foo_dir', 16384, 0, 0)] -MemoryError -x0 -x1 -x2 -x3 diff --git a/tests/extmod/vfs_fat_finaliser.py b/tests/extmod/vfs_fat_finaliser.py new file mode 100644 index 0000000000..e30f42f847 --- /dev/null +++ b/tests/extmod/vfs_fat_finaliser.py @@ -0,0 +1,69 @@ +# Test VfsFat class and its finaliser + +try: + import uerrno, uos + + uos.VfsFat +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + def __init__(self, blocks, sec_size=512): + self.sec_size = sec_size + self.data = bytearray(blocks * self.sec_size) + + def readblocks(self, n, buf): + for i in range(len(buf)): + buf[i] = self.data[n * self.sec_size + i] + + def writeblocks(self, n, buf): + for i in range(len(buf)): + self.data[n * self.sec_size + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # MP_BLOCKDEV_IOCTL_BLOCK_COUNT + return len(self.data) // self.sec_size + if op == 5: # MP_BLOCKDEV_IOCTL_BLOCK_SIZE + return self.sec_size + + +# Create block device, and skip test if not enough RAM +try: + bdev = RAMBlockDevice(50) +except MemoryError: + print("SKIP") + raise SystemExit + +# Format block device and create VFS object +uos.VfsFat.mkfs(bdev) +vfs = uos.VfsFat(bdev) + +# Here we test that opening a file with the heap locked fails correctly. This +# is a special case because file objects use a finaliser and allocating with a +# finaliser is a different path to normal allocation. It would be better to +# test this in the core tests but there are no core objects that use finaliser. +import micropython + +micropython.heap_lock() +try: + vfs.open("x", "r") +except MemoryError: + print("MemoryError") +micropython.heap_unlock() + +# Here we test that the finaliser is actually called during a garbage collection. +import gc + +N = 4 +for i in range(N): + n = "x%d" % i + f = vfs.open(n, "w") + f.write(n) + f = None # release f without closing + [0, 1, 2, 3] # use up Python stack so f is really gone +gc.collect() # should finalise all N files by closing them +for i in range(N): + with vfs.open("x%d" % i, "r") as f: + print(f.read()) diff --git a/tests/extmod/vfs_fat_finaliser.py.exp b/tests/extmod/vfs_fat_finaliser.py.exp new file mode 100644 index 0000000000..cc51daf2a3 --- /dev/null +++ b/tests/extmod/vfs_fat_finaliser.py.exp @@ -0,0 +1,5 @@ +MemoryError +x0 +x1 +x2 +x3 diff --git a/tests/extmod/vfs_fat_mtime.py b/tests/extmod/vfs_fat_mtime.py new file mode 100644 index 0000000000..d8fd66b75f --- /dev/null +++ b/tests/extmod/vfs_fat_mtime.py @@ -0,0 +1,74 @@ +# Test for VfsFat using a RAM device, mtime feature + +try: + import utime, uos + + utime.time + utime.sleep + uos.VfsFat +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + ERASE_BLOCK_SIZE = 512 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, buf): + addr = block * self.ERASE_BLOCK_SIZE + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, buf): + addr = block * self.ERASE_BLOCK_SIZE + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 5: # block size + return self.ERASE_BLOCK_SIZE + + +def test(bdev, vfs_class): + print("test", vfs_class) + + # Initial format of block device. + vfs_class.mkfs(bdev) + + # construction + vfs = vfs_class(bdev) + + # Create an empty file, should have a timestamp. + current_time = int(utime.time()) + vfs.open("test1", "wt").close() + + # Wait 2 seconds so mtime will increase (FAT has 2 second resolution). + utime.sleep(2) + + # Create another empty file, should have a timestamp. + vfs.open("test2", "wt").close() + + # Stat the files and check mtime is non-zero. + stat1 = vfs.stat("test1") + stat2 = vfs.stat("test2") + print(stat1[8] != 0, stat2[8] != 0) + + # Check that test1 has mtime which matches time.time() at point of creation. + # TODO this currently fails on the unix port because FAT stores timestamps + # in localtime and stat() is UTC based. + # print(current_time - 1 <= stat1[8] <= current_time + 1) + + # Check that test1 is older than test2. + print(stat1[8] < stat2[8]) + + # Unmount. + vfs.umount() + + +bdev = RAMBlockDevice(50) +test(bdev, uos.VfsFat) diff --git a/tests/extmod/vfs_fat_mtime.py.exp b/tests/extmod/vfs_fat_mtime.py.exp new file mode 100644 index 0000000000..55550b9834 --- /dev/null +++ b/tests/extmod/vfs_fat_mtime.py.exp @@ -0,0 +1,3 @@ +test +True True +True diff --git a/tests/extmod/vfs_fat_ramdisk.py b/tests/extmod/vfs_fat_ramdisk.py index de00a50d17..439becc60c 100644 --- a/tests/extmod/vfs_fat_ramdisk.py +++ b/tests/extmod/vfs_fat_ramdisk.py @@ -67,7 +67,7 @@ with vfs.open("foo_file.txt", "w") as f: f.write("hello!") print(list(vfs.ilistdir())) -print("stat root:", vfs.stat("/")) +print("stat root:", vfs.stat("/")[:-3]) # timestamps differ across runs print("stat file:", vfs.stat("foo_file.txt")[:-3]) # timestamps differ across runs print(b"FOO_FILETXT" in bdev.data) diff --git a/tests/extmod/vfs_fat_ramdisk.py.exp b/tests/extmod/vfs_fat_ramdisk.py.exp index 704408cd0b..5407014d5f 100644 --- a/tests/extmod/vfs_fat_ramdisk.py.exp +++ b/tests/extmod/vfs_fat_ramdisk.py.exp @@ -5,7 +5,7 @@ statvfs: (512, 512, 16, 16, 16, 0, 0, 0, 0, 255) getcwd: / True [('foo_file.txt', 32768, 0, 6)] -stat root: (16384, 0, 0, 0, 0, 0, 0, 946684800, 946684800, 946684800) +stat root: (16384, 0, 0, 0, 0, 0, 0) stat file: (32768, 0, 0, 0, 0, 0, 6) True True diff --git a/tests/extmod/vfs_lfs.py b/tests/extmod/vfs_lfs.py index c3656fe687..8e56400df3 100644 --- a/tests/extmod/vfs_lfs.py +++ b/tests/extmod/vfs_lfs.py @@ -35,6 +35,11 @@ class RAMBlockDevice: return 0 +def print_stat(st, print_size=True): + # don't print times (just check that they have the correct type) + print(st[:6], st[6] if print_size else -1, type(st[7]), type(st[8]), type(st[9])) + + def test(bdev, vfs_class): print("test", vfs_class) @@ -63,16 +68,16 @@ def test(bdev, vfs_class): # mkdir, rmdir vfs.mkdir("testdir") print(list(vfs.ilistdir())) - print(list(vfs.ilistdir("testdir"))) + print(sorted(list(vfs.ilistdir("testdir")))) vfs.rmdir("testdir") print(list(vfs.ilistdir())) vfs.mkdir("testdir") # stat a file - print(vfs.stat("test")) + print_stat(vfs.stat("test")) # stat a dir (size seems to vary on LFS2 so don't print that) - print(vfs.stat("testdir")[:6]) + print_stat(vfs.stat("testdir"), False) # read with vfs.open("test", "r") as f: @@ -91,19 +96,57 @@ def test(bdev, vfs_class): # rename vfs.rename("testbig", "testbig2") - print(list(vfs.ilistdir())) + print(sorted(list(vfs.ilistdir()))) + vfs.chdir("testdir") + vfs.rename("/testbig2", "testbig2") + print(sorted(list(vfs.ilistdir()))) + vfs.rename("testbig2", "/testbig2") + vfs.chdir("/") + print(sorted(list(vfs.ilistdir()))) # remove vfs.remove("testbig2") - print(list(vfs.ilistdir())) + print(sorted(list(vfs.ilistdir()))) # getcwd, chdir + vfs.mkdir("/testdir2") + vfs.mkdir("/testdir/subdir") print(vfs.getcwd()) vfs.chdir("/testdir") print(vfs.getcwd()) + + # create file in directory to make sure paths are relative + vfs.open("test2", "w").close() + print_stat(vfs.stat("test2")) + print_stat(vfs.stat("/testdir/test2")) + vfs.remove("test2") + + # chdir back to root and remove testdir vfs.chdir("/") print(vfs.getcwd()) + vfs.chdir("testdir") + print(vfs.getcwd()) + vfs.chdir("..") + print(vfs.getcwd()) + vfs.chdir("testdir/subdir") + print(vfs.getcwd()) + vfs.chdir("../..") + print(vfs.getcwd()) + vfs.chdir("/./testdir2") + print(vfs.getcwd()) + vfs.chdir("../testdir") + print(vfs.getcwd()) + vfs.chdir("../..") + print(vfs.getcwd()) + vfs.chdir(".//testdir") + print(vfs.getcwd()) + vfs.chdir("subdir/./") + print(vfs.getcwd()) + vfs.chdir("/") + print(vfs.getcwd()) + vfs.rmdir("testdir/subdir") vfs.rmdir("testdir") + vfs.rmdir("testdir2") bdev = RAMBlockDevice(30) diff --git a/tests/extmod/vfs_lfs.py.exp b/tests/extmod/vfs_lfs.py.exp index c16e727b81..a0450c84b7 100644 --- a/tests/extmod/vfs_lfs.py.exp +++ b/tests/extmod/vfs_lfs.py.exp @@ -7,8 +7,8 @@ test [('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] [] [('test', 32768, 0, 8)] -(32768, 0, 0, 0, 0, 0, 8, 0, 0, 0) -(16384, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 8 +(16384, 0, 0, 0, 0, 0) -1 littlefs data length: 4096 write 0 @@ -16,10 +16,24 @@ write 1 write 2 write 3 (1024, 1024, 30, 6, 6, 0, 0, 0, 0, 255) -[('test', 32768, 0, 8), ('testdir', 16384, 0, 0), ('testbig2', 32768, 0, 16384)] +[('test', 32768, 0, 8), ('testbig2', 32768, 0, 16384), ('testdir', 16384, 0, 0)] +[('testbig2', 32768, 0, 16384)] +[('test', 32768, 0, 8), ('testbig2', 32768, 0, 16384), ('testdir', 16384, 0, 0)] [('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] / /testdir +(32768, 0, 0, 0, 0, 0) 0 +(32768, 0, 0, 0, 0, 0) 0 +/ +/testdir +/ +/testdir/subdir +/ +/testdir2 +/testdir +/ +/testdir +/testdir/subdir / test (1024, 1024, 30, 28, 28, 0, 0, 0, 0, 255) @@ -30,17 +44,31 @@ test [('testdir', 16384, 0, 0), ('test', 32768, 0, 8)] [] [('test', 32768, 0, 8)] -(32768, 0, 0, 0, 0, 0, 8, 0, 0, 0) -(16384, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 8 +(16384, 0, 0, 0, 0, 0) -1 littlefs data length: 4096 write 0 write 1 write 2 write 3 -(1024, 1024, 30, 9, 9, 0, 0, 0, 0, 255) -[('testbig2', 32768, 0, 16384), ('testdir', 16384, 0, 0), ('test', 32768, 0, 8)] -[('testdir', 16384, 0, 0), ('test', 32768, 0, 8)] +(1024, 1024, 30, 7, 7, 0, 0, 0, 0, 255) +[('test', 32768, 0, 8), ('testbig2', 32768, 0, 16384), ('testdir', 16384, 0, 0)] +[('testbig2', 32768, 0, 16384)] +[('test', 32768, 0, 8), ('testbig2', 32768, 0, 16384), ('testdir', 16384, 0, 0)] +[('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] +/ +/testdir +(32768, 0, 0, 0, 0, 0) 0 +(32768, 0, 0, 0, 0, 0) 0 / /testdir / +/testdir/subdir +/ +/testdir2 +/testdir +/ +/testdir +/testdir/subdir +/ diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 7ac19184b9..9207f4a8c6 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -58,6 +58,12 @@ def test(bdev, vfs_class): f.write('print("package")\n') import lfspkg + # chdir and import module from current directory (needs "" in sys.path) + uos.mkdir("/lfs/subdir") + uos.chdir("/lfs/subdir") + uos.rename("/lfs/lfsmod.py", "/lfs/subdir/lfsmod2.py") + import lfsmod2 + # umount uos.umount("/lfs") @@ -72,6 +78,7 @@ import sys sys.path.clear() sys.path.append("/lfs") +sys.path.append("") # run tests test(bdev, uos.VfsLfs1) diff --git a/tests/extmod/vfs_lfs_mount.py.exp b/tests/extmod/vfs_lfs_mount.py.exp index 90aff35016..b5c5215314 100644 --- a/tests/extmod/vfs_lfs_mount.py.exp +++ b/tests/extmod/vfs_lfs_mount.py.exp @@ -1,6 +1,8 @@ test hello from lfs package +hello from lfs test hello from lfs package +hello from lfs diff --git a/tests/extmod/vfs_lfs_mtime.py b/tests/extmod/vfs_lfs_mtime.py new file mode 100644 index 0000000000..bacdd2246c --- /dev/null +++ b/tests/extmod/vfs_lfs_mtime.py @@ -0,0 +1,105 @@ +# Test for VfsLfs using a RAM device, mtime feature + +try: + import utime, uos + + utime.time + utime.sleep + uos.VfsLfs2 +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 5: # block size + return self.ERASE_BLOCK_SIZE + if op == 6: # erase block + return 0 + + +def test(bdev, vfs_class): + print("test", vfs_class) + + # Initial format of block device. + vfs_class.mkfs(bdev) + + # construction + print("mtime=True") + vfs = vfs_class(bdev, mtime=True) + + # Create an empty file, should have a timestamp. + current_time = int(utime.time()) + vfs.open("test1", "wt").close() + + # Wait 1 second so mtime will increase by at least 1. + utime.sleep(1) + + # Create another empty file, should have a timestamp. + vfs.open("test2", "wt").close() + + # Stat the files and check mtime is non-zero. + stat1 = vfs.stat("test1") + stat2 = vfs.stat("test2") + print(stat1[8] != 0, stat2[8] != 0) + + # Check that test1 has mtime which matches time.time() at point of creation. + print(current_time <= stat1[8] <= current_time + 1) + + # Check that test1 is older than test2. + print(stat1[8] < stat2[8]) + + # Wait 1 second so mtime will increase by at least 1. + utime.sleep(1) + + # Open test1 for reading and ensure mtime did not change. + vfs.open("test1", "rt").close() + print(vfs.stat("test1") == stat1) + + # Open test1 for writing and ensure mtime increased from the previous value. + vfs.open("test1", "wt").close() + stat1_old = stat1 + stat1 = vfs.stat("test1") + print(stat1_old[8] < stat1[8]) + + # Unmount. + vfs.umount() + + # Check that remounting with mtime=False can read the timestamps. + print("mtime=False") + vfs = vfs_class(bdev, mtime=False) + print(vfs.stat("test1") == stat1) + print(vfs.stat("test2") == stat2) + f = vfs.open("test1", "wt") + f.close() + print(vfs.stat("test1") == stat1) + vfs.umount() + + # Check that remounting with mtime=True still has the timestamps. + print("mtime=True") + vfs = vfs_class(bdev, mtime=True) + print(vfs.stat("test1") == stat1) + print(vfs.stat("test2") == stat2) + vfs.umount() + + +bdev = RAMBlockDevice(30) +test(bdev, uos.VfsLfs2) diff --git a/tests/extmod/vfs_lfs_mtime.py.exp b/tests/extmod/vfs_lfs_mtime.py.exp new file mode 100644 index 0000000000..cf6455c040 --- /dev/null +++ b/tests/extmod/vfs_lfs_mtime.py.exp @@ -0,0 +1,14 @@ +test +mtime=True +True True +True +True +True +True +mtime=False +True +True +True +mtime=True +True +True diff --git a/tests/extmod/vfs_userfs.py b/tests/extmod/vfs_userfs.py index 06e546b081..3cdfe82eea 100644 --- a/tests/extmod/vfs_userfs.py +++ b/tests/extmod/vfs_userfs.py @@ -16,14 +16,20 @@ except (ImportError, AttributeError): class UserFile(uio.IOBase): - def __init__(self, data): + def __init__(self, mode, data): + assert isinstance(data, bytes) + self.is_text = mode.find("b") == -1 self.data = data self.pos = 0 def read(self): - return self.data + if self.is_text: + return str(self.data, "utf8") + else: + return self.data def readinto(self, buf): + assert not self.is_text n = 0 while n < len(buf) and self.pos < len(self.data): buf[n] = self.data[self.pos] @@ -54,12 +60,12 @@ class UserFS: def open(self, path, mode): print("open", path, mode) - return UserFile(self.files[path]) + return UserFile(mode, self.files[path]) # create and mount a user filesystem user_files = { - "/data.txt": b"some data in a text file\n", + "/data.txt": b"some data in a text file", "/usermod1.py": b"print('in usermod1')\nimport usermod2", "/usermod2.py": b"print('in usermod2')", } diff --git a/tests/extmod/vfs_userfs.py.exp b/tests/extmod/vfs_userfs.py.exp index 6a4d925b91..00ddd95fca 100644 --- a/tests/extmod/vfs_userfs.py.exp +++ b/tests/extmod/vfs_userfs.py.exp @@ -1,12 +1,12 @@ open /data.txt r -b'some data in a text file\n' +some data in a text file stat /usermod1 stat /usermod1.py -open /usermod1.py r +open /usermod1.py rb ioctl 4 0 in usermod1 stat /usermod2 stat /usermod2.py -open /usermod2.py r +open /usermod2.py rb ioctl 4 0 in usermod2 diff --git a/tests/feature_check/repl_words_move_check.py b/tests/feature_check/repl_words_move_check.py new file mode 100644 index 0000000000..e74615e98c --- /dev/null +++ b/tests/feature_check/repl_words_move_check.py @@ -0,0 +1,4 @@ +# just check if ctrl+w is supported, because it makes sure that +# both MICROPY_REPL_EMACS_WORDS_MOVE and MICROPY_REPL_EXTRA_WORDS_MOVE are enabled. +t = 1231 +t == 1 diff --git a/tests/feature_check/repl_words_move_check.py.exp b/tests/feature_check/repl_words_move_check.py.exp new file mode 100644 index 0000000000..82a4e28ee4 --- /dev/null +++ b/tests/feature_check/repl_words_move_check.py.exp @@ -0,0 +1,7 @@ +MicroPython \.\+ version +Use \.\+ +>>> # Check for emacs keys in REPL +>>> t = \.\+ +>>> t == 2 +True +>>> diff --git a/tests/float/cmath_fun.py b/tests/float/cmath_fun.py index 5622918282..39011733b0 100644 --- a/tests/float/cmath_fun.py +++ b/tests/float/cmath_fun.py @@ -22,9 +22,7 @@ for r in base_values: test_values_non_zero.append(complex(r, -i)) if r != 0.0 and i != 0.0: test_values_non_zero.append(complex(-r, -i)) -test_values = [ - complex(0.0, 0.0), -] + test_values_non_zero +test_values = [complex(0.0, 0.0)] + test_values_non_zero print(test_values) functions = [ @@ -59,3 +57,9 @@ for f_name, f, test_vals in functions: if abs(real) < 1e-6: real = 0.0 print("complex(%.5g, %.5g)" % (real, ret.imag)) + +# test invalid type passed to cmath function +try: + log([]) +except TypeError: + print("TypeError") diff --git a/tests/float/cmath_fun_special.py b/tests/float/cmath_fun_special.py index 33b94d04db..e4c3c17745 100644 --- a/tests/float/cmath_fun_special.py +++ b/tests/float/cmath_fun_special.py @@ -29,4 +29,4 @@ for f_name, f, test_vals in functions: print(f_name) for val in test_vals: ret = f(val) - print("complex(%.5g, %.5g)" % (ret.real, ret.imag)) + print("complex(%.4g, %.4g)" % (ret.real, ret.imag)) diff --git a/tests/float/complex1.py b/tests/float/complex1.py index e6338551c5..a510ffc830 100644 --- a/tests/float/complex1.py +++ b/tests/float/complex1.py @@ -41,6 +41,8 @@ print("%.5g %.5g" % (ans.real, ans.imag)) # comparison print(1j == 1) print(1j == 1j) +print(0 + 0j == False, 1 + 0j == True) +print(False == 0 + 0j, True == 1 + 0j) # comparison of nan is special nan = float("nan") * 1j diff --git a/tests/float/complex_reverse_op.py b/tests/float/complex_reverse_op.py new file mode 100644 index 0000000000..a7351949d0 --- /dev/null +++ b/tests/float/complex_reverse_op.py @@ -0,0 +1,10 @@ +# test complex interacting with special reverse methods + + +class A: + def __radd__(self, x): + print("__radd__") + return 2 + + +print(1j + A()) diff --git a/tests/float/complex_special_methods.py b/tests/float/complex_special_methods.py new file mode 100644 index 0000000000..7e54905e49 --- /dev/null +++ b/tests/float/complex_special_methods.py @@ -0,0 +1,10 @@ +# test complex interacting with special methods + + +class A: + def __add__(self, x): + print("__add__") + return 1 + + +print(A() + 1j) diff --git a/tests/float/float1.py b/tests/float/float1.py index e51e96fd99..efde5146be 100644 --- a/tests/float/float1.py +++ b/tests/float/float1.py @@ -64,6 +64,8 @@ print(1.2 <= 3.4) print(1.2 <= -3.4) print(1.2 >= 3.4) print(1.2 >= -3.4) +print(0.0 == False, 1.0 == True) +print(False == 0.0, True == 1.0) # comparison of nan is special nan = float("nan") diff --git a/tests/float/float2int_doubleprec_intbig.py b/tests/float/float2int_doubleprec_intbig.py index 24d30fe691..84fa2d2536 100644 --- a/tests/float/float2int_doubleprec_intbig.py +++ b/tests/float/float2int_doubleprec_intbig.py @@ -96,6 +96,7 @@ else: fp2int_test(1.9999999999999981 * 2.0 ** 1023.0, "large pos", False) fp2int_test(float("inf"), "inf test", True) +fp2int_test(float("-inf"), "inf test", True) fp2int_test(float("nan"), "NaN test", True) # test numbers < 1 (this used to fail; see issue #1044) diff --git a/tests/float/float2int_fp30_intbig.py b/tests/float/float2int_fp30_intbig.py index da39800401..75ff52f39d 100644 --- a/tests/float/float2int_fp30_intbig.py +++ b/tests/float/float2int_fp30_intbig.py @@ -93,6 +93,7 @@ else: fp2int_test(1.999999879 * 2.0 ** 126.0, "large pos", False) fp2int_test(float("inf"), "inf test", True) +fp2int_test(float("-inf"), "inf test", True) fp2int_test(float("nan"), "NaN test", True) # test numbers < 1 (this used to fail; see issue #1044) diff --git a/tests/float/float2int_intbig.py b/tests/float/float2int_intbig.py index 30522d44ff..62aca39634 100644 --- a/tests/float/float2int_intbig.py +++ b/tests/float/float2int_intbig.py @@ -32,8 +32,10 @@ if ll_type is None: # basic conversion -print(int(14187745.0)) -print("%d" % 14187745.0) +# fmt: off +print(int(14187745.)) +print("%d" % 14187745.) +# fmt: on if ll_type == 2: print(int(2.0 ** 100)) print("%d" % 2.0 ** 100) diff --git a/tests/float/lexer.py b/tests/float/lexer.py new file mode 100644 index 0000000000..a4b1941eea --- /dev/null +++ b/tests/float/lexer.py @@ -0,0 +1,6 @@ +# since black code formatter does not allow leading decimal point with nothing +# before it, we need to test it explicitly + +# fmt: off +print(.1) +# fmt: on diff --git a/tests/float/math_domain.py b/tests/float/math_domain.py index 2d4670f75d..e63628cf50 100644 --- a/tests/float/math_domain.py +++ b/tests/float/math_domain.py @@ -39,8 +39,8 @@ for name, f, args in ( # double argument functions for name, f, args in ( ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3))), - ("fmod", math.fmod, ((1.2, inf), (1.2, 0), (inf, 1.2))), - ("atan2", math.atan2, ((0, 0),)), + ("fmod", math.fmod, ((1.2, inf), (1.2, -inf), (1.2, 0), (inf, 1.2))), + ("atan2", math.atan2, ((0, 0), (-inf, inf), (-inf, -inf), (inf, -inf))), ("copysign", math.copysign, ()), ): for x in args + ((0, inf), (inf, 0), (inf, inf), (inf, nan), (nan, inf), (nan, nan)): diff --git a/tests/float/string_format.py b/tests/float/string_format.py index 92632fe5e1..13382b9037 100644 --- a/tests/float/string_format.py +++ b/tests/float/string_format.py @@ -25,6 +25,9 @@ test("{:06e}", float("inf")) test("{:06e}", float("-inf")) test("{:06e}", float("nan")) +test("{:f}", False) +test("{:f}", True) + # The following fails right now # test("{:10.1}", 0.0) diff --git a/tests/micropython/const_error.py b/tests/micropython/const_error.py index 311cfb4d5e..d35be530a7 100644 --- a/tests/micropython/const_error.py +++ b/tests/micropython/const_error.py @@ -15,3 +15,12 @@ test_syntax("a = const(x)") # redefined constant test_syntax("A = const(1); A = const(2)") + +# these operations are not supported within const +test_syntax("A = const(1 @ 2)") +test_syntax("A = const(1 / 2)") +test_syntax("A = const(1 ** -2)") +test_syntax("A = const(1 << -2)") +test_syntax("A = const(1 >> -2)") +test_syntax("A = const(1 % 0)") +test_syntax("A = const(1 // 0)") diff --git a/tests/micropython/const_error.py.exp b/tests/micropython/const_error.py.exp index 5275689b41..3edc3efe9c 100644 --- a/tests/micropython/const_error.py.exp +++ b/tests/micropython/const_error.py.exp @@ -1,2 +1,9 @@ SyntaxError SyntaxError +SyntaxError +SyntaxError +SyntaxError +SyntaxError +SyntaxError +SyntaxError +SyntaxError diff --git a/tests/micropython/heap_lock.py b/tests/micropython/heap_lock.py index 8558935374..f2892a6dc5 100644 --- a/tests/micropython/heap_lock.py +++ b/tests/micropython/heap_lock.py @@ -5,6 +5,7 @@ import micropython l = [] l2 = list(range(100)) +micropython.heap_lock() micropython.heap_lock() # general allocation on the heap @@ -19,6 +20,14 @@ try: except MemoryError: print("MemoryError") +print(micropython.heap_unlock()) + +# Should still fail +try: + print([]) +except MemoryError: + print("MemoryError") + micropython.heap_unlock() # check that allocation works after an unlock diff --git a/tests/micropython/heap_lock.py.exp b/tests/micropython/heap_lock.py.exp index 819c326634..ae79c88b8e 100644 --- a/tests/micropython/heap_lock.py.exp +++ b/tests/micropython/heap_lock.py.exp @@ -1,3 +1,5 @@ MemoryError MemoryError +1 +MemoryError [] diff --git a/tests/micropython/heap_locked.py b/tests/micropython/heap_locked.py new file mode 100644 index 0000000000..d9e5b5d409 --- /dev/null +++ b/tests/micropython/heap_locked.py @@ -0,0 +1,12 @@ +# test micropython.heap_locked() + +import micropython + +if not hasattr(micropython, "heap_locked"): + print("SKIP") + raise SystemExit + +micropython.heap_lock() +print(micropython.heap_locked()) +micropython.heap_unlock() +print(micropython.heap_locked()) diff --git a/tests/micropython/heap_locked.py.exp b/tests/micropython/heap_locked.py.exp new file mode 100644 index 0000000000..b261da18d5 --- /dev/null +++ b/tests/micropython/heap_locked.py.exp @@ -0,0 +1,2 @@ +1 +0 diff --git a/tests/micropython/heapalloc_exc_compressed.py b/tests/micropython/heapalloc_exc_compressed.py new file mode 100644 index 0000000000..79e423ca0f --- /dev/null +++ b/tests/micropython/heapalloc_exc_compressed.py @@ -0,0 +1,48 @@ +import micropython + +# Tests both code paths for built-in exception raising. +# mp_obj_new_exception_msg_varg (exception requires decompression at raise-time to format) +# mp_obj_new_exception_msg (decompression can be deferred) + +# NameError uses mp_obj_new_exception_msg_varg for NameError("name '%q' isn't defined") +# set.pop uses mp_obj_new_exception_msg for KeyError("pop from an empty set") + +# Tests that deferred decompression works both via print(e) and accessing the message directly via e.args. + +a = set() + +# First test the regular case (can use heap for allocating the decompression buffer). +try: + name() +except NameError as e: + print(type(e).__name__, e) + +try: + a.pop() +except KeyError as e: + print(type(e).__name__, e) + +try: + name() +except NameError as e: + print(e.args[0]) + +try: + a.pop() +except KeyError as e: + print(e.args[0]) + +# Then test that it still works when the heap is locked (i.e. in ISR context). +micropython.heap_lock() + +try: + name() +except NameError as e: + print(type(e).__name__) + +try: + a.pop() +except KeyError as e: + print(type(e).__name__) + +micropython.heap_unlock() diff --git a/tests/micropython/heapalloc_exc_compressed.py.exp b/tests/micropython/heapalloc_exc_compressed.py.exp new file mode 100644 index 0000000000..e00efe088c --- /dev/null +++ b/tests/micropython/heapalloc_exc_compressed.py.exp @@ -0,0 +1,6 @@ +NameError name 'name' is not defined +KeyError pop from empty set +name 'name' is not defined +pop from empty set +NameError +KeyError diff --git a/tests/micropython/heapalloc_exc_compressed_emg_exc.py b/tests/micropython/heapalloc_exc_compressed_emg_exc.py new file mode 100644 index 0000000000..86ade07862 --- /dev/null +++ b/tests/micropython/heapalloc_exc_compressed_emg_exc.py @@ -0,0 +1,41 @@ +import micropython + +# Does the full test from heapalloc_exc_compressed.py but while the heap is +# locked (this can only work when the emergency exception buf is enabled). + +# Some ports need to allocate heap for the emgergency exception buffer. +try: + micropython.alloc_emergency_exception_buf(256) +except AttributeError: + pass + +a = set() + + +def test(): + micropython.heap_lock() + + try: + name() + except NameError as e: + print(type(e).__name__, e) + + try: + a.pop() + except KeyError as e: + print(type(e).__name__, e) + + try: + name() + except NameError as e: + print(e.args[0]) + + try: + a.pop() + except KeyError as e: + print(e.args[0]) + + micropython.heap_unlock() + + +test() diff --git a/tests/micropython/heapalloc_exc_compressed_emg_exc.py.exp b/tests/micropython/heapalloc_exc_compressed_emg_exc.py.exp new file mode 100644 index 0000000000..4293b45091 --- /dev/null +++ b/tests/micropython/heapalloc_exc_compressed_emg_exc.py.exp @@ -0,0 +1,4 @@ +NameError name 'name' is not defined +KeyError pop from empty set +name 'name' is not defined +pop from empty set diff --git a/tests/micropython/heapalloc_yield_from.py b/tests/micropython/heapalloc_yield_from.py new file mode 100644 index 0000000000..58788b1dbc --- /dev/null +++ b/tests/micropython/heapalloc_yield_from.py @@ -0,0 +1,39 @@ +# Check that yield-from can work without heap allocation + +import micropython + +# Yielding from a function generator +def sub_gen(a): + for i in range(a): + yield i + + +def gen(g): + yield from g + + +g = gen(sub_gen(4)) +micropython.heap_lock() +print(next(g)) +print(next(g)) +micropython.heap_unlock() + +# Yielding from a user iterator +class G: + def __init__(self): + self.value = 0 + + def __iter__(self): + return self + + def __next__(self): + v = self.value + self.value += 1 + return v + + +g = gen(G()) +micropython.heap_lock() +print(next(g)) +print(next(g)) +micropython.heap_unlock() diff --git a/tests/micropython/heapalloc_yield_from.py.exp b/tests/micropython/heapalloc_yield_from.py.exp new file mode 100644 index 0000000000..5565ed6787 --- /dev/null +++ b/tests/micropython/heapalloc_yield_from.py.exp @@ -0,0 +1,4 @@ +0 +1 +0 +1 diff --git a/tests/import/mpy_invalid.py b/tests/micropython/import_mpy_invalid.py similarity index 86% rename from tests/import/mpy_invalid.py rename to tests/micropython/import_mpy_invalid.py index 3b416fc63c..d497e897e1 100644 --- a/tests/import/mpy_invalid.py +++ b/tests/micropython/import_mpy_invalid.py @@ -1,11 +1,9 @@ # test importing of invalid .mpy files -import sys, uio - try: - uio.IOBase - import uos + import sys, uio, uos + uio.IOBase uos.mount except (ImportError, AttributeError): print("SKIP") @@ -14,18 +12,16 @@ except (ImportError, AttributeError): class UserFile(uio.IOBase): def __init__(self, data): - self.data = data + self.data = memoryview(data) self.pos = 0 def read(self): return self.data def readinto(self, buf): - n = 0 - while n < len(buf) and self.pos < len(self.data): - buf[n] = self.data[self.pos] - n += 1 - self.pos += 1 + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n return n def ioctl(self, req, arg): diff --git a/tests/import/mpy_invalid.py.exp b/tests/micropython/import_mpy_invalid.py.exp similarity index 100% rename from tests/import/mpy_invalid.py.exp rename to tests/micropython/import_mpy_invalid.py.exp diff --git a/tests/micropython/import_mpy_native_gc.py b/tests/micropython/import_mpy_native_gc.py new file mode 100644 index 0000000000..e8fac8f179 --- /dev/null +++ b/tests/micropython/import_mpy_native_gc.py @@ -0,0 +1,91 @@ +# Test that native code loaded from a .mpy file is retained after a GC. + +try: + import gc, sys, uio, uos + + sys.implementation.mpy + uio.IOBase + uos.mount +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class UserFile(uio.IOBase): + def __init__(self, data): + self.data = memoryview(data) + self.pos = 0 + + def readinto(self, buf): + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n + return n + + def ioctl(self, req, arg): + return 0 + + +class UserFS: + def __init__(self, files): + self.files = files + + def mount(self, readonly, mksfs): + pass + + def umount(self): + pass + + def stat(self, path): + if path in self.files: + return (32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) + raise OSError + + def open(self, path, mode): + return UserFile(self.files[path]) + + +# Pre-compiled examples/natmod/features0 example for various architectures, keyed +# by the required value of sys.implementation.mpy. +features0_file_contents = { + # -march=x64 -mcache-lookup-bc + 0xB05: b'M\x05\x0b\x1f \x84b\xe9/\x00\x00\x00SH\x8b\x1ds\x00\x00\x00\xbe\x02\x00\x00\x00\xffS\x18\xbf\x01\x00\x00\x00H\x85\xc0u\x0cH\x8bC \xbe\x02\x00\x00\x00[\xff\xe0H\x0f\xaf\xf8H\xff\xc8\xeb\xe6ATUSH\x8b\x1dA\x00\x00\x00H\x8b\x7f\x08L\x8bc(A\xff\xd4H\x8d5\x1f\x00\x00\x00H\x89\xc5H\x8b\x05-\x00\x00\x00\x0f\xb78\xffShH\x89\xefA\xff\xd4H\x8b\x03[]A\\\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x84@\x12factorial\x10\x00\x00\r \x01"\x9f\x1c\x01\x1e\xff', + # -march=armv7m + 0x1605: b"M\x05\x16\x1f \x84\x12\x1a\xe0\x00\x00\x13\xb5\nK\nJ{D\x9cX\x02!\xe3h\x98G\x03F\x01 3\xb9\x02!#i\x01\x93\x02\xb0\xbd\xe8\x10@\x18GXC\x01;\xf4\xe7\x00\xbfj\x00\x00\x00\x00\x00\x00\x00\xf8\xb5\tN\tK~D\xf4X@hgi\xb8G\x05F\x07K\x07I\xf2XyD\x10\x88ck\x98G(F\xb8G h\xf8\xbd6\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x01\x84\x00\x12factorial\x10\x00\x00\r<\x01>\x9f8\x01:\xff", +} + +# Populate other armv7m-derived archs based on armv7m. +for arch in (0x1A05, 0x1E05, 0x2205): + features0_file_contents[arch] = features0_file_contents[0x1605] + +if sys.implementation.mpy not in features0_file_contents: + print("SKIP") + raise SystemExit + +# These are the test .mpy files. +user_files = {"/features0.mpy": features0_file_contents[sys.implementation.mpy]} + +# Create and mount a user filesystem. +uos.mount(UserFS(user_files), "/userfs") +sys.path.append("/userfs") + +# Import the native function. +gc.collect() +from features0 import factorial + +# Free the module that contained the function. +del sys.modules["features0"] + +# Run a GC cycle which should reclaim the module but not the function. +gc.collect() + +# Allocate lots of fragmented memory to overwrite anything that was just freed by the GC. +for i in range(1000): + [] + +# Run the native function, it should not have been freed or overwritten. +print(factorial(10)) + +# Unmount and undo path addition. +uos.umount("/userfs") +sys.path.pop() diff --git a/tests/micropython/import_mpy_native_gc.py.exp b/tests/micropython/import_mpy_native_gc.py.exp new file mode 100644 index 0000000000..3fbd4a8698 --- /dev/null +++ b/tests/micropython/import_mpy_native_gc.py.exp @@ -0,0 +1 @@ +3628800 diff --git a/tests/micropython/import_mpy_native_x64.py b/tests/micropython/import_mpy_native_x64.py new file mode 100644 index 0000000000..d0de507d44 --- /dev/null +++ b/tests/micropython/import_mpy_native_x64.py @@ -0,0 +1,117 @@ +# test importing of .mpy files with native code (x64 only) + +try: + import sys, uio, uos + + uio.IOBase + uos.mount +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +if not (sys.platform == "linux" and sys.maxsize > 2 ** 32): + print("SKIP") + raise SystemExit + + +class UserFile(uio.IOBase): + def __init__(self, data): + self.data = memoryview(data) + self.pos = 0 + + def readinto(self, buf): + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n + return n + + def ioctl(self, req, arg): + return 0 + + +class UserFS: + def __init__(self, files): + self.files = files + + def mount(self, readonly, mksfs): + pass + + def umount(self): + pass + + def stat(self, path): + if path in self.files: + return (32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) + raise OSError + + def open(self, path, mode): + return UserFile(self.files[path]) + + +# these are the test .mpy files +# fmt: off +user_files = { + # bad architecture + '/mod0.mpy': b'M\x05\xff\x00\x10', + + # test loading of viper and asm + '/mod1.mpy': ( + b'M\x05\x0b\x1f\x20' # header + + b'\x20' # n bytes, bytecode + b'\x00\x08\x02m\x02m' # prelude + b'\x51' # LOAD_CONST_NONE + b'\x63' # RETURN_VALUE + + b'\x00\x02' # n_obj, n_raw_code + + b'\x22' # n bytes, viper code + b'\x00\x00\x00\x00\x00\x00' # dummy machine code + b'\x00\x00' # qstr0 + b'\x01\x0c\x0aprint' # n_qstr, qstr0 + b'\x00\x00\x00' # scope_flags, n_obj, n_raw_code + + b'\x23' # n bytes, asm code + b'\x00\x00\x00\x00\x00\x00\x00\x00' # dummy machine code + b'\x00\x00\x00' # scope_flags, n_pos_args, type_sig + ), + + # test loading viper with additional scope flags and relocation + '/mod2.mpy': ( + b'M\x05\x0b\x1f\x20' # header + + b'\x20' # n bytes, bytecode + b'\x00\x08\x02m\x02m' # prelude + b'\x51' # LOAD_CONST_NONE + b'\x63' # RETURN_VALUE + + b'\x00\x01' # n_obj, n_raw_code + + b'\x12' # n bytes(=4), viper code + b'\x00\x00\x00\x00' # dummy machine code + b'\x00' # n_qstr + b'\x70' # scope_flags: VIPERBSS | VIPERRODATA | VIPERRELOC + b'\x00\x00' # n_obj, n_raw_code + b'\x06rodata' # rodata, 6 bytes + b'\x04' # bss, 4 bytes + b'\x03\x01\x00' # dummy relocation of rodata + ), +} +# fmt: on + +# create and mount a user filesystem +uos.mount(UserFS(user_files), "/userfs") +sys.path.append("/userfs") + +# import .mpy files from the user filesystem +for i in range(len(user_files)): + mod = "mod%u" % i + try: + __import__(mod) + print(mod, "OK") + except ValueError as er: + print(mod, "ValueError", er) + +# unmount and undo path addition +uos.umount("/userfs") +sys.path.pop() diff --git a/tests/micropython/import_mpy_native_x64.py.exp b/tests/micropython/import_mpy_native_x64.py.exp new file mode 100644 index 0000000000..0b478aa77f --- /dev/null +++ b/tests/micropython/import_mpy_native_x64.py.exp @@ -0,0 +1,3 @@ +mod0 ValueError incompatible native .mpy architecture +mod1 OK +mod2 OK diff --git a/tests/micropython/viper_binop_arith_uint.py b/tests/micropython/viper_binop_arith_uint.py new file mode 100644 index 0000000000..e4270a10a7 --- /dev/null +++ b/tests/micropython/viper_binop_arith_uint.py @@ -0,0 +1,32 @@ +# test arithmetic operators with uint type + + +@micropython.viper +def add(x: uint, y: uint): + return x + y, y + x + + +print("add") +print(*add(1, 2)) +print(*(x & 0xFFFFFFFF for x in add(-1, -2))) + + +@micropython.viper +def sub(x: uint, y: uint): + return x - y, y - x + + +print("sub") +print(*(x & 0xFFFFFFFF for x in sub(1, 2))) +print(*(x & 0xFFFFFFFF for x in sub(-1, -2))) + + +@micropython.viper +def mul(x: uint, y: uint): + return x * y, y * x + + +print("mul") +print(*mul(2, 3)) +print(*(x & 0xFFFFFFFF for x in mul(2, -3))) +print(*mul(-2, -3)) diff --git a/tests/micropython/viper_binop_arith_uint.py.exp b/tests/micropython/viper_binop_arith_uint.py.exp new file mode 100644 index 0000000000..72f84b716a --- /dev/null +++ b/tests/micropython/viper_binop_arith_uint.py.exp @@ -0,0 +1,10 @@ +add +3 3 +4294967293 4294967293 +sub +4294967295 1 +1 4294967295 +mul +6 6 +4294967290 4294967290 +6 6 diff --git a/tests/micropython/viper_binop_bitwise_uint.py b/tests/micropython/viper_binop_bitwise_uint.py new file mode 100644 index 0000000000..3bc7ba8d11 --- /dev/null +++ b/tests/micropython/viper_binop_bitwise_uint.py @@ -0,0 +1,58 @@ +# test bitwise operators on uint type + + +@micropython.viper +def shl(x: uint, y: uint) -> uint: + return x << y + + +print("shl") +print(shl(1, 0)) +print(shl(1, 30)) +print(shl(-1, 10) & 0xFFFFFFFF) + + +@micropython.viper +def shr(x: uint, y: uint) -> uint: + return x >> y + + +print("shr") +print(shr(1, 0)) +print(shr(16, 3)) +print(shr(-1, 1) in (0x7FFFFFFF, 0x7FFFFFFF_FFFFFFFF)) + + +@micropython.viper +def and_(x: uint, y: uint): + return x & y, y & x + + +print("and") +print(*and_(1, 0)) +print(*and_(1, 3)) +print(*and_(-1, 2)) +print(*(x & 0xFFFFFFFF for x in and_(-1, -2))) + + +@micropython.viper +def or_(x: uint, y: uint): + return x | y, y | x + + +print("or") +print(*or_(1, 0)) +print(*or_(1, 2)) +print(*(x & 0xFFFFFFFF for x in or_(-1, 2))) + + +@micropython.viper +def xor(x: uint, y: uint): + return x ^ y, y ^ x + + +print("xor") +print(*xor(1, 0)) +print(*xor(1, 3)) +print(*(x & 0xFFFFFFFF for x in xor(-1, 3))) +print(*xor(-1, -3)) diff --git a/tests/micropython/viper_binop_bitwise_uint.py.exp b/tests/micropython/viper_binop_bitwise_uint.py.exp new file mode 100644 index 0000000000..3ad6469a2f --- /dev/null +++ b/tests/micropython/viper_binop_bitwise_uint.py.exp @@ -0,0 +1,22 @@ +shl +1 +1073741824 +4294966272 +shr +1 +2 +True +and +0 0 +1 1 +2 2 +4294967294 4294967294 +or +1 1 +3 3 +4294967295 4294967295 +xor +1 1 +2 2 +4294967292 4294967292 +2 2 diff --git a/tests/micropython/viper_binop_comp_uint.py b/tests/micropython/viper_binop_comp_uint.py new file mode 100644 index 0000000000..85aa32c78c --- /dev/null +++ b/tests/micropython/viper_binop_comp_uint.py @@ -0,0 +1,31 @@ +# test comparison operators with uint type + + +@micropython.viper +def f(x: uint, y: uint): + if x < y: + print(" <", end="") + if x > y: + print(" >", end="") + if x == y: + print(" ==", end="") + if x <= y: + print(" <=", end="") + if x >= y: + print(" >=", end="") + if x != y: + print(" !=", end="") + + +def test(a, b): + print(a, b, end="") + f(a, b) + print() + + +test(1, 1) +test(2, 1) +test(1, 2) +test(2, -1) +test(-2, 1) +test(-2, -1) diff --git a/tests/micropython/viper_binop_comp_uint.py.exp b/tests/micropython/viper_binop_comp_uint.py.exp new file mode 100644 index 0000000000..cacce62b6e --- /dev/null +++ b/tests/micropython/viper_binop_comp_uint.py.exp @@ -0,0 +1,6 @@ +1 1 == <= >= +2 1 > >= != +1 2 < <= != +2 -1 < <= != +-2 1 > >= != +-2 -1 < <= != diff --git a/tests/micropython/viper_error.py b/tests/micropython/viper_error.py index 790f3d75c4..80617af0c1 100644 --- a/tests/micropython/viper_error.py +++ b/tests/micropython/viper_error.py @@ -52,6 +52,7 @@ test("@micropython.viper\ndef f() -> int: return []") # can't do binary op between incompatible types test("@micropython.viper\ndef f(): 1 + []") +test("@micropython.viper\ndef f(x:int, y:uint): x < y") # can't load test("@micropython.viper\ndef f(): 1[0]") @@ -73,6 +74,8 @@ test("@micropython.viper\ndef f(x:int): -x") test("@micropython.viper\ndef f(x:int): ~x") # binary op not implemented +test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y") +test("@micropython.viper\ndef f(x:uint, y:uint): res = x % y") test("@micropython.viper\ndef f(x:int): res = x in x") # yield (from) not implemented diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp index da9a0ca93e..31c85b1d87 100644 --- a/tests/micropython/viper_error.py.exp +++ b/tests/micropython/viper_error.py.exp @@ -6,6 +6,7 @@ ViperTypeError("local 'x' has type 'int' but source is 'object'",) ViperTypeError("can't implicitly convert 'ptr' to 'bool'",) ViperTypeError("return expected 'int' but got 'object'",) ViperTypeError("can't do binary op between 'int' and 'object'",) +ViperTypeError('comparison of int and uint',) ViperTypeError("can't load from 'int'",) ViperTypeError("can't load from 'int'",) ViperTypeError("can't store to 'int'",) @@ -17,6 +18,8 @@ ViperTypeError('must raise an object',) ViperTypeError('unary op __pos__ not implemented',) ViperTypeError('unary op __neg__ not implemented',) ViperTypeError('unary op __invert__ not implemented',) +ViperTypeError('div/mod not implemented for uint',) +ViperTypeError('div/mod not implemented for uint',) ViperTypeError('binary op not implemented',) NotImplementedError('native yield',) NotImplementedError('native yield',) diff --git a/tests/misc/sys_settrace_generator.py.exp b/tests/misc/sys_settrace_generator.py.exp index 5329cdfe71..de9d0bf1c3 100644 --- a/tests/misc/sys_settrace_generator.py.exp +++ b/tests/misc/sys_settrace_generator.py.exp @@ -1,195 +1,195 @@ ### trace_handler::main event: call - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:33 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:34 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:40 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:41 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:42 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:44 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:34 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:44 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:44 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:44 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:38 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:38 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 -### trace_handler::main event: exception - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:49 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:50 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:51 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: call + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:42 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: return + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: call + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: return + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: call + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: return + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: call + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: return + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: exception + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:58 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 +### trace_handler::main event: line + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:59 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 test_generator 7 8 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:53 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:61 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:54 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:62 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:34 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:42 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:35 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:36 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:37 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:38 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:38 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:55 - 2: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 + 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 + 2: @__main__: => miscmisc/sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:57 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:65 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 7 ### trace_handler::main event: return - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:57 - 1: @__main__: => miscmisc/sys_settrace_generator.py:60 + 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:65 + 1: @__main__: => miscmisc/sys_settrace_generator.py:69 Total traces executed: 54 diff --git a/tests/misc/sys_settrace_loop.py.exp b/tests/misc/sys_settrace_loop.py.exp index 3d3da5b6f7..f56f98fae0 100644 --- a/tests/misc/sys_settrace_loop.py.exp +++ b/tests/misc/sys_settrace_loop.py.exp @@ -1,72 +1,72 @@ ### trace_handler::main event: call - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:33 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:35 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:36 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:37 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:36 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:37 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:36 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:37 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:36 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:37 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:38 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -test_for_loop 3 -### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:41 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:42 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:43 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 -### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 ### trace_handler::main event: line 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:46 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +test_for_loop 3 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:49 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:50 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:51 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 +### trace_handler::main event: line + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:54 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 test_while_loop 3 ### trace_handler::main event: return - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:46 - 1: @__main__: => miscmisc/sys_settrace_loop.py:49 + 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:54 + 1: @__main__: => miscmisc/sys_settrace_loop.py:58 Total traces executed: 23 diff --git a/tests/net_hosted/README b/tests/net_hosted/README deleted file mode 100644 index 724dd61584..0000000000 --- a/tests/net_hosted/README +++ /dev/null @@ -1,11 +0,0 @@ -This directory contains network tests which require just "peer to peer" -network connection between test host and device under test, instead of -full Internet connection. - -Note that setup for these tests and tests themselves are WIP, and may -not yet fully correspond to the functional specification above. - -So far, these tests are not run as part of the main testsuite and need -to be run seperately (from the main test/ directory): - - ./run-tests net_hosted/*.py diff --git a/tests/net_hosted/accept_nonblock.py b/tests/net_hosted/accept_nonblock.py deleted file mode 100644 index 941965e178..0000000000 --- a/tests/net_hosted/accept_nonblock.py +++ /dev/null @@ -1,16 +0,0 @@ -# test that socket.accept() on a non-blocking socket raises EAGAIN - -try: - import usocket as socket -except: - import socket - -s = socket.socket() -s.bind(socket.getaddrinfo("127.0.0.1", 8123)[0][-1]) -s.setblocking(False) -s.listen(1) -try: - s.accept() -except OSError as er: - print(er.args[0] == 11) # 11 is EAGAIN -s.close() diff --git a/tests/net_hosted/accept_nonblock.py.exp b/tests/net_hosted/accept_nonblock.py.exp deleted file mode 100644 index 0ca95142bb..0000000000 --- a/tests/net_hosted/accept_nonblock.py.exp +++ /dev/null @@ -1 +0,0 @@ -True diff --git a/tests/net_hosted/accept_timeout.py b/tests/net_hosted/accept_timeout.py deleted file mode 100644 index ff989110ae..0000000000 --- a/tests/net_hosted/accept_timeout.py +++ /dev/null @@ -1,22 +0,0 @@ -# test that socket.accept() on a socket with timeout raises ETIMEDOUT - -try: - import usocket as socket -except: - import socket - -try: - socket.socket.settimeout -except AttributeError: - print("SKIP") - raise SystemExit - -s = socket.socket() -s.bind(socket.getaddrinfo("127.0.0.1", 8123)[0][-1]) -s.settimeout(1) -s.listen(1) -try: - s.accept() -except OSError as er: - print(er.args[0] in (110, "timed out")) # 110 is ETIMEDOUT; CPython uses a string -s.close() diff --git a/tests/net_hosted/accept_timeout.py.exp b/tests/net_hosted/accept_timeout.py.exp deleted file mode 100644 index 0ca95142bb..0000000000 --- a/tests/net_hosted/accept_timeout.py.exp +++ /dev/null @@ -1 +0,0 @@ -True diff --git a/tests/net_hosted/connect_nonblock.py b/tests/net_hosted/connect_nonblock.py deleted file mode 100644 index 3a3eaa2ba0..0000000000 --- a/tests/net_hosted/connect_nonblock.py +++ /dev/null @@ -1,20 +0,0 @@ -# test that socket.connect() on a non-blocking socket raises EINPROGRESS - -try: - import usocket as socket -except: - import socket - - -def test(peer_addr): - s = socket.socket() - s.setblocking(False) - try: - s.connect(peer_addr) - except OSError as er: - print(er.args[0] == 115) # 115 is EINPROGRESS - s.close() - - -if __name__ == "__main__": - test(socket.getaddrinfo("micropython.org", 80)[0][-1]) diff --git a/tests/net_hosted/connect_nonblock.py.exp b/tests/net_hosted/connect_nonblock.py.exp deleted file mode 100644 index 0ca95142bb..0000000000 --- a/tests/net_hosted/connect_nonblock.py.exp +++ /dev/null @@ -1 +0,0 @@ -True diff --git a/tests/net_hosted/connect_poll.py b/tests/net_hosted/connect_poll.py deleted file mode 100644 index b2739e36e9..0000000000 --- a/tests/net_hosted/connect_poll.py +++ /dev/null @@ -1,31 +0,0 @@ -# test that socket.connect() has correct polling behaviour before, during and after - -try: - import usocket as socket, uselect as select -except: - import socket, select - - -def test(peer_addr): - s = socket.socket() - poller = select.poll() - poller.register(s) - - # test poll before connect - p = poller.poll(0) - print(len(p), p[0][-1]) - - s.connect(peer_addr) - - # test poll during connection - print(len(poller.poll(0))) - - # test poll after connection is established - p = poller.poll(1000) - print(len(p), p[0][-1]) - - s.close() - - -if __name__ == "__main__": - test(socket.getaddrinfo("micropython.org", 80)[0][-1]) diff --git a/tests/net_hosted/connect_poll.py.exp b/tests/net_hosted/connect_poll.py.exp deleted file mode 100644 index d18a39a12a..0000000000 --- a/tests/net_hosted/connect_poll.py.exp +++ /dev/null @@ -1,3 +0,0 @@ -1 20 -1 -1 4 diff --git a/tests/net_hosted/ssl_getpeercert.py b/tests/net_hosted/ssl_getpeercert.py deleted file mode 100644 index dee5fcfd89..0000000000 --- a/tests/net_hosted/ssl_getpeercert.py +++ /dev/null @@ -1,21 +0,0 @@ -# test ssl.getpeercert() method - -try: - import usocket as socket - import ussl as ssl -except: - import socket - import ssl - - -def test(peer_addr): - s = socket.socket() - s.connect(peer_addr) - s = ssl.wrap_socket(s) - cert = s.getpeercert(True) - print(type(cert), len(cert) > 100) - s.close() - - -if __name__ == "__main__": - test(socket.getaddrinfo("micropython.org", 443)[0][-1]) diff --git a/tests/net_hosted/ssl_getpeercert.py.exp b/tests/net_hosted/ssl_getpeercert.py.exp deleted file mode 100644 index ff7ef5adf1..0000000000 --- a/tests/net_hosted/ssl_getpeercert.py.exp +++ /dev/null @@ -1 +0,0 @@ - True diff --git a/tests/net_inet/README b/tests/net_inet/README deleted file mode 100644 index 9a5614efa6..0000000000 --- a/tests/net_inet/README +++ /dev/null @@ -1,5 +0,0 @@ -This directory contains network tests which require Internet connection. -Note that these tests are not run as part of the main testsuite and need -to be run seperately (from the main test/ directory): - - ./run-tests net_inet/*.py diff --git a/tests/net_inet/test_tls_sites.py b/tests/net_inet/test_tls_sites.py deleted file mode 100644 index 7fdafab99a..0000000000 --- a/tests/net_inet/test_tls_sites.py +++ /dev/null @@ -1,60 +0,0 @@ -try: - import usocket as _socket -except: - import _socket -try: - import ussl as ssl -except: - import ssl - - # CPython only supports server_hostname with SSLContext - ssl = ssl.SSLContext() - - -def test_one(site, opts): - ai = _socket.getaddrinfo(site, 443) - addr = ai[0][-1] - - s = _socket.socket() - - try: - s.connect(addr) - - if "sni" in opts: - s = ssl.wrap_socket(s, server_hostname=opts["host"]) - else: - s = ssl.wrap_socket(s) - - s.write(b"GET / HTTP/1.0\r\nHost: %s\r\n\r\n" % bytes(site, "latin")) - resp = s.read(4096) - # print(resp) - - finally: - s.close() - - -SITES = [ - "google.com", - "www.google.com", - "api.telegram.org", - {"host": "api.pushbullet.com", "sni": True}, - # "w9rybpfril.execute-api.ap-southeast-2.amazonaws.com", - {"host": "w9rybpfril.execute-api.ap-southeast-2.amazonaws.com", "sni": True}, -] - - -def main(): - for site in SITES: - opts = {} - if isinstance(site, dict): - opts = site - site = opts["host"] - - try: - test_one(site, opts) - print(site, "ok") - except Exception as e: - print(site, repr(e)) - - -main() diff --git a/tests/net_inet/test_tls_sites.py.exp b/tests/net_inet/test_tls_sites.py.exp deleted file mode 100644 index 2f3c113d2f..0000000000 --- a/tests/net_inet/test_tls_sites.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -google.com ok -www.google.com ok -api.telegram.org ok -api.pushbullet.com ok -w9rybpfril.execute-api.ap-southeast-2.amazonaws.com ok diff --git a/tests/perf_bench/misc_aes.py b/tests/perf_bench/misc_aes.py index c274b71aea..0743737cb7 100644 --- a/tests/perf_bench/misc_aes.py +++ b/tests/perf_bench/misc_aes.py @@ -12,266 +12,26 @@ # discrete arithmetic routines, mostly from a precomputed table # non-linear, invertible, substitution box -aes_s_box_table = bytes( - ( - 0x63, - 0x7C, - 0x77, - 0x7B, - 0xF2, - 0x6B, - 0x6F, - 0xC5, - 0x30, - 0x01, - 0x67, - 0x2B, - 0xFE, - 0xD7, - 0xAB, - 0x76, - 0xCA, - 0x82, - 0xC9, - 0x7D, - 0xFA, - 0x59, - 0x47, - 0xF0, - 0xAD, - 0xD4, - 0xA2, - 0xAF, - 0x9C, - 0xA4, - 0x72, - 0xC0, - 0xB7, - 0xFD, - 0x93, - 0x26, - 0x36, - 0x3F, - 0xF7, - 0xCC, - 0x34, - 0xA5, - 0xE5, - 0xF1, - 0x71, - 0xD8, - 0x31, - 0x15, - 0x04, - 0xC7, - 0x23, - 0xC3, - 0x18, - 0x96, - 0x05, - 0x9A, - 0x07, - 0x12, - 0x80, - 0xE2, - 0xEB, - 0x27, - 0xB2, - 0x75, - 0x09, - 0x83, - 0x2C, - 0x1A, - 0x1B, - 0x6E, - 0x5A, - 0xA0, - 0x52, - 0x3B, - 0xD6, - 0xB3, - 0x29, - 0xE3, - 0x2F, - 0x84, - 0x53, - 0xD1, - 0x00, - 0xED, - 0x20, - 0xFC, - 0xB1, - 0x5B, - 0x6A, - 0xCB, - 0xBE, - 0x39, - 0x4A, - 0x4C, - 0x58, - 0xCF, - 0xD0, - 0xEF, - 0xAA, - 0xFB, - 0x43, - 0x4D, - 0x33, - 0x85, - 0x45, - 0xF9, - 0x02, - 0x7F, - 0x50, - 0x3C, - 0x9F, - 0xA8, - 0x51, - 0xA3, - 0x40, - 0x8F, - 0x92, - 0x9D, - 0x38, - 0xF5, - 0xBC, - 0xB6, - 0xDA, - 0x21, - 0x10, - 0xFF, - 0xF3, - 0xD2, - 0xCD, - 0x0C, - 0x13, - 0xEC, - 0x5F, - 0x97, - 0x44, - 0x17, - 0xC4, - 0xA7, - 0x7E, - 0x3D, - 0x64, - 0x5D, - 0x19, - 0x73, - 0x60, - 0x81, - 0x4F, - 0xDC, - 0x22, - 0x2A, - 0x90, - 0x88, - 0x46, - 0xEE, - 0xB8, - 0x14, - 0xDE, - 0x5E, - 0x0B, - 0xDB, - 0xE0, - 0x32, - 0x3A, - 0x0A, - 0x49, - 0x06, - 0x24, - 0x5C, - 0xC2, - 0xD3, - 0xAC, - 0x62, - 0x91, - 0x95, - 0xE4, - 0x79, - 0xE7, - 0xC8, - 0x37, - 0x6D, - 0x8D, - 0xD5, - 0x4E, - 0xA9, - 0x6C, - 0x56, - 0xF4, - 0xEA, - 0x65, - 0x7A, - 0xAE, - 0x08, - 0xBA, - 0x78, - 0x25, - 0x2E, - 0x1C, - 0xA6, - 0xB4, - 0xC6, - 0xE8, - 0xDD, - 0x74, - 0x1F, - 0x4B, - 0xBD, - 0x8B, - 0x8A, - 0x70, - 0x3E, - 0xB5, - 0x66, - 0x48, - 0x03, - 0xF6, - 0x0E, - 0x61, - 0x35, - 0x57, - 0xB9, - 0x86, - 0xC1, - 0x1D, - 0x9E, - 0xE1, - 0xF8, - 0x98, - 0x11, - 0x69, - 0xD9, - 0x8E, - 0x94, - 0x9B, - 0x1E, - 0x87, - 0xE9, - 0xCE, - 0x55, - 0x28, - 0xDF, - 0x8C, - 0xA1, - 0x89, - 0x0D, - 0xBF, - 0xE6, - 0x42, - 0x68, - 0x41, - 0x99, - 0x2D, - 0x0F, - 0xB0, - 0x54, - 0xBB, - 0x16, - ) -) +# fmt: off +aes_s_box_table = bytes(( + 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16, +)) +# fmt: on # multiplication of polynomials modulo x^8 + x^4 + x^3 + x + 1 = 0x11b def aes_gf8_mul_2(x): diff --git a/tests/run-multitests.py b/tests/run-multitests.py new file mode 100755 index 0000000000..7ab4e85c57 --- /dev/null +++ b/tests/run-multitests.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 + +# This file is part of the MicroPython project, http://micropython.org/ +# The MIT License (MIT) +# Copyright (c) 2020 Damien P. George + +import sys, os, time, re, select +import argparse +import subprocess + +sys.path.append("../tools") +import pyboard + +if os.name == "nt": + CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3.exe") + MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/windows/micropython.exe") +else: + CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") + MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython") + +PYTHON_TRUTH = CPYTHON3 + +INSTANCE_READ_TIMEOUT_S = 10 + +APPEND_CODE_TEMPLATE = """ +import sys +class multitest: + @staticmethod + def flush(): + if hasattr(sys.stdout, "flush"): + sys.stdout.flush() + @staticmethod + def skip(): + print("SKIP") + multitest.flush() + raise SystemExit + @staticmethod + def next(): + print("NEXT") + multitest.flush() + @staticmethod + def globals(**gs): + for g in gs: + print("SET {{}} = {{!r}}".format(g, gs[g])) + multitest.flush() + @staticmethod + def get_network_ip(): + try: + import network + ip = network.WLAN().ifconfig()[0] + except: + ip = "127.0.0.1" + return ip + +{} + +instance{}() +multitest.flush() +""" + +# The btstack implementation on Unix generates some spurious output that we +# can't control. +IGNORE_OUTPUT_MATCHES = ( + "libusb: error ", # It tries to open devices that it doesn't have access to (libusb prints unconditionally). + "hci_transport_h2_libusb.c", # Same issue. We enable LOG_ERROR in btstack. + "USB Path: ", # Hardcoded in btstack's libusb transport. + "hci_number_completed_packet", # Warning from btstack. +) + + +class PyInstance: + def __init__(self): + pass + + def close(self): + pass + + def prepare_script_from_file(self, filename, prepend, append): + with open(filename, "rb") as f: + script = f.read() + if prepend: + script = bytes(prepend, "ascii") + b"\n" + script + if append: + script += b"\n" + bytes(append, "ascii") + return script + + def run_file(self, filename, prepend="", append=""): + return self.run_script(self.prepare_script_from_file(filename, prepend, append)) + + def start_file(self, filename, prepend="", append=""): + return self.start_script(self.prepare_script_from_file(filename, prepend, append)) + + +class PyInstanceSubProcess(PyInstance): + def __init__(self, argv, env=None): + self.argv = argv + self.env = {n: v for n, v in (i.split("=") for i in env)} if env else None + self.popen = None + self.finished = True + + def __str__(self): + return self.argv[0].rsplit("/")[-1] + + def run_script(self, script): + output = b"" + err = None + try: + p = subprocess.run( + self.argv, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + input=script, + env=self.env, + ) + output = p.stdout + except subprocess.CalledProcessError as er: + err = er + return str(output.strip(), "ascii"), err + + def start_script(self, script): + self.popen = subprocess.Popen( + self.argv, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=self.env, + ) + self.popen.stdin.write(script) + self.popen.stdin.close() + self.finished = False + + def stop(self): + if self.popen and self.popen.poll() is None: + self.popen.terminate() + + def readline(self): + sel = select.select([self.popen.stdout.raw], [], [], 0.1) + if not sel[0]: + self.finished = self.popen.poll() is not None + return None, None + out = self.popen.stdout.raw.readline() + if out == b"": + self.finished = self.popen.poll() is not None + return None, None + else: + return str(out.rstrip(), "ascii"), None + + def is_finished(self): + return self.finished + + def wait_finished(self): + self.popen.wait() + out = self.popen.stdout.read() + return str(out, "ascii"), "" + + +class PyInstancePyboard(PyInstance): + def __init__(self, device): + self.device = device + self.pyb = pyboard.Pyboard(device) + self.pyb.enter_raw_repl() + self.finished = True + + def __str__(self): + return self.device.rsplit("/")[-1] + + def close(self): + self.pyb.exit_raw_repl() + self.pyb.close() + + def run_script(self, script): + output = b"" + err = None + try: + self.pyb.enter_raw_repl() + output = self.pyb.exec_(script) + except pyboard.PyboardError as er: + err = er + return str(output.strip(), "ascii"), err + + def start_script(self, script): + self.pyb.enter_raw_repl() + self.pyb.exec_raw_no_follow(script) + self.finished = False + + def stop(self): + self.pyb.serial.write(b"\r\x03") + + def readline(self): + if self.finished: + return None, None + if self.pyb.serial.inWaiting() == 0: + return None, None + out = self.pyb.read_until(1, (b"\r\n", b"\x04")) + if out.endswith(b"\x04"): + self.finished = True + out = out[:-1] + err = str(self.pyb.read_until(1, b"\x04"), "ascii") + err = err[:-1] + if not out and not err: + return None, None + else: + err = None + return str(out.rstrip(), "ascii"), err + + def is_finished(self): + return self.finished + + def wait_finished(self): + out, err = self.pyb.follow(10, None) + return str(out, "ascii"), str(err, "ascii") + + +def prepare_test_file_list(test_files): + test_files2 = [] + for test_file in sorted(test_files): + num_instances = 0 + with open(test_file) as f: + for line in f: + m = re.match(r"def instance([0-9]+)\(\):", line) + if m: + num_instances = max(num_instances, int(m.group(1)) + 1) + test_files2.append((test_file, num_instances)) + return test_files2 + + +def trace_instance_output(instance_idx, line): + if cmd_args.trace_output: + t_ms = round((time.time() - trace_t0) * 1000) + print("{:6} i{} :".format(t_ms, instance_idx), line) + + +def run_test_on_instances(test_file, num_instances, instances): + global trace_t0 + trace_t0 = time.time() + + error = False + skip = False + injected_globals = "" + output = [[] for _ in range(num_instances)] + + if cmd_args.trace_output: + print("TRACE {}:".format("|".join(str(i) for i in instances))) + + # Start all instances running, in order, waiting until they signal they are ready + for idx in range(num_instances): + append_code = APPEND_CODE_TEMPLATE.format(injected_globals, idx) + instance = instances[idx] + instance.start_file(test_file, append=append_code) + last_read_time = time.time() + while True: + if instance.is_finished(): + break + out, err = instance.readline() + if out is None and err is None: + if time.time() > last_read_time + INSTANCE_READ_TIMEOUT_S: + output[idx].append("TIMEOUT") + error = True + break + time.sleep(0.1) + continue + last_read_time = time.time() + if out is not None and not any(m in out for m in IGNORE_OUTPUT_MATCHES): + trace_instance_output(idx, out) + if out.startswith("SET "): + injected_globals += out[4:] + "\n" + elif out == "SKIP": + skip = True + break + elif out == "NEXT": + break + else: + output[idx].append(out) + if err is not None: + trace_instance_output(idx, err) + output[idx].append(err) + error = True + + if error or skip: + break + + if not error and not skip: + # Capture output and wait for all instances to finish running + last_read_time = [time.time() for _ in range(num_instances)] + while True: + num_running = 0 + num_output = 0 + for idx in range(num_instances): + instance = instances[idx] + if instance.is_finished(): + continue + num_running += 1 + out, err = instance.readline() + if out is None and err is None: + if time.time() > last_read_time[idx] + INSTANCE_READ_TIMEOUT_S: + output[idx].append("TIMEOUT") + error = True + continue + num_output += 1 + last_read_time[idx] = time.time() + if out is not None and not any(m in out for m in IGNORE_OUTPUT_MATCHES): + trace_instance_output(idx, out) + output[idx].append(out) + if err is not None: + trace_instance_output(idx, err) + output[idx].append(err) + error = True + + if not num_output: + time.sleep(0.1) + if not num_running or error: + break + + # Stop all instances + for idx in range(num_instances): + instances[idx].stop() + + output_str = "" + for idx, lines in enumerate(output): + output_str += "--- instance{} ---\n".format(idx) + output_str += "\n".join(lines) + "\n" + + return error, skip, output_str + + +def run_tests(test_files, instances_truth, instances_test): + skipped_tests = [] + passed_tests = [] + failed_tests = [] + + for test_file, num_instances in test_files: + instances_str = "|".join(str(instances_test[i]) for i in range(num_instances)) + print("{} on {}: ".format(test_file, instances_str), end="") + if cmd_args.show_output or cmd_args.trace_output: + print() + sys.stdout.flush() + + # Run test on test instances + error, skip, output_test = run_test_on_instances(test_file, num_instances, instances_test) + + if not skip: + # Check if truth exists in a file, and read it in + test_file_expected = test_file + ".exp" + if os.path.isfile(test_file_expected): + with open(test_file_expected) as f: + output_truth = f.read() + else: + # Run test on truth instances to get expected output + _, _, output_truth = run_test_on_instances( + test_file, num_instances, instances_truth + ) + + if cmd_args.show_output: + print("### TEST ###") + print(output_test, end="") + if not skip: + print("### TRUTH ###") + print(output_truth, end="") + + # Print result of test + if skip: + print("skip") + skipped_tests.append(test_file) + elif output_test == output_truth: + print("pass") + passed_tests.append(test_file) + else: + print("FAIL") + failed_tests.append(test_file) + if not cmd_args.show_output: + print("### TEST ###") + print(output_test, end="") + print("### TRUTH ###") + print(output_truth, end="") + + if cmd_args.show_output: + print() + + print("{} tests performed".format(len(skipped_tests) + len(passed_tests) + len(failed_tests))) + print("{} tests passed".format(len(passed_tests))) + + if skipped_tests: + print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) + if failed_tests: + print("{} tests failed: {}".format(len(failed_tests), " ".join(failed_tests))) + + return not failed_tests + + +def main(): + global cmd_args + + cmd_parser = argparse.ArgumentParser(description="Run network tests for MicroPython") + cmd_parser.add_argument( + "-s", "--show-output", action="store_true", help="show test output after running" + ) + cmd_parser.add_argument( + "-t", "--trace-output", action="store_true", help="trace test output while running" + ) + cmd_parser.add_argument( + "-i", "--instance", action="append", default=[], help="instance(s) to run the tests on" + ) + cmd_parser.add_argument("files", nargs="+", help="input test files") + cmd_args = cmd_parser.parse_args() + + # clear search path to make sure tests use only builtin modules and those in extmod + os.environ["MICROPYPATH"] = os.pathsep + "../extmod" + + test_files = prepare_test_file_list(cmd_args.files) + max_instances = max(t[1] for t in test_files) + + instances_truth = [PyInstanceSubProcess([PYTHON_TRUTH]) for _ in range(max_instances)] + + instances_test = [] + for i in cmd_args.instance: + # Each instance arg is ,ENV=VAR,ENV=VAR... + i = i.split(",") + cmd = i[0] + env = i[1:] + if cmd.startswith("exec:"): + instances_test.append(PyInstanceSubProcess([cmd[len("exec:") :]], env)) + elif cmd == "micropython": + instances_test.append(PyInstanceSubProcess([MICROPYTHON], env)) + elif cmd == "cpython": + instances_test.append(PyInstanceSubProcess([CPYTHON3], env)) + elif cmd.startswith("pyb:"): + instances_test.append(PyInstancePyboard(cmd[len("pyb:") :])) + else: + print("unknown instance string: {}".format(cmd), file=sys.stderr) + sys.exit(1) + + for _ in range(max_instances - len(instances_test)): + instances_test.append(PyInstanceSubProcess([MICROPYTHON])) + + try: + all_pass = run_tests(test_files, instances_truth, instances_test) + finally: + for i in instances_truth: + i.close() + for i in instances_test: + i.close() + + if not all_pass: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index aeefa1480d..f88ca0b788 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -14,7 +14,7 @@ import pyboard # Paths for host executables CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") -MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython_coverage") +MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython-coverage") NATMOD_EXAMPLE_DIR = "../examples/natmod/" diff --git a/tests/run-tests b/tests/run-tests index 568390eba5..c1a2780dad 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -5,24 +5,35 @@ import subprocess import sys import platform import argparse +import inspect import re import threading import multiprocessing from multiprocessing.pool import ThreadPool from glob import glob +# See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] +# are guaranteed to always work, this one should though. +BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) + +def base_path(*p): + return os.path.abspath(os.path.join(BASEPATH, *p)).replace('\\', '/') + # Tests require at least CPython 3.3. If your default python3 executable # is of lower version, you can point MICROPY_CPYTHON3 environment var # to the correct executable. if os.name == 'nt': CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe') - MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/windows/micropython.exe') + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/windows/micropython.exe')) else: CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') - MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/unix/micropython') + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/unix/micropython')) # mpy-cross is only needed if --via-mpy command-line arg is passed -MPYCROSS = os.getenv('MICROPY_MPYCROSS', '../mpy-cross/mpy-cross') +MPYCROSS = os.getenv('MICROPY_MPYCROSS', base_path('../mpy-cross/mpy-cross')) + +# For diff'ing test output +DIFF = os.getenv('MICROPY_DIFF', 'diff -u') # Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale os.environ['PYTHONIOENCODING'] = 'utf-8' @@ -56,11 +67,12 @@ def run_micropython(pyb, args, test_file, is_special=False): special_tests = ( 'micropython/meminfo.py', 'basics/bytes_compare3.py', 'basics/builtin_help.py', 'thread/thread_exc2.py', + 'esp32/partition_ota.py', ) had_crash = False if pyb is None: # run on PC - if test_file.startswith(('cmdline/', 'feature_check/')) or test_file in special_tests: + if test_file.startswith(('cmdline/', base_path('feature_check/'))) or test_file in special_tests: # special handling for tests of the unix cmdline program is_special = True @@ -124,8 +136,8 @@ def run_micropython(pyb, args, test_file, is_special=False): os.close(subterminal) else: output_mupy = subprocess.check_output(args + [test_file], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - return b'CRASH' + except subprocess.CalledProcessError as error: + return error.output + b'CRASH' else: # a standard test run on PC @@ -137,17 +149,19 @@ def run_micropython(pyb, args, test_file, is_special=False): # if running via .mpy, first compile the .py file if args.via_mpy: - subprocess.check_output([MPYCROSS, '-mcache-lookup-bc', '-o', 'mpytest.mpy', '-X', 'emit=' + args.emit, test_file]) + subprocess.check_output([MPYCROSS] + args.mpy_cross_flags.split() + ['-o', 'mpytest.mpy', '-X', 'emit=' + args.emit, test_file]) cmdlist.extend(['-m', 'mpytest']) else: cmdlist.append(test_file) # run the actual test - e = {"MICROPYPATH": os.getcwd() + ":", "LANG": "en_US.UTF-8"} - p = subprocess.Popen(cmdlist, env=e, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output_mupy, _ = p.communicate() - if p.returncode != 0: - output_mupy = b'CRASH' + e = {"LANG": "en_US.UTF-8", + "MICROPYPATH": os.environ["MICROPYPATH"]} + try: + output_mupy = subprocess.check_output(cmdlist, env=e, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as error: + had_crash = True + output_mupy = error.output + b'CRASH' # clean up if we had an intermediate .mpy file if args.via_mpy: @@ -204,7 +218,7 @@ def run_micropython(pyb, args, test_file, is_special=False): if lines_exp[i][1].match(lines_mupy[i_mupy]): lines_mupy[i_mupy] = lines_exp[i][0] else: - print("don't match: %r %s" % (lines_exp[i][1], lines_mupy[i_mupy])) # DEBUG + # print("don't match: %r %s" % (lines_exp[i][1], lines_mupy[i_mupy])) # DEBUG pass i_mupy += 1 if i_mupy >= len(lines_mupy): @@ -215,7 +229,10 @@ def run_micropython(pyb, args, test_file, is_special=False): def run_feature_check(pyb, args, base_path, test_file): - return run_micropython(pyb, args, base_path + "/feature_check/" + test_file, is_special=True) + if pyb is not None and test_file.startswith("repl_"): + # REPL feature tests will not run via pyboard because they require prompt interactivity + return b"" + return run_micropython(pyb, args, base_path("feature_check", test_file), is_special=True) class ThreadSafeCounter: def __init__(self, start=0): @@ -232,7 +249,7 @@ class ThreadSafeCounter: def value(self): return self._value -def run_tests(pyb, tests, args, base_path=".", num_threads=1): +def run_tests(pyb, tests, args, result_dir, num_threads=1): test_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter() passed_count = ThreadSafeCounter() @@ -258,6 +275,10 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): # If we're asked to --list-tests, we can't assume that there's a # connection to target, so we can't run feature checks usefully. if not (args.list_tests or args.write_exp): + # Even if we run completely different tests in a different directory, + # we need to access feature_checks from the same directory as the + # run-tests script itself so use base_path. + # Check if micropython.native is supported, and skip such tests if it's not output = run_feature_check(pyb, args, base_path, 'native_check.py') if output == b'CRASH': @@ -305,14 +326,23 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, base_path, 'repl_emacs_check.py') - if not 'True' in str(t, 'ascii'): + if 'True' not in str(t, 'ascii'): skip_tests.add('cmdline/repl_emacs_keys.py') + # Check if words movement in repl is supported, and skip such tests if it's not + t = run_feature_check(pyb, args, base_path, 'repl_words_move_check.py') + if 'True' not in str(t, 'ascii'): + skip_tests.add('cmdline/repl_words_move.py') + upy_byteorder = run_feature_check(pyb, args, base_path, 'byteorder.py') - upy_float_precision = int(run_feature_check(pyb, args, base_path, 'float.py')) + upy_float_precision = run_feature_check(pyb, args, base_path, 'float.py') + if upy_float_precision == b'CRASH': + upy_float_precision = 0 + else: + upy_float_precision = int(upy_float_precision) has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n' has_coverage = run_feature_check(pyb, args, base_path, 'coverage.py') == b'coverage\n' - cpy_byteorder = subprocess.check_output([CPYTHON3, base_path + '/feature_check/byteorder.py']) + cpy_byteorder = subprocess.check_output([CPYTHON3, base_path('feature_check/byteorder.py')]) skip_endian = (upy_byteorder != cpy_byteorder) # These tests don't test slice explicitly but rather use it to perform the test @@ -360,6 +390,7 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): if not has_complex: skip_tests.add('float/complex1.py') skip_tests.add('float/complex1_intbig.py') + skip_tests.add('float/complex_special_methods.py') skip_tests.add('float/int_big_float.py') skip_tests.add('float/true_value.py') skip_tests.add('float/types.py') @@ -396,8 +427,6 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): skip_tests.add('micropython/opt_level.py') # don't assume line numbers are stored elif args.target == 'nrf': skip_tests.add('basics/memoryview1.py') # no item assignment for memoryview - skip_tests.add('extmod/ticks_diff.py') # unimplemented: utime.ticks_diff - skip_tests.add('extmod/time_ms_us.py') # unimplemented: utime.ticks_ms skip_tests.add('extmod/urandom_basic.py') # unimplemented: urandom.seed skip_tests.add('micropython/opt_level.py') # no support for line numbers skip_tests.add('misc/non_compliant.py') # no item assignment for bytearray @@ -418,16 +447,16 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): # Some tests are known to fail with native emitter # Remove them from the below when they work if args.emit == 'native': - skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_executing gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_name generator_pend_throw generator_return generator_send'.split()}) # require yield - skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join gen_stack_overflow'.split()}) # require yield - skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2 with_break with_return coroutine'.split()}) # require yield - skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs + skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from_close generator_name'.split()}) # require raise_varargs, generator name + skip_tests.update({'basics/async_%s.py' % t for t in 'with with2 with_break with_return'.split()}) # require yield + skip_tests.add('basics/annotate_var.py') # requires checking for unbound local skip_tests.add('basics/del_deref.py') # requires checking for unbound local skip_tests.add('basics/del_local.py') # requires checking for unbound local skip_tests.add('basics/exception_chain.py') # raise from is not supported skip_tests.add('basics/scope_implicit.py') # requires checking for unbound local skip_tests.add('basics/try_finally_return2.py') # requires raise_varargs skip_tests.add('basics/unboundlocal.py') # requires checking for unbound local + skip_tests.add('extmod/uasyncio_lock.py') # requires async with skip_tests.add('misc/features.py') # requires raise_varargs skip_tests.add('misc/print_exception.py') # because native doesn't have proper traceback info skip_tests.add('misc/sys_exc_info.py') # sys.exc_info() is not supported for native @@ -435,10 +464,7 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): skip_tests.add('micropython/heapalloc_traceback.py') # because native doesn't have proper traceback info skip_tests.add('micropython/opt_level_lineno.py') # native doesn't have proper traceback info skip_tests.add('micropython/schedule.py') # native code doesn't check pending events - skip_tests.add('stress/gc_trace.py') # requires yield - skip_tests.add('stress/recursive_gen.py') # requires yield - skip_tests.add('extmod/vfs_userfs.py') # because native doesn't properly handle globals across different modules - skip_tests.add('../extmod/ulab/tests/argminmax.py') # requires yield + # skip_tests.add('../extmod/ulab/tests/argminmax.py') # requires yield def run_one_test(test_file): test_file = test_file.replace('\\', '/') @@ -454,7 +480,7 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): test_basename = test_file.replace('..', '_').replace('./', '').replace('/', '_') test_name = os.path.splitext(os.path.basename(test_file))[0] - is_native = test_name.startswith("native_") or test_name.startswith("viper_") + is_native = test_name.startswith("native_") or test_name.startswith("viper_") or args.emit == "native" is_endian = test_name.endswith("_endian") is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig") is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") @@ -473,7 +499,7 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): skip_it |= skip_slice and is_slice skip_it |= skip_async and is_async skip_it |= skip_const and is_const - skip_it |= skip_revops and test_name.startswith("class_reverse_op") + skip_it |= skip_revops and "reverse_op" in test_name skip_it |= skip_io_module and is_io_module if args.list_tests: @@ -493,20 +519,17 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): with open(test_file_expected, 'rb') as f: output_expected = f.read() else: - # run CPython to work out expected output e = {"PYTHONPATH": os.getcwd(), "PATH": os.environ["PATH"], "LANG": "en_US.UTF-8"} - p = subprocess.Popen([CPYTHON3, '-B', test_file], env=e, stdout=subprocess.PIPE) - output_expected = b'' - while p.poll() is None: - output_expected += p.stdout.read() - output_expected += p.stdout.read() - if p.returncode != 0: - output_expected = b'CPYTHON3 CRASH' - elif args.write_exp: - with open(test_file_expected, 'wb') as f: - f.write(output_expected) + # run CPython to work out expected output + try: + output_expected = subprocess.check_output([CPYTHON3, '-B', test_file], env=e, stderr=subprocess.STDOUT) + if args.write_exp: + with open(test_file_expected, 'wb') as f: + f.write(output_expected) + except subprocess.CalledProcessError as error: + output_expected = error.output + b'CPYTHON3 CRASH' # canonical form for all host platforms is to use \n for end-of-line output_expected = output_expected.replace(b'\r\n', b'\n') @@ -524,11 +547,11 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): testcase_count.add(len(output_expected.splitlines())) - filename_expected = test_basename + ".exp" - filename_mupy = test_basename + ".out" + filename_expected = os.path.join(result_dir, test_basename + ".exp") + filename_mupy = os.path.join(result_dir, test_basename + ".out") if output_expected == output_mupy: - print("pass ", test_file) + # print("pass ", test_file) passed_count.add(1) rm_f(filename_expected) rm_f(filename_mupy) @@ -587,12 +610,23 @@ class append_filter(argparse.Action): def main(): cmd_parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, - description='Run and manage tests for MicroPython.', + description='''Run and manage tests for MicroPython. + +Tests are discovered by scanning test directories for .py files or using the +specified test files. If test files nor directories are specified, the script +expects to be ran in the tests directory (where this file is located) and the +builtin tests suitable for the target platform are ran. +When running tests, run-tests compares the MicroPython output of the test with the output +produced by running the test through CPython unless a .exp file is found, in which +case it is used as comparison. +If a test fails, run-tests produces a pair of .out and .exp files in the result +directory with the MicroPython output and the expectations, respectively. +''', epilog='''\ Options -i and -e can be multiple and processed in the order given. Regex "search" (vs "match") operation is used. An action (include/exclude) of the last matching regex is used: - run-tests -i async - exclude all, then include tests containg "async" anywhere + run-tests -i async - exclude all, then include tests containing "async" anywhere run-tests -e '/big.+int' - include all, then exclude by regex run-tests -e async -i async_foo - include all, exclude async, yet still include async_foo ''') @@ -602,26 +636,45 @@ the last matching regex is used: cmd_parser.add_argument('-u', '--user', default='micro', help='the telnet login username') cmd_parser.add_argument('-p', '--password', default='python', help='the telnet login password') cmd_parser.add_argument('-d', '--test-dirs', nargs='*', help='input test directories (if no files given)') + cmd_parser.add_argument('-r', '--result-dir', default=base_path('results'), help='directory for test results') cmd_parser.add_argument('-e', '--exclude', action=append_filter, metavar='REGEX', dest='filters', help='exclude test by regex on path/name.py') cmd_parser.add_argument('-i', '--include', action=append_filter, metavar='REGEX', dest='filters', help='include test by regex on path/name.py') - cmd_parser.add_argument('--write-exp', action='store_true', help='save .exp files to run tests w/o CPython') + cmd_parser.add_argument('--write-exp', action='store_true', help='use CPython to generate .exp files to run tests w/o CPython') cmd_parser.add_argument('--list-tests', action='store_true', help='list tests instead of running them') cmd_parser.add_argument('--emit', default='bytecode', help='MicroPython emitter to use (bytecode or native)') cmd_parser.add_argument('--heapsize', help='heapsize to use (use default if not specified)') cmd_parser.add_argument('--via-mpy', action='store_true', help='compile .py files to .mpy first') + cmd_parser.add_argument('--mpy-cross-flags', default='-mcache-lookup-bc', help='flags to pass to mpy-cross') cmd_parser.add_argument('--keep-path', action='store_true', help='do not clear MICROPYPATH when running tests') cmd_parser.add_argument('-j', '--jobs', default=1, metavar='N', type=int, help='Number of tests to run simultaneously') cmd_parser.add_argument('--auto-jobs', action='store_const', dest='jobs', const=multiprocessing.cpu_count(), help='Set the -j values to the CPU (thread) count') cmd_parser.add_argument('files', nargs='*', help='input test files') + cmd_parser.add_argument('--print-failures', action='store_true', help='print the diff of expected vs. actual output for failed tests and exit') + cmd_parser.add_argument('--clean-failures', action='store_true', help='delete the .exp and .out files from failed tests and exit') args = cmd_parser.parse_args() + if args.print_failures: + for exp in glob(os.path.join(args.result_dir, "*.exp")): + testbase = exp[:-4] + print() + print("FAILURE {0}".format(testbase)) + os.system("{0} {1}.exp {1}.out".format(DIFF, testbase)) + + sys.exit(0) + + if args.clean_failures: + for f in glob(os.path.join(args.result_dir, "*.exp")) + glob(os.path.join(args.result_dir, "*.out")): + os.remove(f) + + sys.exit(0) + LOCAL_TARGETS = ('unix', 'qemu-arm',) EXTERNAL_TARGETS = ('pyboard', 'wipy', 'esp8266', 'esp32', 'minimal', 'nrf') if args.target in LOCAL_TARGETS or args.list_tests: pyb = None elif args.target in EXTERNAL_TARGETS: global pyboard - sys.path.append('../tools') + sys.path.append(base_path('../tools')) import pyboard pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password) pyb.enter_raw_repl() @@ -657,15 +710,12 @@ the last matching regex is used: tests = args.files if not args.keep_path: - # clear search path to make sure tests use only builtin modules - os.environ['MICROPYPATH'] = '' + # clear search path to make sure tests use only builtin modules and those in extmod + os.environ['MICROPYPATH'] = os.pathsep + base_path('../extmod') + os.pathsep + base_path('.') - # Even if we run completely different tests in a different directory, - # we need to access feature_check's from the same directory as the - # run-tests script itself. - base_path = os.path.dirname(sys.argv[0]) or "." try: - res = run_tests(pyb, tests, args, base_path, args.jobs) + os.makedirs(args.result_dir, exist_ok=True) + res = run_tests(pyb, tests, args, args.result_dir, args.jobs) finally: if pyb: pyb.close() diff --git a/tests/thread/stress_aes.py b/tests/thread/stress_aes.py index 3191192a4d..2b974d5e51 100644 --- a/tests/thread/stress_aes.py +++ b/tests/thread/stress_aes.py @@ -19,266 +19,26 @@ # discrete arithmetic routines, mostly from a precomputed table # non-linear, invertible, substitution box -aes_s_box_table = bytes( - ( - 0x63, - 0x7C, - 0x77, - 0x7B, - 0xF2, - 0x6B, - 0x6F, - 0xC5, - 0x30, - 0x01, - 0x67, - 0x2B, - 0xFE, - 0xD7, - 0xAB, - 0x76, - 0xCA, - 0x82, - 0xC9, - 0x7D, - 0xFA, - 0x59, - 0x47, - 0xF0, - 0xAD, - 0xD4, - 0xA2, - 0xAF, - 0x9C, - 0xA4, - 0x72, - 0xC0, - 0xB7, - 0xFD, - 0x93, - 0x26, - 0x36, - 0x3F, - 0xF7, - 0xCC, - 0x34, - 0xA5, - 0xE5, - 0xF1, - 0x71, - 0xD8, - 0x31, - 0x15, - 0x04, - 0xC7, - 0x23, - 0xC3, - 0x18, - 0x96, - 0x05, - 0x9A, - 0x07, - 0x12, - 0x80, - 0xE2, - 0xEB, - 0x27, - 0xB2, - 0x75, - 0x09, - 0x83, - 0x2C, - 0x1A, - 0x1B, - 0x6E, - 0x5A, - 0xA0, - 0x52, - 0x3B, - 0xD6, - 0xB3, - 0x29, - 0xE3, - 0x2F, - 0x84, - 0x53, - 0xD1, - 0x00, - 0xED, - 0x20, - 0xFC, - 0xB1, - 0x5B, - 0x6A, - 0xCB, - 0xBE, - 0x39, - 0x4A, - 0x4C, - 0x58, - 0xCF, - 0xD0, - 0xEF, - 0xAA, - 0xFB, - 0x43, - 0x4D, - 0x33, - 0x85, - 0x45, - 0xF9, - 0x02, - 0x7F, - 0x50, - 0x3C, - 0x9F, - 0xA8, - 0x51, - 0xA3, - 0x40, - 0x8F, - 0x92, - 0x9D, - 0x38, - 0xF5, - 0xBC, - 0xB6, - 0xDA, - 0x21, - 0x10, - 0xFF, - 0xF3, - 0xD2, - 0xCD, - 0x0C, - 0x13, - 0xEC, - 0x5F, - 0x97, - 0x44, - 0x17, - 0xC4, - 0xA7, - 0x7E, - 0x3D, - 0x64, - 0x5D, - 0x19, - 0x73, - 0x60, - 0x81, - 0x4F, - 0xDC, - 0x22, - 0x2A, - 0x90, - 0x88, - 0x46, - 0xEE, - 0xB8, - 0x14, - 0xDE, - 0x5E, - 0x0B, - 0xDB, - 0xE0, - 0x32, - 0x3A, - 0x0A, - 0x49, - 0x06, - 0x24, - 0x5C, - 0xC2, - 0xD3, - 0xAC, - 0x62, - 0x91, - 0x95, - 0xE4, - 0x79, - 0xE7, - 0xC8, - 0x37, - 0x6D, - 0x8D, - 0xD5, - 0x4E, - 0xA9, - 0x6C, - 0x56, - 0xF4, - 0xEA, - 0x65, - 0x7A, - 0xAE, - 0x08, - 0xBA, - 0x78, - 0x25, - 0x2E, - 0x1C, - 0xA6, - 0xB4, - 0xC6, - 0xE8, - 0xDD, - 0x74, - 0x1F, - 0x4B, - 0xBD, - 0x8B, - 0x8A, - 0x70, - 0x3E, - 0xB5, - 0x66, - 0x48, - 0x03, - 0xF6, - 0x0E, - 0x61, - 0x35, - 0x57, - 0xB9, - 0x86, - 0xC1, - 0x1D, - 0x9E, - 0xE1, - 0xF8, - 0x98, - 0x11, - 0x69, - 0xD9, - 0x8E, - 0x94, - 0x9B, - 0x1E, - 0x87, - 0xE9, - 0xCE, - 0x55, - 0x28, - 0xDF, - 0x8C, - 0xA1, - 0x89, - 0x0D, - 0xBF, - 0xE6, - 0x42, - 0x68, - 0x41, - 0x99, - 0x2D, - 0x0F, - 0xB0, - 0x54, - 0xBB, - 0x16, - ) -) +# fmt: off +aes_s_box_table = bytes(( + 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16, +)) +# fmt: on # multiplication of polynomials modulo x^8 + x^4 + x^3 + x + 1 = 0x11b def aes_gf8_mul_2(x): diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py new file mode 100644 index 0000000000..c5a402b3a3 --- /dev/null +++ b/tests/thread/stress_schedule.py @@ -0,0 +1,49 @@ +# This test ensures that the scheduler doesn't trigger any assertions +# while dealing with concurrent access from multiple threads. + +import _thread +import utime +import micropython +import gc + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + +gc.disable() + +n = 0 # How many times the task successfully ran. + + +def task(x): + global n + n += 1 + + +def thread(): + while True: + try: + micropython.schedule(task, None) + except RuntimeError: + # Queue full, back off. + utime.sleep_ms(10) + + +for i in range(8): + _thread.start_new_thread(thread, ()) + +_NUM_TASKS = const(10000) +_TIMEOUT_MS = const(10000) + +# Wait up to 10 seconds for 10000 tasks to be scheduled. +t = utime.ticks_ms() +while n < _NUM_TASKS and utime.ticks_diff(utime.ticks_ms(), t) < _TIMEOUT_MS: + pass + +if n < _NUM_TASKS: + # Not all the tasks were scheduled, likely the scheduler stopped working. + print(n) +else: + print("PASS") diff --git a/tests/thread/stress_schedule.py.exp b/tests/thread/stress_schedule.py.exp new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/tests/thread/stress_schedule.py.exp @@ -0,0 +1 @@ +PASS diff --git a/tests/thread/thread_stacksize1.py b/tests/thread/thread_stacksize1.py index 0c6e7a92cd..7473c578ed 100644 --- a/tests/thread/thread_stacksize1.py +++ b/tests/thread/thread_stacksize1.py @@ -11,7 +11,7 @@ import _thread if sys.implementation.name == "micropython": sz = 2 * 1024 else: - sz = 32 * 1024 + sz = 512 * 1024 def foo(): diff --git a/tests/unicode/unicode_slice.py b/tests/unicode/unicode_slice.py new file mode 100644 index 0000000000..d9237088f8 --- /dev/null +++ b/tests/unicode/unicode_slice.py @@ -0,0 +1,12 @@ +# Test slicing of Unicode strings + +s = "Привет" + +print(s[:]) +print(s[2:]) +print(s[:5]) +print(s[2:5]) +print(s[2:5:1]) +print(s[2:10]) +print(s[-3:10]) +print(s[-4:10]) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 555715f526..39d7978fa5 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -53,6 +53,11 @@ data # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' +1 +2 +OverflowError: overflow converting long int to machine word +OverflowError: overflow converting long int to machine word +ValueError: Warning: test # format float ? @@ -74,6 +79,9 @@ unlocked 1 2 3 +KeyboardInterrupt: +KeyboardInterrupt: +10 # ringbuf 99 0 98 1 @@ -96,6 +104,27 @@ cc99 22ff -1 -1 +# pairheap +create: 0 0 0 0 +pop all: 0 1 2 3 +create: 7 6 5 4 3 2 1 0 +pop all: 0 1 2 3 4 5 6 7 +create: 1 - - 1 1 1 1 1 1 +pop all: 1 2 +create: 1 1 1 1 2 2 +pop all: 2 4 +create: 1 1 1 1 1 +pop all: 1 3 4 +create: 3 3 3 1 1 1 +pop all: 1 2 4 5 +# mp_obj_is_type +1 1 +0 0 +1 1 +1 1 +0 0 +1 1 +# end coverage.c 0123456789 b'0123456789' 7300 7300 diff --git a/tests/unix/time.py b/tests/unix/time.py new file mode 100644 index 0000000000..55a4b18aae --- /dev/null +++ b/tests/unix/time.py @@ -0,0 +1,59 @@ +try: + import utime as time +except ImportError: + import time + +DAYS_PER_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + +tzseconds = -time.mktime((1970, 1, 1, 14, 0, 0, 0, 0, 0)) + + +def is_leap(year): + return (year % 4) == 0 + + +def test(): + seconds = 0 + wday = 3 # Jan 1, 1970 was a Thursday + for year in range(1970, 2038): + print("Testing %d" % year) + yday = 1 + for month in range(1, 13): + if month == 2 and is_leap(year): + DAYS_PER_MONTH[2] = 29 + else: + DAYS_PER_MONTH[2] = 28 + for day in range(1, DAYS_PER_MONTH[month] + 1): + secs = time.mktime((year, month, day, 14, 0, 0, 0, 0, 0)) + tzseconds + if secs != seconds: + print( + "mktime failed for %d-%02d-%02d got %d expected %d" + % (year, month, day, secs, seconds) + ) + return + tuple = time.localtime(seconds) + secs = time.mktime(tuple) + if secs != seconds: + print( + "localtime failed for %d-%02d-%02d got %d expected %d" + % (year, month, day, secs, seconds) + ) + return + seconds += 86400 + if yday != tuple[7]: + print( + "locatime for %d-%02d-%02d got yday %d, expecting %d" + % (year, month, day, tuple[7], yday) + ) + return + if wday != tuple[6]: + print( + "locatime for %d-%02d-%02d got wday %d, expecting %d" + % (year, month, day, tuple[6], wday) + ) + return + yday += 1 + wday = (wday + 1) % 7 + + +test() diff --git a/tools/check_code_size.sh b/tools/check_code_size.sh deleted file mode 100755 index bdf1b5ad9b..0000000000 --- a/tools/check_code_size.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) -# -# SPDX-License-Identifier: MIT - -# -# This script check that changes don't lead to code size regressions. -# (Size of the language core (== minimal port should not grow)). -# - -REFERENCE=$HOME/persist/firmware.bin -#REFERENCE=/tmp/micropython -#TRAVIS_PULL_REQUEST=false - -if [ -f $REFERENCE ]; then - size_old=$(stat -c%s $REFERENCE) - size_new=$(stat -c%s ports/minimal/build/firmware.bin) - echo "Old size: $size_old new size: $size_new" - if [ $size_new -gt $size_old ]; then - echo "Validation failure: Core code size increased" - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - exit 1 - fi - fi -else - echo "Warning: reference file doesn't exist, code size check didn't run" -fi diff --git a/tools/codeformat.py b/tools/codeformat.py index 58e2113459..606d4947ba 100644 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -113,7 +113,10 @@ TOP = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) UNCRUSTIFY_CFG = os.path.join(TOP, "tools/uncrustify.cfg") -C_EXTS = (".c", ".h") +C_EXTS = ( + ".c", + ".h", +) PY_EXTS = (".py",) # Transform a filename argument relative to the current directory into one @@ -135,8 +138,10 @@ def fixup_c(filename): # Write out file with fixups. with open(filename, "w", newline="") as f: dedent_stack = [] + i = 0 while lines: # Get next line. + i += 1 l = lines.pop(0) # Revert "// |" back to "//| " @@ -146,6 +151,7 @@ def fixup_c(filename): # Dedent #'s to match indent of following line (not previous line). m = re.match(r"( +)#(if |ifdef |ifndef |elif |else|endif)", l) if m: + print(i, dedent_stack, m) indent = len(m.group(1)) directive = m.group(2) if directive in ("if ", "ifdef ", "ifndef "): @@ -158,15 +164,14 @@ def fixup_c(filename): else: # This #-line does not need dedenting. dedent_stack.append(-1) - else: - if dedent_stack: - if dedent_stack[-1] >= 0: - # This associated #-line needs dedenting to match the #if. - indent_diff = indent - dedent_stack[-1] - assert indent_diff >= 0 - l = l[indent_diff:] - if directive == "endif": - dedent_stack.pop() + elif dedent_stack: + if dedent_stack[-1] >= 0: + # This associated #-line needs dedenting to match the #if. + indent_diff = indent - dedent_stack[-1] + assert indent_diff >= 0 + l = l[indent_diff:] + if directive == "endif": + dedent_stack.pop() # Write out line. f.write(l) diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 69d16572f5..377f245596 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -76,9 +76,10 @@ def freeze(path, script=None, opt=0): If `script` is an iterable then freeze() is called on all items of the iterable (with the same `path` and `opt` passed through). - If `script` is a string then it specifies the filename to freeze, and - can include extra directories before the file. The file will be - searched for in `path`. + If `script` is a string then it specifies the file or directory to + freeze, and can include extra directories before the file or last + directory. The file or directory will be searched for in `path`. If + `script` is a directory then all files in that directory will be frozen. `opt` is the optimisation level to pass to mpy-cross when compiling .py to .mpy. @@ -163,17 +164,10 @@ def get_timestamp_newest(path): return ts_newest -def mkdir(path): - cur_path = "" - for p in path.split("/")[:-1]: - cur_path += p + "/" - try: - os.mkdir(cur_path) - except OSError as er: - if er.args[0] == 17: # file exists - pass - else: - raise er +def mkdir(filename): + path = os.path.dirname(filename) + if not os.path.isdir(path): + os.makedirs(path) def freeze_internal(kind, path, script, opt): @@ -182,14 +176,22 @@ def freeze_internal(kind, path, script, opt): if any(f[0] == KIND_AS_STR for f in manifest_list): raise FreezeError("can only freeze one str directory") manifest_list.append((KIND_AS_STR, path, script, opt)) - elif script is None: - for dirpath, dirnames, filenames in os.walk(path, followlinks=True): + elif script is None or isinstance(script, str) and script.find(".") == -1: + # Recursively search `path` for files to freeze, optionally restricted + # to a subdirectory specified by `script` + if script is None: + subdir = "" + else: + subdir = "/" + script + for dirpath, dirnames, filenames in os.walk(path + subdir, followlinks=True): for f in filenames: freeze_internal(kind, path, (dirpath + "/" + f)[len(path) + 1 :], opt) elif not isinstance(script, str): + # `script` is an iterable of items to freeze for s in script: freeze_internal(kind, path, s, opt) else: + # `script` should specify an individual file to be frozen extension_kind = {KIND_AS_MPY: ".py", KIND_MPY: ".mpy"} if kind == KIND_AUTO: for k, ext in extension_kind.items(): @@ -235,6 +237,9 @@ def main(): # Get paths to tools MAKE_FROZEN = VARS["MPY_DIR"] + "/tools/make-frozen.py" MPY_CROSS = VARS["MPY_DIR"] + "/mpy-cross/mpy-cross" + if sys.platform == "win32": + MPY_CROSS += ".exe" + MPY_CROSS = os.getenv("MICROPY_MPYCROSS", MPY_CROSS) MPY_TOOL = VARS["MPY_DIR"] + "/tools/mpy-tool.py" # Ensure mpy-cross is built @@ -275,7 +280,8 @@ def main(): + ["-o", outfile, "-s", script, "-O{}".format(opt), infile] ) if res != 0: - print("error compiling {}: {}".format(infile, out)) + print("error compiling {}:".format(infile)) + sys.stdout.buffer.write(out) raise SystemExit(1) ts_outfile = get_timestamp(outfile) mpy_files.append(outfile) @@ -298,13 +304,31 @@ def main(): sys.exit(1) # Freeze .mpy files - res, output_mpy = system( - [sys.executable, MPY_TOOL, "-f", "-q", args.build_dir + "/genhdr/qstrdefs.preprocessed.h"] - + mpy_files - ) - if res != 0: - print("error freezing mpy {}: {}".format(mpy_files, output_mpy)) - sys.exit(1) + if mpy_files: + res, output_mpy = system( + [ + sys.executable, + MPY_TOOL, + "-f", + "-q", + args.build_dir + "/genhdr/qstrdefs.preprocessed.h", + ] + + mpy_files + ) + if res != 0: + print("error freezing mpy {}:".format(mpy_files)) + print(str(output_mpy, "utf8")) + sys.exit(1) + else: + output_mpy = ( + b'#include "py/emitglue.h"\n' + b"extern const qstr_pool_t mp_qstr_const_pool;\n" + b"const qstr_pool_t mp_qstr_frozen_const_pool = {\n" + b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0\n" + b"};\n" + b'const char mp_frozen_mpy_names[1] = {"\\0"};\n' + b"const mp_raw_code_t *const mp_frozen_mpy_content[0] = {};\n" + ) # Generate output print("GEN", args.output) diff --git a/tools/metrics.py b/tools/metrics.py new file mode 100755 index 0000000000..25acb30f51 --- /dev/null +++ b/tools/metrics.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +# +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Damien P. George +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +""" +This script is used to compute metrics, like code size, of the various ports. + +Typical usage is: + + $ ./tools/metrics.py build | tee size0 + + $ git switch new-feature-branch + $ ./tools/metrics.py build | tee size1 + + $ ./tools/metrics.py diff size0 size1 + +Other commands: + + $ ./tools/metrics.py sizes # print all firmware sizes + $ ./tools/metrics.py clean # clean all ports + +""" + +import collections, sys, re, subprocess + +MAKE_FLAGS = ["-j3", "CFLAGS_EXTRA=-DNDEBUG"] + + +class PortData: + def __init__(self, name, dir, output, make_flags=None): + self.name = name + self.dir = dir + self.output = output + self.make_flags = make_flags + self.needs_mpy_cross = dir not in ("bare-arm", "minimal") + + +port_data = { + "b": PortData("bare-arm", "bare-arm", "build/firmware.elf"), + "m": PortData("minimal x86", "minimal", "build/firmware.elf"), + "u": PortData("unix x64", "unix", "micropython"), + "n": PortData("unix nanbox", "unix", "micropython-nanbox", "VARIANT=nanbox"), + "s": PortData("stm32", "stm32", "build-PYBV10/firmware.elf", "BOARD=PYBV10"), + "c": PortData("cc3200", "cc3200", "build/WIPY/release/application.axf", "BTARGET=application"), + "8": PortData("esp8266", "esp8266", "build-GENERIC/firmware.elf"), + "3": PortData("esp32", "esp32", "build-GENERIC/application.elf"), + "r": PortData("nrf", "nrf", "build-pca10040/firmware.elf"), + "d": PortData("samd", "samd", "build-ADAFRUIT_ITSYBITSY_M4_EXPRESS/firmware.elf"), +} + + +def syscmd(*args): + sys.stdout.flush() + a2 = [] + for a in args: + if isinstance(a, str): + a2.append(a) + elif a: + a2.extend(a) + subprocess.check_call(a2) + + +def parse_port_list(args): + if not args: + return list(port_data.values()) + else: + ports = [] + for arg in args: + for port_char in arg: + try: + ports.append(port_data[port_char]) + except KeyError: + print("unknown port:", port_char) + sys.exit(1) + return ports + + +def read_build_log(filename): + data = collections.OrderedDict() + lines = [] + found_sizes = False + with open(filename) as f: + for line in f: + line = line.strip() + if line.strip() == "COMPUTING SIZES": + found_sizes = True + elif found_sizes: + lines.append(line) + is_size_line = False + for line in lines: + if is_size_line: + fields = line.split() + data[fields[-1]] = [int(f) for f in fields[:-2]] + is_size_line = False + else: + is_size_line = line.startswith("text\t ") + return data + + +def do_diff(args): + """Compute the difference between firmware sizes.""" + + # Parse arguments. + error_threshold = None + if len(args) >= 2 and args[0] == "--error-threshold": + args.pop(0) + error_threshold = int(args.pop(0)) + + if len(args) != 2: + print("usage: %s diff [--error-threshold ] " % sys.argv[0]) + sys.exit(1) + + data1 = read_build_log(args[0]) + data2 = read_build_log(args[1]) + + max_delta = None + for key, value1 in data1.items(): + value2 = data2[key] + for port in port_data.values(): + if key == "ports/{}/{}".format(port.dir, port.output): + name = port.name + break + data = [v2 - v1 for v1, v2 in zip(value1, value2)] + warn = "" + board = re.search(r"/build-([A-Za-z0-9_]+)/", key) + if board: + board = board.group(1) + else: + board = "" + if name == "cc3200": + delta = data[0] + percent = 100 * delta / value1[0] + if data[1] != 0: + warn += " %+u(data)" % data[1] + else: + delta = data[3] + percent = 100 * delta / value1[3] + if data[1] != 0: + warn += " %+u(data)" % data[1] + if data[2] != 0: + warn += " %+u(bss)" % data[2] + if warn: + warn = "[incl%s]" % warn + print("%11s: %+5u %+.3f%% %s%s" % (name, delta, percent, board, warn)) + max_delta = delta if max_delta is None else max(max_delta, delta) + + if error_threshold is not None and max_delta is not None: + if max_delta > error_threshold: + sys.exit(1) + + +def do_clean(args): + """Clean ports.""" + + ports = parse_port_list(args) + + print("CLEANING") + for port in ports: + syscmd("make", "-C", "ports/{}".format(port.dir), port.make_flags, "clean") + + +def do_build(args): + """Build ports and print firmware sizes.""" + + ports = parse_port_list(args) + + if any(port.needs_mpy_cross for port in ports): + print("BUILDING MPY-CROSS") + syscmd("make", "-C", "mpy-cross", MAKE_FLAGS) + + print("BUILDING PORTS") + for port in ports: + syscmd("make", "-C", "ports/{}".format(port.dir), MAKE_FLAGS, port.make_flags) + + do_sizes(args) + + +def do_sizes(args): + """Compute and print sizes of firmware.""" + + ports = parse_port_list(args) + + print("COMPUTING SIZES") + for port in ports: + syscmd("size", "ports/{}/{}".format(port.dir, port.output)) + + +def main(): + # Get command to execute + if len(sys.argv) == 1: + print("Available commands:") + for cmd in globals(): + if cmd.startswith("do_"): + print(" {:9} {}".format(cmd[3:], globals()[cmd].__doc__)) + sys.exit(1) + cmd = sys.argv.pop(1) + + # Dispatch to desired command + try: + cmd = globals()["do_{}".format(cmd)] + except KeyError: + print("{}: unknown command '{}'".format(sys.argv[0], cmd)) + sys.exit(1) + cmd(sys.argv[1:]) + + +if __name__ == "__main__": + main() diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 0fbbce40f2..5c0f60dc78 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -234,6 +234,7 @@ class RawCode(object): self.ip, self.ip2, self.prelude = extract_prelude(self.bytecode, self.prelude_offset) self.simple_name = self._unpack_qstr(self.ip2) self.source_file = self._unpack_qstr(self.ip2 + 2) + self.line_info_offset = self.ip2 + 4 def _unpack_qstr(self, ip): qst = self.bytecode[ip] | self.bytecode[ip + 1] << 8 @@ -313,12 +314,13 @@ class RawCode(object): "#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B" ) print( - "STATIC const mp_obj_float_t %s = {{&mp_type_float}, %.16g};" % (obj_name, obj) + "STATIC const mp_obj_float_t %s = {{&mp_type_float}, (mp_float_t)%.16g};" + % (obj_name, obj) ) print("#endif") elif type(obj) is complex: print( - "STATIC const mp_obj_complex_t %s = {{&mp_type_complex}, %.16g, %.16g};" + "STATIC const mp_obj_complex_t %s = {{&mp_type_complex}, (mp_float_t)%.16g, (mp_float_t)%.16g};" % (obj_name, obj.real, obj.imag) ) else: @@ -384,7 +386,10 @@ class RawCode(object): print(" .n_def_pos_args = %u," % self.prelude[5]) print(" .qstr_block_name = %s," % self.simple_name.qstr_id) print(" .qstr_source_file = %s," % self.source_file.qstr_id) - print(" .line_info = fun_data_%s + %u," % (self.escaped_name, 0)) # TODO + print( + " .line_info = fun_data_%s + %u," + % (self.escaped_name, self.line_info_offset) + ) print(" .opcodes = fun_data_%s + %u," % (self.escaped_name, self.ip)) print(" },") print(" .line_of_definition = %u," % 0) # TODO diff --git a/tools/pyboard.py b/tools/pyboard.py index 82273eb5c1..17d5cd8092 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -51,6 +51,7 @@ Or: import sys import time import os +import ast try: stdout = sys.stdout.buffer @@ -407,7 +408,12 @@ class Pyboard: data = bytearray() self.exec_("print(r(%u))" % chunk_size, data_consumer=lambda d: data.extend(d)) assert data.endswith(b"\r\n\x04") - data = eval(str(data[:-3], "ascii")) + try: + data = ast.literal_eval(str(data[:-3], "ascii")) + if not isinstance(data, bytes): + raise ValueError("Not bytes") + except (UnicodeError, ValueError) as e: + raise PyboardError("fs_get: Could not interpret received data: %s" % str(e)) if not data: break f.write(data) @@ -538,12 +544,16 @@ def main(): cmd_parser = argparse.ArgumentParser(description="Run scripts on the pyboard.") cmd_parser.add_argument( + "-d", "--device", - default="/dev/ttyACM0", + default=os.environ.get("PYBOARD_DEVICE", "/dev/ttyACM0"), help="the serial device or the IP address of the pyboard", ) cmd_parser.add_argument( - "-b", "--baudrate", default=115200, help="the baud rate of the serial device" + "-b", + "--baudrate", + default=os.environ.get("PYBOARD_BAUDRATE", "115200"), + help="the baud rate of the serial device", ) cmd_parser.add_argument("-u", "--user", default="micro", help="the telnet login username") cmd_parser.add_argument("-p", "--password", default="python", help="the telnet login password") @@ -555,11 +565,17 @@ def main(): type=int, help="seconds to wait for USB connected board to become available", ) - cmd_parser.add_argument( + group = cmd_parser.add_mutually_exclusive_group() + group.add_argument( "--follow", action="store_true", help="follow the output after running the scripts [default if no scripts given]", ) + group.add_argument( + "--no-follow", + action="store_true", + help="Do not follow the output after running the scripts.", + ) cmd_parser.add_argument( "-f", "--filesystem", action="store_true", help="perform a filesystem action" ) @@ -586,7 +602,13 @@ def main(): def execbuffer(buf): try: - ret, ret_err = pyb.exec_raw(buf, timeout=None, data_consumer=stdout_write_bytes) + if args.no_follow: + pyb.exec_raw_no_follow(buf) + ret_err = None + else: + ret, ret_err = pyb.exec_raw( + buf, timeout=None, data_consumer=stdout_write_bytes + ) except PyboardError as er: print(er) pyb.close() diff --git a/tools/pydfu.py b/tools/pydfu.py index fd1988a9b3..609eec99ac 100755 --- a/tools/pydfu.py +++ b/tools/pydfu.py @@ -16,6 +16,7 @@ from __future__ import print_function import argparse import collections +import inspect import re import struct import sys @@ -54,6 +55,19 @@ __DFU_STATE_DFU_ERROR = 0x0A _DFU_DESCRIPTOR_TYPE = 0x21 +__DFU_STATUS_STR = { + __DFU_STATE_APP_IDLE: "STATE_APP_IDLE", + __DFU_STATE_APP_DETACH: "STATE_APP_DETACH", + __DFU_STATE_DFU_IDLE: "STATE_DFU_IDLE", + __DFU_STATE_DFU_DOWNLOAD_SYNC: "STATE_DFU_DOWNLOAD_SYNC", + __DFU_STATE_DFU_DOWNLOAD_BUSY: "STATE_DFU_DOWNLOAD_BUSY", + __DFU_STATE_DFU_DOWNLOAD_IDLE: "STATE_DFU_DOWNLOAD_IDLE", + __DFU_STATE_DFU_MANIFEST_SYNC: "STATE_DFU_MANIFEST_SYNC", + __DFU_STATE_DFU_MANIFEST: "STATE_DFU_MANIFEST", + __DFU_STATE_DFU_MANIFEST_WAIT_RESET: "STATE_DFU_MANIFEST_WAIT_RESET", + __DFU_STATE_DFU_UPLOAD_IDLE: "STATE_DFU_UPLOAD_IDLE", + __DFU_STATE_DFU_ERROR: "STATE_DFU_ERROR", +} # USB device handle __dev = None @@ -146,22 +160,32 @@ def clr_status(): def get_status(): """Get the status of the last operation.""" stat = __dev.ctrl_transfer(0xA1, __DFU_GETSTATUS, 0, __DFU_INTERFACE, 6, 20000) - # print (__DFU_STAT[stat[4]], stat) + + # firmware can provide an optional string for any error + if stat[5]: + message = get_string(__dev, stat[5]) + if message: + print(message) + return stat[4] +def check_status(stage, expected): + status = get_status() + if status != expected: + raise SystemExit("DFU: %s failed (%s)" % (stage, __DFU_STATUS_STR.get(status, status))) + + def mass_erase(): - """Performs a MASS erase (i.e. erases the entire device.""" + """Performs a MASS erase (i.e. erases the entire device).""" # Send DNLOAD with first byte=0x41 __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, "\x41", __TIMEOUT) # Execute last command - if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: - raise Exception("DFU: erase failed") + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_BUSY) # Check command state - if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: - raise Exception("DFU: erase failed") + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_IDLE) def page_erase(addr): @@ -174,13 +198,10 @@ def page_erase(addr): __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT) # Execute last command - if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: - raise Exception("DFU: erase failed") + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_BUSY) # Check command state - if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: - - raise Exception("DFU: erase failed") + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_IDLE) def set_address(addr): @@ -190,12 +211,10 @@ def set_address(addr): __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT) # Execute last command - if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: - raise Exception("DFU: set address failed") + check_status("set address", __DFU_STATE_DFU_DOWNLOAD_BUSY) # Check command state - if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: - raise Exception("DFU: set address failed") + check_status("set address", __DFU_STATE_DFU_DOWNLOAD_IDLE) def write_memory(addr, buf, progress=None, progress_addr=0, progress_size=0): @@ -227,12 +246,10 @@ def write_memory(addr, buf, progress=None, progress_addr=0, progress_size=0): ) # Execute last command - if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: - raise Exception("DFU: write memory failed") + check_status("write memory", __DFU_STATE_DFU_DOWNLOAD_BUSY) # Check command state - if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: - raise Exception("DFU: write memory failed") + check_status("write memory", __DFU_STATE_DFU_DOWNLOAD_IDLE) xfer_count += 1 xfer_bytes += chunk @@ -252,12 +269,10 @@ def write_page(buf, xfer_offset): __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 2, __DFU_INTERFACE, buf, __TIMEOUT) # Execute last command - if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: - raise Exception("DFU: write memory failed") + check_status("write memory", __DFU_STATE_DFU_DOWNLOAD_BUSY) # Check command state - if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: - raise Exception("DFU: write memory failed") + check_status("write memory", __DFU_STATE_DFU_DOWNLOAD_IDLE) if __verbose: print("Write: 0x%x " % (xfer_base + xfer_offset)) @@ -265,8 +280,7 @@ def write_page(buf, xfer_offset): def exit_dfu(): """Exit DFU mode, and start running the program.""" - - # set jump address + # Set jump address set_address(0x08000000) # Send DNLOAD with 0 length to exit DFU @@ -292,6 +306,7 @@ def consume(fmt, data, names): """Parses the struct defined by `fmt` from `data`, stores the parsed fields into a named tuple using `names`. Returns the named tuple, and the data with the struct stripped off.""" + size = struct.calcsize(fmt) return named(struct.unpack(fmt, data[:size]), names), data[size:] @@ -310,9 +325,9 @@ def read_dfu_file(filename): """Reads a DFU file, and parses the individual elements from the file. Returns an array of elements. Each element is a dictionary with the following keys: - num - The element index + num - The element index. address - The address that the element data should be written to. - size - The size of the element ddata. + size - The size of the element data. data - The element data. If an error occurs while parsing the file, then None is returned. """ @@ -326,10 +341,10 @@ def read_dfu_file(filename): # Decode the DFU Prefix # # <5sBIB - # < little endian + # < little endian Endianness # 5s char[5] signature "DfuSe" # B uint8_t version 1 - # I uint32_t size Size of the DFU file (not including suffix) + # I uint32_t size Size of the DFU file (without suffix) # B uint8_t targets Number of targets dfu_prefix, data = consume("<5sBIB", data, "signature version size targets") print( @@ -340,12 +355,12 @@ def read_dfu_file(filename): # Decode the Image Prefix # # <6sBI255s2I - # < little endian + # < little endian Endianness # 6s char[6] signature "Target" # B uint8_t altsetting - # I uint32_t named bool indicating if a name was used - # 255s char[255] name name of the target - # I uint32_t size size of image (not incl prefix) + # I uint32_t named Bool indicating if a name was used + # 255s char[255] name Name of the target + # I uint32_t size Size of image (without prefix) # I uint32_t elements Number of elements in the image img_prefix, data = consume( "<6sBI255s2I", data, "signature altsetting named name " "size elements" @@ -361,12 +376,15 @@ def read_dfu_file(filename): ) target_size = img_prefix["size"] - target_data, data = data[:target_size], data[target_size:] + target_data = data[:target_size] + data = data[target_size:] for elem_idx in range(img_prefix["elements"]): # Decode target prefix - # < little endian - # I uint32_t element address - # I uint32_t element size + # + # <2I + # < little endian Endianness + # I uint32_t element Address + # I uint32_t element Size elem_prefix, target_data = consume("<2I", target_data, "addr size") elem_prefix["num"] = elem_idx print(" %(num)d, address: 0x%(addr)08x, size: %(size)d" % elem_prefix) @@ -380,14 +398,16 @@ def read_dfu_file(filename): print("target %d PARSE ERROR" % target_idx) # Decode DFU Suffix - # < little endian - # H uint16_t device Firmware version + # + # <4H3sBI + # < little endian Endianness + # H uint16_t device Firmware version # H uint16_t product # H uint16_t vendor - # H uint16_t dfu 0x11a (DFU file format version) - # 3s char[3] ufd 'UFD' - # B uint8_t len 16 - # I uint32_t crc32 + # H uint16_t dfu 0x11a (DFU file format version) + # 3s char[3] ufd "UFD" + # B uint8_t len 16 + # I uint32_t crc32 Checksum dfu_suffix = named( struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc" ) @@ -418,23 +438,25 @@ class FilterDFU(object): def get_dfu_devices(*args, **kwargs): - """Returns a list of USB device which are currently in DFU mode. - Additional filters (like idProduct and idVendor) can be passed in to - refine the search. + """Returns a list of USB devices which are currently in DFU mode. + Additional filters (like idProduct and idVendor) can be passed in + to refine the search. """ - # convert to list for compatibility with newer pyusb + + # Convert to list for compatibility with newer PyUSB return list(usb.core.find(*args, find_all=True, custom_match=FilterDFU(), **kwargs)) def get_memory_layout(device): """Returns an array which identifies the memory layout. Each entry of the array will contain a dictionary with the following keys: - addr - Address of this memory segment + addr - Address of this memory segment. last_addr - Last address contained within the memory segment. - size - size of the segment, in bytes - num_pages - number of pages in the segment - page_size - size of each page, in bytes + size - Size of the segment, in bytes. + num_pages - Number of pages in the segment. + page_size - Size of each page, in bytes. """ + cfg = device[0] intf = cfg[(0, 0)] mem_layout_str = get_string(device, intf.iInterface) @@ -469,8 +491,7 @@ def list_dfu_devices(*args, **kwargs): """Prints a lits of devices detected in DFU mode.""" devices = get_dfu_devices(*args, **kwargs) if not devices: - print("No DFU capable devices found") - return + raise SystemExit("No DFU capable devices found") for device in devices: print( "Bus {} Device {:03d}: ID {:04x}:{:04x}".format( @@ -543,18 +564,22 @@ def cli_progress(addr, offset, size): def main(): """Test program for verifying this files functionality.""" global __verbose + global __VID + global __PID # Parse CMD args parser = argparse.ArgumentParser(description="DFU Python Util") - # parser.add_argument("path", help="file path") parser.add_argument( "-l", "--list", help="list available DFU devices", action="store_true", default=False ) + parser.add_argument("--vid", help="USB Vendor ID", type=lambda x: int(x, 0), default=__VID) + parser.add_argument("--pid", help="USB Product ID", type=lambda x: int(x, 0), default=__PID) parser.add_argument( "-m", "--mass-erase", help="mass erase device", action="store_true", default=False ) parser.add_argument( "-u", "--upload", help="read file from DFU device", dest="path", default=False ) + parser.add_argument("-x", "--exit", help="Exit DFU", action="store_true", default=False) parser.add_argument( "-v", "--verbose", help="increase output verbosity", action="store_true", default=False ) @@ -562,28 +587,42 @@ def main(): __verbose = args.verbose + __VID = args.vid + __PID = args.pid + if args.list: list_dfu_devices(idVendor=__VID, idProduct=__PID) return init() + command_run = False if args.mass_erase: print("Mass erase...") mass_erase() + command_run = True if args.path: elements = read_dfu_file(args.path) if not elements: + print("No data in dfu file") return print("Writing memory...") write_elements(elements, args.mass_erase, progress=cli_progress) print("Exiting DFU...") exit_dfu() - return + command_run = True - print("No command specified") + if args.exit: + print("Exiting DFU...") + exit_dfu() + command_run = True + + if command_run: + print("Finished") + else: + print("No command specified") if __name__ == "__main__": diff --git a/tools/tinytest-codegen.py b/tools/tinytest-codegen.py index b223c7f963..abc6f74d91 100755 --- a/tools/tinytest-codegen.py +++ b/tools/tinytest-codegen.py @@ -110,6 +110,8 @@ exclude_tests = ( # different filename in output "micropython/emg_exc.py", "micropython/heapalloc_traceback.py", + # don't have emergency exception buffer + "micropython/heapalloc_exc_compressed_emg_exc.py", # pattern matching in .exp "micropython/meminfo.py", # needs sys stdfiles