diff --git a/.gitignore b/.gitignore index 50bd30e87..565f6231b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,17 @@ user.props # Generated rst files ###################### genrst/ + + +lib/asf4/ +lib/axtls/ +lib/berkeley-db-1.xx/ +lib/btstack/ +lib/libffi/ +lib/lwip/ +lib/mbedtls/ +lib/mynewt-nimble/ +lib/nrfx/ +lib/nxp_driver/ +lib/stm32lib/ +lib/tinyusb/ diff --git a/.gitmodules b/.gitmodules index c152e9e77..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,38 +0,0 @@ -[submodule "lib/axtls"] - path = lib/axtls - url = https://github.com/pfalcon/axtls - branch = micropython -[submodule "lib/libffi"] - path = lib/libffi - url = https://github.com/atgreen/libffi -[submodule "lib/lwip"] - path = lib/lwip - url = https://git.savannah.gnu.org/r/lwip.git -[submodule "lib/berkeley-db-1.xx"] - path = lib/berkeley-db-1.xx - url = https://github.com/pfalcon/berkeley-db-1.xx -[submodule "lib/stm32lib"] - path = lib/stm32lib - url = https://github.com/micropython/stm32lib - branch = work-F4-1.13.1+F7-1.5.0+L4-1.3.0 -[submodule "lib/nrfx"] - path = lib/nrfx - url = https://github.com/NordicSemiconductor/nrfx.git -[submodule "lib/mbedtls"] - path = lib/mbedtls - url = https://github.com/ARMmbed/mbedtls.git -[submodule "lib/asf4"] - path = lib/asf4 - url = https://github.com/adafruit/asf4 -[submodule "lib/tinyusb"] - path = lib/tinyusb - url = https://github.com/hathach/tinyusb -[submodule "lib/mynewt-nimble"] - path = lib/mynewt-nimble - url = https://github.com/apache/mynewt-nimble.git -[submodule "lib/btstack"] - path = lib/btstack - url = https://github.com/bluekitchen/btstack.git -[submodule "lib/nxp_driver"] - path = lib/nxp_driver - url = https://github.com/hathach/nxp_driver.git diff --git a/CODECONVENTIONS.md b/CODECONVENTIONS.md index b18c9818a..78fb912a6 100644 --- a/CODECONVENTIONS.md +++ b/CODECONVENTIONS.md @@ -27,9 +27,12 @@ change beyond 5 lines would likely require such detailed description. To get good practical examples of good commits and their messages, browse the `git log` of the project. -MicroPython doesn't require explicit sign-off for patches ("Signed-off-by" -lines and similar). Instead, the commit message, and your name and email -address on it construes your sign-off of the following: +When committing you are encouraged to sign-off your commit by adding +"Signed-off-by" lines and similar, eg using "git commit -s". If you don't +explicitly sign-off in this way then the commit message, which includes your +name and email address in the "Author" line, implies your sign-off. In either +case, of explicit or implicit sign-off, you are certifying and signing off +against the following: * That you wrote the change yourself, or took it from a project with a compatible license (in the latter case the commit message, and possibly @@ -43,8 +46,11 @@ address on it construes your sign-off of the following: copyright for your changes (for smaller changes, the commit message conveys your copyright; if you make significant changes to a particular source module, you're welcome to add your name to the file header). -* Your signature for all of the above, which is the 'Author' line in - the commit message, and which should include your full real name and +* Your contribution including commit message will be publicly and + indefinitely available for anyone to access, including redistribution + under the terms of the project's license. +* Your signature for all of the above, which is the "Signed-off-by" line + or the "Author" line in the commit message, includes your full real name and a valid and active email address by which you can be contacted in the foreseeable future. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06f970607..73004ac51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,6 @@ make sure that you are acquainted with Contributor Guidelines: https://github.com/micropython/micropython/wiki/ContributorGuidelines -and Code Conventions: +as well as the Code Conventions, which includes details of how to commit: https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md diff --git a/base.type b/base.type new file mode 100644 index 000000000..e69de29bb diff --git a/docs/Makefile b/docs/Makefile index e9c128e90..05709617c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. PYTHON = python3 -SPHINXOPTS = +SPHINXOPTS = -W --keep-going SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build/$(MICROPY_PORT) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 8861ca4ac..d5c222f3a 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -118,17 +118,21 @@ Use the :mod:`time ` module:: Timers ------ -Virtual (RTOS-based) timers are supported. Use the :ref:`machine.Timer ` class -with timer ID of -1:: +The ESP32 port has four hardware timers. Use the :ref:`machine.Timer ` class +with a timer ID from 0 to 3 (inclusive):: from machine import Timer - tim = Timer(-1) - tim.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1)) - tim.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(2)) + tim0 = Timer(0) + tim0.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(0)) + + tim1 = Timer(1) + tim1.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(1)) The period is in milliseconds. +Virtual timers are not currently supported on this port. + .. _Pins_and_GPIO: Pins and GPIO @@ -172,9 +176,10 @@ PWM (pulse width modulation) PWM can be enabled on all output-enabled pins. The base frequency can range from 1Hz to 40MHz but there is a tradeoff; as the base frequency -*increases* the duty resolution *decreases*. See +*increases* the duty resolution *decreases*. See `LED Control `_ for more details. +Currently the duty cycle has to be in the range of 0-1023. Use the ``machine.PWM`` class:: @@ -272,7 +277,7 @@ class:: .. Warning:: Currently *all* of ``sck``, ``mosi`` and ``miso`` *must* be specified when - initialising Software SPI. + initialising Software SPI. Hardware SPI bus ---------------- @@ -444,11 +449,11 @@ Use the ``TouchPad`` class in the ``machine`` module:: from machine import TouchPad, Pin t = TouchPad(Pin(14)) - t.read() # Returns a smaller number when touched + t.read() # Returns a smaller number when touched ``TouchPad.read`` returns a value relative to the capacitive variation. Small numbers (typically in -the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when -no touch is present. However the values are *relative* and can vary depending on the board +the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when +no touch is present. However the values are *relative* and can vary depending on the board and surrounding composition so some calibration may be required. There are ten capacitive touch-enabled pins that can be used on the ESP32: 0, 2, 4, 12, 13 diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 9c7db3205..c884d93fc 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -138,6 +138,45 @@ Also note that Pin(16) is a special pin (used for wakeup from deepsleep mode) and may be not available for use with higher-level classes like ``Neopixel``. +UART (serial bus) +----------------- + +See :ref:`machine.UART `. :: + + from machine import UART + uart = UART(0, baudrate=9600) + uart.write('hello') + uart.read(5) # read up to 5 bytes + +Two UARTs are available. UART0 is on Pins 1 (TX) and 3 (RX). UART0 is +bidirectional, and by default is used for the REPL. UART1 is on Pins 2 +(TX) and 8 (RX) however Pin 8 is used to connect the flash chip, so +UART1 is TX only. + +When UART0 is attached to the REPL, all incoming chars on UART(0) go +straight to stdin so uart.read() will always return None. Use +sys.stdin.read() if it's needed to read characters from the UART(0) +while it's also used for the REPL (or detach, read, then reattach). +When detached the UART(0) can be used for other purposes. + +If there are no objects in any of the dupterm slots when the REPL is +started (on hard or soft reset) then UART(0) is automatically attached. +Without this, the only way to recover a board without a REPL would be to +completely erase and reflash (which would install the default boot.py which +attaches the REPL). + +To detach the REPL from UART0, use:: + + import uos + uos.dupterm(None, 1) + +The REPL is attached by default. If you have detached it, to reattach +it use:: + + import uos, machine + uart = machine.UART(0, 115200) + uos.dupterm(uart, 1) + PWM (pulse width modulation) ---------------------------- diff --git a/docs/library/esp.rst b/docs/library/esp.rst index cb2bc7af8..b9ae57bd9 100644 --- a/docs/library/esp.rst +++ b/docs/library/esp.rst @@ -31,7 +31,7 @@ Functions The system enters the set sleep mode automatically when possible. -.. function:: deepsleep(time=0, /) +.. function:: deepsleep(time_us=0, /) **Note**: ESP8266 only - use `machine.deepsleep()` on ESP32 diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index f3be3692e..dfde840a2 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -84,9 +84,9 @@ methods to enable over-the-air (OTA) updates. Returns a 6-tuple ``(type, subtype, addr, size, label, encrypted)``. .. method:: Partition.readblocks(block_num, buf) -.. method:: Partition.readblocks(block_num, buf, offset) + Partition.readblocks(block_num, buf, offset) .. method:: Partition.writeblocks(block_num, buf) -.. method:: Partition.writeblocks(block_num, buf, offset) + Partition.writeblocks(block_num, buf, offset) .. method:: Partition.ioctl(cmd, arg) These methods implement the simple and :ref:`extended @@ -151,6 +151,11 @@ used to transmit or receive many other types of digital signals:: r = esp32.RMT(0, pin=Pin(18), clock_div=8) r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + + # To use carrier frequency + r = esp32.RMT(0, pin=Pin(18), clock_div=8, carrier_freq=38000) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8, carrier_freq=38000, carrier_duty_percent=50) + # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns @@ -164,6 +169,9 @@ define the pulses. multiplying the resolution by a 15-bit (0-32,768) number. There are eight channels (0-7) and each can have a different clock divider. +To enable the carrier frequency feature of the esp32 hardware, specify the +``carrier_freq`` as something like 38000, a typical IR carrier frequency. + So, in the example above, the 80MHz clock is divided by 8. Thus the resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns, @@ -174,17 +182,20 @@ For more details see Espressif's `ESP-IDF RMT documentation. .. Warning:: The current MicroPython RMT implementation lacks some features, most notably - receiving pulses and carrier transmit. RMT should be considered a + receiving pulses. RMT should be considered a *beta feature* and the interface may change in the future. -.. class:: RMT(channel, \*, pin=None, clock_div=8) +.. class:: RMT(channel, \*, pin=None, clock_div=8, carrier_freq=0, carrier_duty_percent=50) This class provides access to one of the eight RMT channels. *channel* is required and identifies which RMT channel (0-7) will be configured. *pin*, also required, configures which Pin is bound to the RMT channel. *clock_div* is an 8-bit clock divider that divides the source clock (80MHz) to the RMT - channel allowing the resolution to be specified. + channel allowing the resolution to be specified. *carrier_freq* is used to + enable the carrier feature and specify its frequency, default value is ``0`` + (not enabled). To enable, specify a positive integer. *carrier_duty_percent* + defaults to 50. .. method:: RMT.source_freq() @@ -198,19 +209,21 @@ For more details see Espressif's `ESP-IDF RMT documentation. .. method:: RMT.wait_done(timeout=0) - Returns True if `RMT.write_pulses` has completed. + Returns ``True`` if the channel is currently transmitting a stream of pulses + started with a call to `RMT.write_pulses`. If *timeout* (defined in ticks of ``source_freq / clock_div``) is specified - the method will wait for *timeout* or until `RMT.write_pulses` is complete, - returning ``False`` if the channel continues to transmit. - -.. Warning:: - Avoid using ``wait_done()`` if looping is enabled. + the method will wait for *timeout* or until transmission is complete, + returning ``False`` if the channel continues to transmit. If looping is + enabled with `RMT.loop` and a stream has started, then this method will + always (wait and) return ``False``. .. method:: RMT.loop(enable_loop) - Configure looping on the channel, allowing a stream of pulses to be - indefinitely repeated. *enable_loop* is bool, set to True to enable looping. + Configure looping on the channel. *enable_loop* is bool, set to ``True`` to + enable looping on the *next* call to `RMT.write_pulses`. If called with + ``False`` while a looping stream is currently being transmitted then the + current set of pulses will be completed before transmission stops. .. method:: RMT.write_pulses(pulses, start) @@ -219,6 +232,15 @@ For more details see Espressif's `ESP-IDF RMT documentation. resolution ``(1 / (source_freq / clock_div))``. *start* defines whether the stream starts at 0 or 1. + If transmission of a stream is currently in progress then this method will + block until transmission of that stream has ended before beginning sending + *pulses*. + + If looping is enabled with `RMT.loop`, the stream of pulses will be repeated + indefinitely. Further calls to `RMT.write_pulses` will end the previous + stream - blocking until the last set of pulses has been transmitted - + before starting the next stream. + Ultra-Low-Power co-processor ---------------------------- diff --git a/docs/library/index.rst b/docs/library/index.rst index 7b1636ce6..952e67d43 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -23,7 +23,7 @@ into MicroPython. There are a few categories of such modules: * Modules which implement a subset of Python functionality, with a provision for extension by the user (via Python code). * Modules which implement MicroPython extensions to the Python standard libraries. -* Modules specific to a particular `MicroPython port` and thus not portable. +* Modules specific to a particular :term:`MicroPython port` and thus not portable. Note about the availability of the modules and their contents: This documentation in general aspires to describe all modules and functions/classes which are @@ -38,7 +38,7 @@ in a module (or even the entire module) described in this documentation **may be unavailable** in a particular build of MicroPython on a particular system. The best place to find general information of the availability/non-availability of a particular feature is the "General Information" section which contains -information pertaining to a specific `MicroPython port`. +information pertaining to a specific :term:`MicroPython port`. On some ports you are able to discover the available, built-in libraries that can be imported by entering the following at the REPL:: diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index ded52deb0..7106a1a2f 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -100,7 +100,7 @@ Functions unlocked. Note: `heap_locked()` is not enabled on most ports by default, - requires `MICROPY_PY_MICROPYTHON_HEAP_LOCKED`. + requires ``MICROPY_PY_MICROPYTHON_HEAP_LOCKED``. .. function:: kbd_intr(chr) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 46a27a8fe..885a4460c 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -101,7 +101,7 @@ Methods nic.ifconfig(('192.168.0.4', '255.255.255.0', '192.168.0.1', '8.8.8.8')) .. method:: WLAN.config('param') -.. method:: WLAN.config(param=value, ...) + WLAN.config(param=value, ...) Get or set general network interface parameters. These methods allow to work with additional parameters beyond standard IP configuration (as dealt with by @@ -117,7 +117,7 @@ Methods print(ap.config('channel')) Following are commonly supported parameters (availability of a specific parameter - depends on network technology type, driver, and `MicroPython port`). + depends on network technology type, driver, and :term:`MicroPython port`). ============= =========== Parameter Description diff --git a/docs/library/network.rst b/docs/library/network.rst index 01e6f6136..208c58f85 100644 --- a/docs/library/network.rst +++ b/docs/library/network.rst @@ -39,7 +39,7 @@ Common network adapter interface ================================ This section describes an (implied) abstract base class for all network -interface classes implemented by `MicroPython ports ` +interface classes implemented by :term:`MicroPython ports ` for different hardware. This means that MicroPython does not actually provide ``AbstractNIC`` class, but any actual NIC class, as described in the following sections, implements methods as described here. diff --git a/docs/library/pyb.Flash.rst b/docs/library/pyb.Flash.rst index 2d2f83da1..13f209787 100644 --- a/docs/library/pyb.Flash.rst +++ b/docs/library/pyb.Flash.rst @@ -26,6 +26,7 @@ Constructors This constructor is deprecated and will be removed in a future version of MicroPython. .. class:: pyb.Flash(\*, start=-1, len=-1) + :noindex: Create and return a block device that accesses the flash at the specified offset. The length defaults to the remaining size of the device. @@ -35,9 +36,9 @@ Methods ------- .. method:: Flash.readblocks(block_num, buf) -.. method:: Flash.readblocks(block_num, buf, offset) + Flash.readblocks(block_num, buf, offset) .. method:: Flash.writeblocks(block_num, buf) -.. method:: Flash.writeblocks(block_num, buf, offset) + Flash.writeblocks(block_num, buf, offset) .. method:: Flash.ioctl(cmd, arg) These methods implement the simple and :ref:`extended diff --git a/docs/library/pyb.SPI.rst b/docs/library/pyb.SPI.rst index a1910be49..181bdd3b6 100644 --- a/docs/library/pyb.SPI.rst +++ b/docs/library/pyb.SPI.rst @@ -64,6 +64,7 @@ Methods respectively. - ``bits`` can be 8 or 16, and is the number of bits in each transferred word. - ``firstbit`` can be ``SPI.MSB`` or ``SPI.LSB``. + - ``ti`` True indicates Texas Instruments, as opposed to Motorola, signal conventions. - ``crc`` can be None for no CRC, or a polynomial specifier. Note that the SPI clock frequency will not always be the requested baudrate. diff --git a/docs/library/pyb.Timer.rst b/docs/library/pyb.Timer.rst index bb7e7e52d..532b64da8 100644 --- a/docs/library/pyb.Timer.rst +++ b/docs/library/pyb.Timer.rst @@ -62,7 +62,7 @@ Constructors Methods ------- -.. method:: Timer.init(\*, freq, prescaler, period) +.. method:: Timer.init(\*, freq, prescaler, period, mode=Timer.UP, div=1, callback=None, deadtime=0) Initialise the timer. Initialisation must be either by frequency (in Hz) or by prescaler and period:: diff --git a/docs/library/pyb.UART.rst b/docs/library/pyb.UART.rst index ab7ab2fb8..0a611a725 100644 --- a/docs/library/pyb.UART.rst +++ b/docs/library/pyb.UART.rst @@ -48,13 +48,16 @@ Constructors .. class:: pyb.UART(bus, ...) - Construct a UART object on the given bus. ``bus`` can be 1-6, or 'XA', 'XB', 'YA', or 'YB'. + Construct a UART object on the given bus. + For Pyboard ``bus`` can be 1-4, 6, 'XA', 'XB', 'YA', or 'YB'. + For Pyboard Lite ``bus`` can be 1, 2, 6, 'XB', or 'YA'. + For Pyboard D ``bus`` can be 1-4, 'XA', 'YA' or 'YB'. With no additional parameters, the UART object is created but not initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. - The physical pins of the UART busses are: + The physical pins of the UART busses on Pyboard are: - ``UART(4)`` is on ``XA``: ``(TX, RX) = (X1, X2) = (PA0, PA1)`` - ``UART(1)`` is on ``XB``: ``(TX, RX) = (X9, X10) = (PB6, PB7)`` @@ -62,10 +65,22 @@ Constructors - ``UART(3)`` is on ``YB``: ``(TX, RX) = (Y9, Y10) = (PB10, PB11)`` - ``UART(2)`` is on: ``(TX, RX) = (X3, X4) = (PA2, PA3)`` - The Pyboard Lite supports UART(1), UART(2) and UART(6) only. Pins are as above except: + The Pyboard Lite supports UART(1), UART(2) and UART(6) only, pins are: + - ``UART(1)`` is on ``XB``: ``(TX, RX) = (X9, X10) = (PB6, PB7)`` + - ``UART(6)`` is on ``YA``: ``(TX, RX) = (Y1, Y2) = (PC6, PC7)`` - ``UART(2)`` is on: ``(TX, RX) = (X1, X2) = (PA2, PA3)`` + The Pyboard D supports UART(1), UART(2), UART(3) and UART(4) only, pins are: + + - ``UART(4)`` is on ``XA``: ``(TX, RX) = (X1, X2) = (PA0, PA1)`` + - ``UART(1)`` is on ``YA``: ``(TX, RX) = (Y1, Y2) = (PA9, PA10)`` + - ``UART(3)`` is on ``YB``: ``(TX, RX) = (Y9, Y10) = (PB10, PB11)`` + - ``UART(2)`` is on: ``(TX, RX) = (X3, X4) = (PA2, PA3)`` + + *Note:* Pyboard D has ``UART(1)`` on ``YA``, unlike Pyboard and Pyboard Lite that both + have ``UART(1)`` on ``XB`` and ``UART(6)`` on ``YA``. + Methods ------- diff --git a/docs/library/uasyncio.rst b/docs/library/uasyncio.rst index 0f363a076..31b38a4e0 100644 --- a/docs/library/uasyncio.rst +++ b/docs/library/uasyncio.rst @@ -74,6 +74,12 @@ Additional functions 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 diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index fd2efe87d..e103932ef 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -48,7 +48,8 @@ Configuration - ``'mac'``: Returns the device MAC address. If a device has a fixed address (e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random address will be generated when the BLE interface is made active. - Note: on some ports, accessing this value requires that the interface is + + **Note:** on some ports, accessing this value requires that the interface is active (so that the MAC address can be queried from the controller). - ``'gap_name'``: Get/set the GAP device name used by service 0x1800, @@ -65,21 +66,18 @@ Configuration Event Handling -------------- -.. method:: BLE.irq(handler, trigger=0xffff) +.. method:: BLE.irq(handler) Registers a callback for events from the BLE stack. The *handler* takes two arguments, ``event`` (which will be one of the codes below) and ``data`` (which is an event-specific tuple of values). - The optional *trigger* parameter allows you to set a mask of events that - your program is interested in. The default is all events. - - Note: the ``addr``, ``adv_data``, ``char_data``, ``notify_data``, and - ``uuid`` entries in the tuples are - references to data managed by the :mod:`ubluetooth` module (i.e. the same - instance will be re-used across multiple calls to the event handler). If - your program wants to use this data outside of the handler, then it must - copy them first, e.g. by using ``bytes(addr)`` or ``bluetooth.UUID(uuid)``. + **Note:** the ``addr``, ``adv_data``, ``char_data``, ``notify_data``, and + ``uuid`` entries in the tuples are references to data managed by the + :mod:`ubluetooth` module (i.e. the same instance will be re-used across + multiple calls to the event handler). If your program wants to use this + data outside of the handler, then it must copy them first, e.g. by using + ``bytes(addr)`` or ``bluetooth.UUID(uuid)``. An event handler showing all possible events:: @@ -101,7 +99,7 @@ Event Handling elif event == _IRQ_SCAN_RESULT: # A single scan result. addr_type, addr, adv_type, rssi, adv_data = data - elif event == _IRQ_SCAN_COMPLETE: + elif event == _IRQ_SCAN_DONE: # Scan duration finished or manually stopped. pass elif event == _IRQ_PERIPHERAL_CONNECT: @@ -113,17 +111,36 @@ Event Handling elif event == _IRQ_GATTC_SERVICE_RESULT: # Called for each service found by gattc_discover_services(). conn_handle, start_handle, end_handle, uuid = data + elif event == _IRQ_GATTC_SERVICE_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: # Called for each characteristic found by gattc_discover_services(). conn_handle, def_handle, value_handle, properties, uuid = data + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_DESCRIPTOR_RESULT: # Called for each descriptor found by gattc_discover_descriptors(). conn_handle, dsc_handle, uuid = data + elif event == _IRQ_GATTC_DESCRIPTOR_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_READ_RESULT: # A gattc_read() has completed. conn_handle, value_handle, char_data = data - elif event == _IRQ_GATTC_WRITE_STATUS: + elif event == _IRQ_GATTC_READ_DONE: + # A gattc_read() has completed. + # Note: The value_handle will be zero on btstack (but present on NimBLE). + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, value_handle, status = data + elif event == _IRQ_GATTC_WRITE_DONE: # A gattc_write() has completed. + # Note: The value_handle will be zero on btstack (but present on NimBLE). + # Note: Status will be zero on success, implementation-specific value otherwise. conn_handle, value_handle, status = data elif event == _IRQ_GATTC_NOTIFY: # A peripheral has sent a notify request. @@ -131,25 +148,34 @@ Event Handling elif event == _IRQ_GATTC_INDICATE: # A peripheral has sent an indicate request. conn_handle, value_handle, notify_data = data + elif event == _IRQ_GATTS_INDICATE_DONE: + # A central has acknowledged the indication. + # Note: Status will be zero on successful acknowledgment, implementation-specific value otherwise. + conn_handle, value_handle, status = data The event codes are:: from micropython import const - _IRQ_CENTRAL_CONNECT = const(1 << 0) - _IRQ_CENTRAL_DISCONNECT = const(1 << 1) - _IRQ_GATTS_WRITE = const(1 << 2) - _IRQ_GATTS_READ_REQUEST = const(1 << 3) - _IRQ_SCAN_RESULT = const(1 << 4) - _IRQ_SCAN_COMPLETE = const(1 << 5) - _IRQ_PERIPHERAL_CONNECT = const(1 << 6) - _IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) - _IRQ_GATTC_SERVICE_RESULT = const(1 << 8) - _IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) - _IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10) - _IRQ_GATTC_READ_RESULT = const(1 << 11) - _IRQ_GATTC_WRITE_STATUS = const(1 << 12) - _IRQ_GATTC_NOTIFY = const(1 << 13) - _IRQ_GATTC_INDICATE = const(1 << 14) + _IRQ_CENTRAL_CONNECT = const(1) + _IRQ_CENTRAL_DISCONNECT = const(2) + _IRQ_GATTS_WRITE = const(3) + _IRQ_GATTS_READ_REQUEST = const(4) + _IRQ_SCAN_RESULT = const(5) + _IRQ_SCAN_DONE = const(6) + _IRQ_PERIPHERAL_CONNECT = const(7) + _IRQ_PERIPHERAL_DISCONNECT = const(8) + _IRQ_GATTC_SERVICE_RESULT = const(9) + _IRQ_GATTC_SERVICE_DONE = const(10) + _IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) + _IRQ_GATTC_CHARACTERISTIC_DONE = const(12) + _IRQ_GATTC_DESCRIPTOR_RESULT = const(13) + _IRQ_GATTC_DESCRIPTOR_DONE = const(14) + _IRQ_GATTC_READ_RESULT = const(15) + _IRQ_GATTC_READ_DONE = const(16) + _IRQ_GATTC_WRITE_DONE = const(17) + _IRQ_GATTC_NOTIFY = const(18) + _IRQ_GATTC_INDICATE = const(19) + _IRQ_GATTS_INDICATE_DONE = const(20) In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your @@ -169,7 +195,7 @@ Broadcaster Role (Advertiser) protocol (e.g. ``bytes``, ``bytearray``, ``str``). *adv_data* is included in all broadcasts, and *resp_data* is send in reply to an active scan. - Note: if *adv_data* (or *resp_data*) is ``None``, then the data passed + **Note:** if *adv_data* (or *resp_data*) is ``None``, then the data passed to the previous call to ``gap_advertise`` will be re-used. This allows a broadcaster to resume advertising with just ``gap_advertise(interval_us)``. To clear the advertising payload pass an empty ``bytes``, i.e. ``b''``. @@ -203,7 +229,7 @@ Observer Role (Scanner) * 0x04 - SCAN_RSP - scan response When scanning is stopped (either due to the duration finishing or when - explicitly stopped), the ``_IRQ_SCAN_COMPLETE`` event will be raised. + explicitly stopped), the ``_IRQ_SCAN_DONE`` event will be raised. Peripheral Role (GATT Server) @@ -260,8 +286,8 @@ writes from a central to a given characteristic, use ( (hr,), (tx, rx,), ) = bt.gatts_register_services(SERVICES) The three value handles (``hr``, ``tx``, ``rx``) can be used with - :meth:`gatts_read `, :meth:`gatts_write `, - and :meth:`gatts_notify `. + :meth:`gatts_read `, :meth:`gatts_write `, :meth:`gatts_notify `, and + :meth:`gatts_indicate `. **Note:** Advertising must be stopped before registering services. @@ -276,12 +302,24 @@ writes from a central to a given characteristic, use .. method:: BLE.gatts_notify(conn_handle, value_handle, [data]) - Notifies a connected central that this value has changed and that it should - issue a read of the current value from this peripheral. + Sends a notification request to a connected central. + + If *data* is specified, then that value is sent to the central as part of + the notification. The local value will not be modified. + + Otherwise, if *data* is not specified, then the current local value (as + set with :meth:`gatts_write `) will be sent. - If *data* is specified, then the that value is sent to the central as part - of the notification, avoiding the need for a separate read request. Note - that this will not update the local value stored. +.. method:: BLE.gatts_indicate(conn_handle, value_handle) + + Sends an indication request to a connected central. + + **Note:** This does not currently support sending a custom value, it will + always send the current local value (as set with :meth:`gatts_write + `). + + On acknowledgment (or failure, e.g. timeout), the + ``_IRQ_GATTS_INDICATE_DONE`` event will be raised. .. method:: BLE.gatts_set_buffer(value_handle, len, append=False, /) @@ -313,33 +351,42 @@ Central Role (GATT Client) Returns ``False`` if the connection handle wasn't connected, and ``True`` otherwise. -.. method:: BLE.gattc_discover_services(conn_handle) +.. method:: BLE.gattc_discover_services(conn_handle, [uuid]) Query a connected peripheral for its services. - For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will be - raised. + Optionally specify a service *uuid* to query for that service only. -.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle) + For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will + be raised, followed by ``_IRQ_GATTC_SERVICE_DONE`` on completion. + +.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle, [uuid]) Query a connected peripheral for characteristics in the specified range. + Optionally specify a characteristic *uuid* to query for that + characteristic only. + + You can use ``start_handle=1``, ``end_handle=0xffff`` to search for a + characteristic in any service. + For each characteristic discovered, the ``_IRQ_GATTC_CHARACTERISTIC_RESULT`` - event will be raised. + event will be raised, followed by ``_IRQ_GATTC_CHARACTERISTIC_DONE`` on completion. .. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle) Query a connected peripheral for descriptors in the specified range. For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event - will be raised. + will be raised, followed by ``_IRQ_GATTC_DESCRIPTOR_DONE`` on completion. .. method:: BLE.gattc_read(conn_handle, value_handle) Issue a remote read to a connected peripheral for the specified characteristic or descriptor handle. - On success, the ``_IRQ_GATTC_READ_RESULT`` event will be raised. + When a value is available, the ``_IRQ_GATTC_READ_RESULT`` event will be + raised. Additionally, the ``_IRQ_GATTC_READ_DONE`` will be raised. .. method:: BLE.gattc_write(conn_handle, value_handle, data, mode=0, /) @@ -357,7 +404,7 @@ Central Role (GATT Client) data. If a response is received from the remote peripheral the - ``_IRQ_GATTC_WRITE_STATUS`` event will be raised. + ``_IRQ_GATTC_WRITE_DONE`` event will be raised. class UUID diff --git a/docs/library/uerrno.rst b/docs/library/uerrno.rst index e336eb5c5..def01362f 100644 --- a/docs/library/uerrno.rst +++ b/docs/library/uerrno.rst @@ -7,7 +7,7 @@ |see_cpython_module| :mod:`python:errno`. This module provides access to symbolic error codes for `OSError` exception. -A particular inventory of codes depends on `MicroPython port`. +A particular inventory of codes depends on :term:`MicroPython port`. Constants --------- @@ -16,7 +16,7 @@ Constants Error codes, based on ANSI C/POSIX standard. All error codes start with "E". As mentioned above, inventory of the codes depends on - `MicroPython port`. Errors are usually accessible as ``exc.args[0]`` + :term:`MicroPython port`. Errors are usually accessible as ``exc.args[0]`` where ``exc`` is an instance of `OSError`. Usage example:: try: diff --git a/docs/library/uio.rst b/docs/library/uio.rst index 1a64b3658..dddb83a17 100644 --- a/docs/library/uio.rst +++ b/docs/library/uio.rst @@ -114,7 +114,9 @@ Classes Get the current contents of the underlying buffer which holds data. .. class:: StringIO(alloc_size) + :noindex: .. class:: BytesIO(alloc_size) + :noindex: Create an empty `StringIO`/`BytesIO` object, preallocated to hold up to *alloc_size* number of bytes. That means that writing that amount diff --git a/docs/library/uos.rst b/docs/library/uos.rst index c49b13a5f..ad10d9129 100644 --- a/docs/library/uos.rst +++ b/docs/library/uos.rst @@ -252,7 +252,7 @@ that the block device supports the extended interface. dependent on the specific block device. .. method:: readblocks(block_num, buf) - .. method:: readblocks(block_num, buf, offset) + readblocks(block_num, buf, offset) The first form reads aligned, multiples of blocks. Starting at the block given by the index *block_num*, read blocks from @@ -267,7 +267,7 @@ that the block device supports the extended interface. The number of bytes to read is given by the length of *buf*. .. method:: writeblocks(block_num, buf) - .. method:: writeblocks(block_num, buf, offset) + writeblocks(block_num, buf, offset) The first form writes aligned, multiples of blocks, and requires that the blocks that are written to be first erased (if necessary) by this method. diff --git a/docs/library/ure.rst b/docs/library/ure.rst index ca5f35b70..e94d28617 100644 --- a/docs/library/ure.rst +++ b/docs/library/ure.rst @@ -138,12 +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 `MicroPython port`.) + (Availability depends on :term:`MicroPython port`.) .. _regex: @@ -184,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]) @@ -193,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/usocket.rst b/docs/library/usocket.rst index e3b9d279a..bc4b4b6d5 100644 --- a/docs/library/usocket.rst +++ b/docs/library/usocket.rst @@ -37,8 +37,8 @@ power) and portable way to work with addresses. However, ``socket`` module (note the difference with native MicroPython ``usocket`` module described here) provides CPython-compatible way to specify addresses using tuples, as described below. Note that depending on a -`MicroPython port`, ``socket`` module can be builtin or need to be -installed from `micropython-lib` (as in the case of `MicroPython Unix port`), +:term:`MicroPython port`, ``socket`` module can be builtin or need to be +installed from `micropython-lib` (as in the case of :term:`MicroPython Unix port`), and some ports still accept only numeric addresses in the tuple format, and require to use `getaddrinfo` function to resolve domain names. @@ -61,7 +61,7 @@ Tuple address format for ``socket`` module: must be 0. *scopeid* is the interface scope identifier for link-local addresses. Note the domain names are not accepted as *ipv6_address*, they should be resolved first using `usocket.getaddrinfo()`. Availability - of IPv6 support depends on a `MicroPython port`. + of IPv6 support depends on a :term:`MicroPython port`. Functions --------- @@ -81,7 +81,7 @@ Functions .. function:: getaddrinfo(host, port, af=0, type=0, proto=0, flags=0, /) - Translate the host/port argument into a sequence of 5-tuples that contain all the + Translate the host/port argument into a sequence of 5-tuples that contain all the necessary arguments for creating a socket connected to that service. Arguments *af*, *type*, and *proto* (which have the same meaning as for the `socket()` function) can be used to filter which kind of addresses are returned. If a parameter is not @@ -141,7 +141,7 @@ Constants .. data:: AF_INET AF_INET6 - Address family types. Availability depends on a particular `MicroPython port`. + Address family types. Availability depends on a particular :term:`MicroPython port`. .. data:: SOCK_STREAM SOCK_DGRAM @@ -151,7 +151,7 @@ Constants .. data:: IPPROTO_UDP IPPROTO_TCP - IP protocol numbers. Availability depends on a particular `MicroPython port`. + IP protocol numbers. Availability depends on a particular :term:`MicroPython port`. Note that you don't need to specify these in a call to `usocket.socket()`, because `SOCK_STREAM` socket type automatically selects `IPPROTO_TCP`, and `SOCK_DGRAM` - `IPPROTO_UDP`. Thus, the only real use of these constants @@ -160,12 +160,12 @@ Constants .. data:: usocket.SOL_* Socket option levels (an argument to `setsockopt()`). The exact - inventory depends on a `MicroPython port`. + inventory depends on a :term:`MicroPython port`. .. data:: usocket.SO_* Socket options (an argument to `setsockopt()`). The exact - inventory depends on a `MicroPython port`. + inventory depends on a :term:`MicroPython port`. Constants specific to WiPy: @@ -185,7 +185,7 @@ Methods on the socket object will fail. The remote end will receive EOF indication if supported by protocol. - Sockets are automatically closed when they are garbage-collected, but it is recommended + Sockets are automatically closed when they are garbage-collected, but it is recommended to `close()` them explicitly as soon you finished working with them. .. method:: socket.bind(address) @@ -259,7 +259,7 @@ Methods completed. If zero is given, the socket is put in non-blocking mode. If None is given, the socket is put in blocking mode. - Not every `MicroPython port` supports this method. A more portable and + Not every :term:`MicroPython port` supports this method. A more portable and generic solution is to use `uselect.poll` object. This allows to wait on multiple objects at the same time (and not just on sockets, but on generic `stream` objects which support polling). Example:: diff --git a/docs/library/ussl.rst b/docs/library/ussl.rst index be84dc054..ffe146331 100644 --- a/docs/library/ussl.rst +++ b/docs/library/ussl.rst @@ -24,7 +24,7 @@ Functions :meth:`~usocket.socket.accept()` on a non-SSL listening server socket. Depending on the underlying module implementation in a particular - `MicroPython port`, some or all keyword arguments above may be not supported. + :term:`MicroPython port`, some or all keyword arguments above may be not supported. .. warning:: diff --git a/docs/library/usys.rst b/docs/library/usys.rst new file mode 100644 index 000000000..960164385 --- /dev/null +++ b/docs/library/usys.rst @@ -0,0 +1,142 @@ +:mod:`usys` -- system specific functions +======================================== + +.. module:: usys + :synopsis: system specific functions + +|see_cpython_module| :mod:`python:sys`. + +Functions +--------- + +.. 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 + value given as an argument to `SystemExit`. + +.. function:: atexit(func) + + Register *func* to be called upon termination. *func* must be a callable + that takes no arguments, or ``None`` to disable the call. The ``atexit`` + function will return the previous value set by this function, which is + initially ``None``. + + .. admonition:: Difference to CPython + :class: attention + + This function is a MicroPython extension intended to provide similar + functionality to the :mod:`atexit` module in CPython. + +.. function:: print_exception(exc, file=usys.stdout, /) + + Print exception with a traceback to a file-like object *file* (or + `usys.stdout` by default). + + .. admonition:: Difference to CPython + :class: attention + + This is simplified version of a function which appears in the + ``traceback`` module in CPython. Unlike ``traceback.print_exception()``, + this function takes just exception value instead of exception type, + exception value, and traceback object; *file* argument should be + positional; further arguments are not supported. CPython-compatible + ``traceback`` module can be found in `micropython-lib`. + +Constants +--------- + +.. data:: argv + + A mutable list of arguments the current program was started with. + +.. data:: byteorder + + The byte order of the system (``"little"`` or ``"big"``). + +.. data:: implementation + + Object with information about the current Python implementation. For + MicroPython, it has following attributes: + + * *name* - string "micropython" + * *version* - tuple (major, minor, micro), e.g. (1, 7, 0) + + This object is the recommended way to distinguish MicroPython from other + Python implementations (note that it still may not exist in the very + minimal ports). + + .. admonition:: Difference to CPython + :class: attention + + CPython mandates more attributes for this object, but the actual useful + bare minimum is implemented in MicroPython. + +.. data:: maxsize + + Maximum value which a native integer type can hold on the current platform, + or maximum value representable by MicroPython integer type, if it's smaller + than platform max value (that is the case for MicroPython ports without + long int support). + + This attribute is useful for detecting "bitness" of a platform (32-bit vs + 64-bit, etc.). It's recommended to not compare this attribute to some + value directly, but instead count number of bits in it:: + + bits = 0 + v = usys.maxsize + while v: + bits += 1 + v >>= 1 + if bits > 32: + # 64-bit (or more) platform + ... + else: + # 32-bit (or less) platform + # Note that on 32-bit platform, value of bits may be less than 32 + # (e.g. 31) due to peculiarities described above, so use "> 16", + # "> 32", "> 64" style of comparisons. + +.. data:: modules + + Dictionary of loaded modules. On some ports, it may not include builtin + modules. + +.. data:: path + + A mutable list of directories to search for imported modules. + +.. data:: platform + + The platform that MicroPython is running on. For OS/RTOS ports, this is + usually an identifier of the OS, e.g. ``"linux"``. For baremetal ports it + is an identifier of a board, e.g. ``"pyboard"`` for the original MicroPython + reference board. It thus can be used to distinguish one board from another. + If you need to check whether your program runs on MicroPython (vs other + Python implementation), use `usys.implementation` instead. + +.. data:: stderr + + Standard error `stream`. + +.. data:: stdin + + Standard input `stream`. + +.. data:: stdout + + Standard output `stream`. + +.. data:: version + + Python language version that this implementation conforms to, as a string. + +.. data:: version_info + + Python language version that this implementation conforms to, as a tuple of ints. + + .. admonition:: Difference to CPython + :class: attention + + Only the first three version numbers (major, minor, micro) are supported and + they can be referenced only by index, not by name. diff --git a/docs/make.bat b/docs/make.bat index 44f968279..c09487fb7 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -6,6 +6,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build +set SPHINXOPTS=-W --keep-going set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 43217493e..2854df23a 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -14,8 +14,8 @@ packages: 1. Python modules and packages are turned into distribution package archives, and published at the Python Package Index (PyPI). -2. `upip` package manager can be used to install a distribution package - on a `MicroPython port` with networking capabilities (for example, +2. :term:`upip` package manager can be used to install a distribution package + on a :term:`MicroPython port` with networking capabilities (for example, on the Unix port). 3. For ports without networking capabilities, an "installation image" can be prepared on the Unix port, and transferred to a device by @@ -51,14 +51,14 @@ even by the smallest devices. Besides the small compression dictionary size, MicroPython distribution packages also have other optimizations, like removing any files from the archive which aren't used by the installation process. In particular, -`upip` package manager doesn't execute ``setup.py`` during installation +:term:`upip` package manager doesn't execute ``setup.py`` during installation (see below), and thus that file is not included in the archive. At the same time, these optimizations make MicroPython distribution -packages not compatible with `CPython`'s package manager, ``pip``. +packages not compatible with :term:`CPython`'s package manager, ``pip``. This isn't considered a big problem, because: -1. Packages can be installed with `upip`, and then can be used with +1. Packages can be installed with :term:`upip`, and then can be used with CPython (if they are compatible with it). 2. In the other direction, majority of CPython packages would be incompatible with MicroPython by various reasons, first of all, @@ -73,12 +73,12 @@ resource constrained devices. ------------------------ MicroPython distribution packages are intended to be installed using -the `upip` package manager. `upip` is a Python application which is +the :term:`upip` package manager. :term:`upip` is a Python application which is usually distributed (as frozen bytecode) with network-enabled -`MicroPython ports `. At the very least, -`upip` is available in the `MicroPython Unix port`. +:term:`MicroPython ports `. At the very least, +:term:`upip` is available in the :term:`MicroPython Unix port`. -On any `MicroPython port` providing `upip`, it can be accessed as +On any :term:`MicroPython port` providing :term:`upip`, it can be accessed as following:: import upip @@ -123,12 +123,12 @@ commands which corresponds to the example above are:: Cross-installing packages ------------------------- -For `MicroPython ports ` without native networking +For :term:`MicroPython ports ` without native networking capabilities, the recommend process is "cross-installing" them into a -"directory image" using the `MicroPython Unix port`, and then +"directory image" using the :term:`MicroPython Unix port`, and then transferring this image to a device by suitable means. -Installing to a directory image involves using ``-p`` switch to `upip`:: +Installing to a directory image involves using ``-p`` switch to :term:`upip`:: micropython -m upip install -p install_dir micropython-pystone_lowmem @@ -137,13 +137,13 @@ packages) will be available in the ``install_dir/`` subdirectory. You would need to transfer contents of this directory (without the ``install_dir/`` prefix) to the device, at the suitable location, where it can be found by the Python ``import`` statement (see discussion of -the `upip` installation path above). +the :term:`upip` installation path above). Cross-installing packages with freezing --------------------------------------- -For the low-memory `MicroPython ports `, the process +For the low-memory :term:`MicroPython ports `, the process described in the previous section does not provide the most efficient resource usage,because the packages are installed in the source form, so need to be compiled to the bytecome on each import. This compilation @@ -160,7 +160,7 @@ mentioned above: * Filesystem is not required for frozen packages. Using frozen bytecode requires building the executable (firmware) -for a given `MicroPython port` from the C source code. Consequently, +for a given :term:`MicroPython port` from the C source code. Consequently, the process is: 1. Follow the instructions for a particular port on setting up a @@ -168,7 +168,7 @@ the process is: study instructions in ``ports/esp8266/README.md`` and follow them. Make sure you can build the port and deploy the resulting executable/firmware successfully before proceeding to the next steps. -2. Build `MicroPython Unix port` and make sure it is in your PATH and +2. Build :term:`MicroPython Unix port` and make sure it is in your PATH and you can execute ``micropython``. 3. Change to port's directory (e.g. ``ports/esp8266/`` for ESP8266). 4. Run ``make clean-frozen``. This step cleans up any previous @@ -281,7 +281,7 @@ following calls:: pkg_resources.resource_stream(__name__, "data/page.html") pkg_resources.resource_stream(__name__, "data/image.png") -You can develop and debug using the `MicroPython Unix port` as usual. +You can develop and debug using the :term:`MicroPython Unix port` as usual. When time comes to make a distribution package out of it, just use overridden "sdist" command from sdist_upip.py module as described in the previous section. diff --git a/docs/reference/pyboard.py.rst b/docs/reference/pyboard.py.rst index d404c738f..30230eebc 100644 --- a/docs/reference/pyboard.py.rst +++ b/docs/reference/pyboard.py.rst @@ -68,7 +68,7 @@ example:: $ pyboard.py -c 'print(1+1)' Similarly, the ``PYBOARD_BAUDRATE`` environment variable can be used -to set the default for the `--baudrate` option. +to set the default for the ``--baudrate`` option. Running a script on the device ------------------------------ diff --git a/docs/templates/replace.inc b/docs/templates/replace.inc index 319c53735..14f1875ee 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, + *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/examples/bluetooth/ble_advertising.py b/examples/bluetooth/ble_advertising.py index 3fb1281f6..eed527f55 100644 --- a/examples/bluetooth/ble_advertising.py +++ b/examples/bluetooth/ble_advertising.py @@ -30,7 +30,7 @@ def _append(adv_type, value): _append( _ADV_TYPE_FLAGS, - struct.pack("B", (0x01 if limited_disc else 0x02) + (0x00 if br_edr else 0x04)), + struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)), ) if name: @@ -47,7 +47,8 @@ def _append(adv_type, value): _append(_ADV_TYPE_UUID128_COMPLETE, b) # See org.bluetooth.characteristic.gap.appearance.xml - _append(_ADV_TYPE_APPEARANCE, struct.pack(" 0 + + def _advertise(self, interval_us=500000): + print("Starting advertising") + self._ble.gap_advertise(interval_us, adv_data=self._payload) + + def on_write(self, callback): + self._write_callback = callback + + +def demo(): + ble = bluetooth.BLE() + p = BLESimplePeripheral(ble) + + def on_rx(v): + print("RX", v) + + p.on_write(on_rx) + + i = 0 + while True: + if p.is_connected(): + # Short burst of queued notifications. + for _ in range(3): + data = str(i) + "_" + print("TX", data) + p.send(data) + i += 1 + time.sleep_ms(100) + + +if __name__ == "__main__": + demo() diff --git a/examples/bluetooth/ble_temperature.py b/examples/bluetooth/ble_temperature.py index 01d2f7441..0e2da2239 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -11,15 +11,16 @@ from micropython import const -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_INDICATE_DONE = const(20) # org.bluetooth.service.environmental_sensing _ENV_SENSE_UUID = bluetooth.UUID(0x181A) # org.bluetooth.characteristic.temperature _TEMP_CHAR = ( bluetooth.UUID(0x2A6E), - bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) _ENV_SENSE_SERVICE = ( _ENV_SENSE_UUID, @@ -52,15 +53,21 @@ def _irq(self, event, data): self._connections.remove(conn_handle) # Start advertising again to allow a new connection. self._advertise() + elif event == _IRQ_GATTS_INDICATE_DONE: + conn_handle, value_handle, status, = data - def set_temperature(self, temp_deg_c, notify=False): + def set_temperature(self, temp_deg_c, notify=False, indicate=False): # Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius. # Write the local value, ready for a central to read. self._ble.gatts_write(self._handle, struct.pack("= 0) { + recv_buf[recv_idx++] = chr; + if (recv_idx == recv_len) { + recv_idx = 0; + recv_len = 0; + if (recv_handler) { + recv_handler(); + } + } + } + + if (host_wake) { + mp_bluetooth_hci_controller_sleep_maybe(); + } +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/btstack/btstack_hci_uart.h b/extmod/btstack/btstack_hci_uart.h new file mode 100644 index 000000000..8011e587d --- /dev/null +++ b/extmod/btstack/btstack_hci_uart.h @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * 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. + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H + +#include "lib/btstack/src/btstack.h" + +// --- Used by the port to create the HCI transport --------------------------- +extern const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block; + +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_btstack_hci_uart_process(void); + +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 772fe4bed..c7269d33e 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -35,7 +35,7 @@ #include "lib/btstack/src/btstack.h" -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY BTSTACK" @@ -53,18 +53,24 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; -STATIC btstack_packet_callback_registration_t hci_event_callback_registration; - 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) { + } 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) { @@ -77,21 +83,201 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu } 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; + } -STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + 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(packet_type=%u, channel=%u, packet=%p, size=%u)\n", packet_type, channel, packet, 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 == HCI_EVENT_LE_META) { + } 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); @@ -119,7 +305,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { - DEBUG_EVENT_printf(" --> hci transport packet set\n"); + 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) { @@ -130,16 +316,8 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ 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 == 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_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); @@ -155,8 +333,33 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ 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) { - DEBUG_EVENT_printf(" --> gatt query complete\n"); + 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); @@ -208,31 +411,71 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ 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); - #endif + } 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 -STATIC void btstack_packet_handler_write_with_response(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { +// 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; - DEBUG_EVENT_printf("btstack_packet_handler_write_with_response(packet_type=%u, channel=%u, packet=%p, size=%u)\n", packet_type, channel, packet, size); - if (packet_type != HCI_EVENT_PACKET) { - return; - } + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE); +} - uint8_t event_type = hci_event_packet_get_type(packet); - if (event_type == GATT_EVENT_QUERY_COMPLETE) { - DEBUG_EVENT_printf(" --> gatt query complete\n"); - uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); - uint8_t status = gatt_event_query_complete_get_att_status(packet); - // TODO there is no value_handle to pass here - mp_bluetooth_gattc_on_write_status(conn_handle, 0, status); - } +// 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); } -#endif + +// 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; @@ -285,9 +528,11 @@ int mp_bluetooth_init(void) { #endif // Register for HCI events. - hci_event_callback_registration.callback = &btstack_packet_handler; 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); @@ -314,7 +559,7 @@ int mp_bluetooth_init(void) { #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, GATT_CLIENT_ANY_CONNECTION, NULL); + 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; @@ -369,8 +614,7 @@ size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { } int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { - mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len); - return 0; + 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) { @@ -515,7 +759,10 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m 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); - mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf)); + 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; @@ -566,19 +813,66 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { 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); + 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) { +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"); - // TODO: We need to use att_server_request_to_send_notification here as the stack may not be ready to send a notification. - int err = att_server_notify(conn_handle, value_handle, value, *value_len); - return btstack_error_to_errno(err); + + // 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"); - return btstack_error_to_errno(att_server_indicate(conn_handle, value_handle, NULL, 0)); + + 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) { @@ -641,14 +935,28 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, return btstack_error_to_errno(gap_connect(btstack_addr, addr_type)); } -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) { +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"); - return btstack_error_to_errno(gatt_client_discover_primary_services(&btstack_packet_handler, conn_handle)); + 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) { +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, @@ -656,7 +964,22 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s .uuid16 = 0, .uuid128 = {0}, }; - return btstack_error_to_errno(gatt_client_discover_characteristics_for_service(&btstack_packet_handler, conn_handle, &service)); + 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) { @@ -670,30 +993,53 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start .uuid16 = 0, .uuid128 = {0}, }; - return btstack_error_to_errno(gatt_client_discover_characteristic_descriptors(&btstack_packet_handler, conn_handle, &characteristic)); + 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, conn_handle, value_handle)); + 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"); - // TODO the below gatt_client functions do not copy the data and require it to be valid - // until the write is done, so there should be some kind of buffering done here. + // 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) { - // TODO need to call gatt_client_request_can_write_without_response_event then do - // the actual write on the callback from that. - return btstack_error_to_errno(gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value)); + // 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 (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { - return btstack_error_to_errno(gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, *value_len, (uint8_t *)value)); + + if (pending_op && err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); } - return MP_EINVAL; + return btstack_error_to_errno(err); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h index 3bdb5f271..2fad86f22 100644 --- a/extmod/btstack/modbluetooth_btstack.h +++ b/extmod/btstack/modbluetooth_btstack.h @@ -33,6 +33,8 @@ #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; @@ -42,6 +44,8 @@ typedef struct _mp_bluetooth_btstack_root_pointers_t { // 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; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 7e6abb549..7bfb77478 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 Ayke van Laethem - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-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 @@ -57,7 +57,6 @@ STATIC const mp_obj_type_t bluetooth_uuid_type; typedef struct { mp_obj_base_t base; mp_obj_t irq_handler; - uint16_t irq_trigger; bool irq_scheduled; mp_obj_t irq_data_tuple; uint8_t irq_data_addr_bytes[6]; @@ -249,7 +248,6 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, o->base.type = &bluetooth_ble_type; o->irq_handler = mp_const_none; - o->irq_trigger = 0; // Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler. o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL); @@ -323,8 +321,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map case MP_QSTR_gap_name: { mp_buffer_info_t bufinfo; mp_get_buffer_raise(e->value, &bufinfo, MP_BUFFER_READ); - int ret = mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len); - bluetooth_handle_errno(ret); + bluetooth_handle_errno(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len)); break; } case MP_QSTR_rxbuf: { @@ -372,10 +369,9 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_config_obj, 1, bluetooth_ble_config); STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_handler, ARG_trigger }; + enum { ARG_handler }; static const mp_arg_t allowed_args[] = { { MP_QSTR_handler, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_BLUETOOTH_IRQ_ALL} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -388,7 +384,6 @@ STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_ma MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); o->irq_handler = callback; - o->irq_trigger = args[ARG_trigger].u_int; MICROPY_PY_BLUETOOTH_EXIT return mp_const_none; @@ -667,10 +662,9 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) if (n_args == 4) { mp_buffer_info_t bufinfo = {0}; mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); - size_t len = bufinfo.len; - int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len); + int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, bufinfo.len); bluetooth_handle_errno(err); - return MP_OBJ_NEW_SMALL_INT(len); + return mp_const_none; } else { int err = mp_bluetooth_gatts_notify(conn_handle, value_handle); return bluetooth_handle_errno(err); @@ -678,6 +672,16 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify); +STATIC mp_obj_t bluetooth_ble_gatts_indicate(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) { + (void)self_in; + mp_int_t conn_handle = mp_obj_get_int(conn_handle_in); + mp_int_t value_handle = mp_obj_get_int(value_handle_in); + + int err = mp_bluetooth_gatts_indicate(conn_handle, value_handle); + return bluetooth_handle_errno(err); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_indicate_obj, bluetooth_ble_gatts_indicate); + STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) { mp_int_t value_handle = mp_obj_get_int(args[1]); mp_int_t len = mp_obj_get_int(args[2]); @@ -692,21 +696,27 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3 #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC mp_obj_t bluetooth_ble_gattc_discover_services(mp_obj_t self_in, mp_obj_t conn_handle_in) { - (void)self_in; - mp_int_t conn_handle = mp_obj_get_int(conn_handle_in); - return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle)); +STATIC mp_obj_t bluetooth_ble_gattc_discover_services(size_t n_args, const mp_obj_t *args) { + mp_int_t conn_handle = mp_obj_get_int(args[1]); + mp_obj_bluetooth_uuid_t *uuid = NULL; + if (n_args == 3) { + uuid = MP_OBJ_TO_PTR(args[2]); + } + return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle, uuid)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_discover_services_obj, bluetooth_ble_gattc_discover_services); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_services_obj, 2, 3, bluetooth_ble_gattc_discover_services); STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, const mp_obj_t *args) { - (void)n_args; mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_int_t start_handle = mp_obj_get_int(args[2]); mp_int_t end_handle = mp_obj_get_int(args[3]); - return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle)); + mp_obj_bluetooth_uuid_t *uuid = NULL; + if (n_args == 3) { + uuid = MP_OBJ_TO_PTR(args[4]); + } + return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 4, bluetooth_ble_gattc_discover_characteristics); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 5, bluetooth_ble_gattc_discover_characteristics); STATIC mp_obj_t bluetooth_ble_gattc_discover_descriptors(size_t n_args, const mp_obj_t *args) { (void)n_args; @@ -763,6 +773,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) }, + { MP_ROM_QSTR(MP_QSTR_gatts_indicate), MP_ROM_PTR(&bluetooth_ble_gatts_indicate_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) }, #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // GATT Client (i.e. central/scanner role) @@ -789,6 +800,7 @@ STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_INDICATE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE_NO_RESPONSE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE) }, }; @@ -825,15 +837,17 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size // Note the int8_t got packed into the ringbuf as a uint8_t. data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf)); } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE if (uuid) { ringbuf_get_uuid(ringbuf, uuid); data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid); } + #endif // The code that enqueues into the ringbuf should ensure that it doesn't // put more than bt->irq_data_data_alloc bytes into the ringbuf, because // that's what's available here in bt->irq_data_bytes. if (bytes_data) { - bytes_data->len = ringbuf_get(ringbuf); + bytes_data->len = ringbuf_get16(ringbuf); for (size_t i = 0; i < bytes_data->len; ++i) { // cast away const, this is actually bt->irq_data_bytes. ((uint8_t *)bytes_data->data)[i] = ringbuf_get(ringbuf); @@ -854,7 +868,7 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { for (;;) { MICROPY_PY_BLUETOOTH_ENTER - mp_int_t event = event = ringbuf_get16(&o->ringbuf); + mp_int_t event = ringbuf_get(&o->ringbuf); if (event < 0) { // Nothing available in ringbuf. MICROPY_PY_BLUETOOTH_EXIT @@ -874,11 +888,14 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) { // conn_handle, value_handle ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE) { + // conn_handle, value_handle, status + ringbuf_extract(&o->ringbuf, data_tuple, 2, 1, NULL, 0, NULL, NULL); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) { // addr_type, addr, adv_type, rssi, adv_data ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &o->irq_data_addr, 2, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_SCAN_COMPLETE) { + } else if (event == MP_BLUETOOTH_IRQ_SCAN_DONE) { // No params required. data_tuple->len = 0; } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) { @@ -890,10 +907,13 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) { // conn_handle, handle, uuid ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, &o->irq_data_uuid, NULL); + } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { + // conn_handle, status + ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) { // conn_handle, value_handle, data ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS) { + } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || event == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { // conn_handle, value_handle, status ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, NULL, NULL); #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE @@ -915,23 +935,24 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_inv // Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data // into the ringbuf and schedule the callback via mp_sched_schedule. -STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event) { - if (!o || !(o->irq_trigger & event) || o->irq_handler == mp_const_none) { +STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint8_t event) { + if (!o || o->irq_handler == mp_const_none) { return false; } - if (ringbuf_free(&o->ringbuf) < len + 2) { + // Check if there is enough room for . + if (ringbuf_free(&o->ringbuf) < len + 1) { // Ringbuffer doesn't have room (and is therefore non-empty). // If this is another scan result, or the front of the ringbuffer isn't a scan result, then nothing to do. - if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek16(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) { + if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) { return false; } // Front of the queue is a scan result, remove it. // event, addr_type, addr, adv_type, rssi - int n = 2 + 1 + 6 + 1 + 1; + int n = 1 + 1 + 6 + 1 + 1; for (int i = 0; i < n; ++i) { ringbuf_get(&o->ringbuf); } @@ -943,7 +964,7 @@ STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event) { } // Append this event, the caller will then append the arguments. - ringbuf_put16(&o->ringbuf, event); + ringbuf_put(&o->ringbuf, event); return true; } @@ -959,7 +980,7 @@ STATIC void schedule_ringbuf(mp_uint_t atomic_state) { } } -void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); if (enqueue_irq(o, 2 + 1 + 6, event)) { @@ -982,11 +1003,22 @@ void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { schedule_ringbuf(atomic_state); } +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2 + 1, MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, value_handle); + ringbuf_put(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE)) { + if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_DONE)) { } schedule_ringbuf(atomic_state); } @@ -995,7 +1027,7 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uin MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) { + if (enqueue_irq(o, 1 + 6 + 1 + 1 + 2 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) { ringbuf_put(&o->ringbuf, addr_type); for (int i = 0; i < 6; ++i) { ringbuf_put(&o->ringbuf, addr[i]); @@ -1004,7 +1036,9 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uin ringbuf_put(&o->ringbuf, adv_type); // Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out. ringbuf_put(&o->ringbuf, (uint8_t)rssi); - ringbuf_put(&o->ringbuf, data_len); + // Length field is 16-bit. + data_len = MIN(UINT16_MAX, data_len); + ringbuf_put16(&o->ringbuf, data_len); for (size_t i = 0; i < data_len; ++i) { ringbuf_put(&o->ringbuf, data[i]); } @@ -1048,15 +1082,27 @@ void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t hand schedule_ringbuf(atomic_state); } -size_t mp_bluetooth_gattc_on_data_available_start(uint16_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out) { +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2, event)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + +size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out) { MICROPY_PY_BLUETOOTH_ENTER *atomic_state_out = atomic_state; mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 2 + 2 + 1 + data_len, event)) { + if (enqueue_irq(o, 2 + 2 + 2 + data_len, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); - ringbuf_put(&o->ringbuf, data_len); + // Length field is 16-bit. + data_len = MIN(UINT16_MAX, data_len); + ringbuf_put16(&o->ringbuf, data_len); return data_len; } else { return 0; @@ -1074,10 +1120,10 @@ void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state) { schedule_ringbuf(atomic_state); } -void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status) { +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS)) { + if (enqueue_irq(o, 2 + 2 + 2, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); ringbuf_put16(&o->ringbuf, status); @@ -1092,7 +1138,7 @@ void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_han // On ESP32, for example, this is not the case. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) { + if (o->irq_handler != mp_const_none) { // Use pre-allocated tuple because this is a hard IRQ. mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_data_tuple); data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle); @@ -1127,47 +1173,58 @@ mp_bluetooth_gatts_db_entry_t *mp_bluetooth_gatts_db_lookup(mp_gatts_db_t db, ui } int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, uint8_t **value, size_t *value_len) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; - } - - *value = entry->data; - *value_len = entry->data_len; - if (entry->append) { - entry->data_len = 0; + if (entry) { + *value = entry->data; + *value_len = entry->data_len; + if (entry->append) { + entry->data_len = 0; + } } - - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; - } + if (entry) { + if (value_len > entry->data_alloc) { + uint8_t *data = m_new_maybe(uint8_t, value_len); + if (data) { + entry->data = data; + entry->data_alloc = value_len; + } else { + MICROPY_PY_BLUETOOTH_EXIT + return MP_ENOMEM; + } + } - if (value_len > entry->data_alloc) { - entry->data = m_new(uint8_t, value_len); - entry->data_alloc = value_len; + memcpy(entry->data, value, value_len); + entry->data_len = value_len; } - - memcpy(entry->data, value, value_len); - entry->data_len = value_len; - - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; + if (entry) { + uint8_t *data = m_renew_maybe(uint8_t, entry->data, entry->data_alloc, len, true); + if (data) { + entry->data = data; + entry->data_alloc = len; + entry->data_len = 0; + entry->append = append; + } else { + MICROPY_PY_BLUETOOTH_EXIT + return MP_ENOMEM; + } } - entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len); - entry->data_alloc = len; - entry->data_len = 0; - entry->append = append; - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } #endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 4e658a7a0..cdb86e5e6 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 Ayke van Laethem - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-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 @@ -68,6 +68,7 @@ #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE (1 << 2) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE (1 << 5) // For mp_bluetooth_gattc_write, the mode parameter #define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) @@ -88,48 +89,52 @@ #define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_NON_RESOLVABLE (0x13) // Random private non-resolvable address. (NRF SD 0x03) // Event codes for the IRQ handler. -// Can also be combined to pass to the trigger param to select which events you -// are interested in. -// Note this is currently stored in a uint16_t (in irq_trigger, and the event -// arg to the irq handler), so one spare value remaining. -#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1 << 0) -#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (1 << 1) -#define MP_BLUETOOTH_IRQ_GATTS_WRITE (1 << 2) -#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (1 << 3) -#define MP_BLUETOOTH_IRQ_SCAN_RESULT (1 << 4) -#define MP_BLUETOOTH_IRQ_SCAN_COMPLETE (1 << 5) -#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (1 << 6) -#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (1 << 7) -#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (1 << 8) -#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (1 << 9) -#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (1 << 10) -#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (1 << 11) -#define MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS (1 << 12) -#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (1 << 13) -#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (1 << 14) -#define MP_BLUETOOTH_IRQ_ALL (0xffff) +#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1) +#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (2) +#define MP_BLUETOOTH_IRQ_GATTS_WRITE (3) +#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (4) +#define MP_BLUETOOTH_IRQ_SCAN_RESULT (5) +#define MP_BLUETOOTH_IRQ_SCAN_DONE (6) +#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (7) +#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (8) +#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (9) +#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE (10) +#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (11) +#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE (12) +#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (13) +#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE (14) +#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (15) +#define MP_BLUETOOTH_IRQ_GATTC_READ_DONE (16) +#define MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE (17) +#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (18) +#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (19) +#define MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE (20) /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. from micropython import const -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_GATTS_READ_REQUEST = const(1 << 3) -_IRQ_SCAN_RESULT = const(1 << 4) -_IRQ_SCAN_COMPLETE = const(1 << 5) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) -_IRQ_GATTC_INDICATE = const(1 << 14) -_IRQ_ALL = const(0xffff) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_SCAN_RESULT = const(5) +_IRQ_SCAN_DONE = const(6) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_DESCRIPTOR_RESULT = const(13) +_IRQ_GATTC_DESCRIPTOR_DONE = const(14) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) */ // Common UUID type. @@ -197,7 +202,7 @@ int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t // Notify the central that it should do a read. int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle); // Notify the central, including a data payload. (Note: does not set the gatts db value). -int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len); +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len); // Indicate the central. int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle); @@ -219,10 +224,10 @@ int mp_bluetooth_gap_scan_stop(void); int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms); // Find all primary services on the connected peripheral. -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle); +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid); // Find all characteristics on the specified service on a connected peripheral. -int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle); +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); // Find all descriptors on the specified characteristic on a connected peripheral. int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle); @@ -238,11 +243,14 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const // API implemented by modbluetooth (called by port-specific implementations): // Notify modbluetooth that a connection/disconnection event has occurred. -void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr); +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr); // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); +// Call this when an acknowledgment is received for an indication. +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status); + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Call this when a characteristic is read from. Return false to deny the read. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); @@ -264,15 +272,18 @@ void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t // Notify modbluetooth that a descriptor was found. void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid); +// Notify modbluetooth that service, characteristic or descriptor discovery has finished. +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status); + // Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate). // Note: these functions are to be called in a group protected by MICROPY_PY_BLUETOOTH_ENTER/EXIT. // _start returns the number of bytes to submit to the calls to _chunk, followed by a call to _end. -size_t mp_bluetooth_gattc_on_data_available_start(uint16_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out); +size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out); void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len); void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state); -// Notify modbluetooth that a write has completed. -void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status); +// Notify modbluetooth that a read or write operation has completed. +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status); #endif // For stacks that don't manage attribute value data (currently all of them), helpers diff --git a/extmod/modubinascii.h b/extmod/modubinascii.h new file mode 100644 index 000000000..fb3169267 --- /dev/null +++ b/extmod/modubinascii.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Paul Sokolovsky + * + * 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_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 f7d2be1bc..bf489cbaa 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -115,8 +115,11 @@ typedef struct _mp_obj_uctypes_struct_t { byte *addr; uint32_t flags; } mp_obj_uctypes_struct_t; - +#if NO_NLR +STATIC mp_obj_t syntax_error(void) { +#else STATIC NORETURN void syntax_error(void) { +#endif mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor")); } @@ -137,11 +140,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); @@ -214,11 +213,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)) { @@ -226,9 +221,12 @@ 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(MP_ERROR_TEXT("can't unambiguously get sizeof scalar")); + mp_raise_TypeError_or_return(MP_ERROR_TEXT("can't unambiguously get sizeof scalar"), (mp_uint_t)-1); } syntax_error(); +#if NO_NLR + return (mp_uint_t)-1; +#endif } mp_obj_dict_t *d = MP_OBJ_TO_PTR(desc_in); @@ -254,6 +252,9 @@ STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_ } else { if (!mp_obj_is_type(v, &mp_type_tuple)) { syntax_error(); +#if NO_NLR + return (mp_uint_t)-1; +#endif } mp_obj_tuple_t *t = MP_OBJ_TO_PTR(v); mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]); @@ -296,6 +297,11 @@ STATIC mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) { } } mp_uint_t size = uctypes_struct_size(obj_in, layout_type, &max_field_size); +#if NO_NLR + if (size == (mp_uint_t)-1) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_NEW_SMALL_INT(size); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uctypes_struct_sizeof_obj, 1, 2, uctypes_struct_sizeof); @@ -418,11 +424,7 @@ 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 - ) { + if (!mp_obj_is_dict_or_ordereddict(self->desc)) { mp_raise_TypeError(MP_ERROR_TEXT("struct: no fields")); } @@ -489,11 +491,17 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set } if (!mp_obj_is_type(deref, &mp_type_tuple)) { +#if NO_NLR + return +#endif syntax_error(); } if (set_val != MP_OBJ_NULL) { // Cannot assign to aggregate +#if NO_NLR + return +#endif syntax_error(); } diff --git a/extmod/moduheapq.c b/extmod/moduheapq.c index 073ce516b..83f4d18a5 100644 --- a/extmod/moduheapq.c +++ b/extmod/moduheapq.c @@ -33,7 +33,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(MP_ERROR_TEXT("heap must be a list")); + mp_raise_TypeError_or_return(MP_ERROR_TEXT("heap must be a list"), NULL); } return MP_OBJ_TO_PTR(heap_in); } @@ -72,6 +72,11 @@ STATIC void uheapq_heap_siftup(mp_obj_list_t *heap, mp_uint_t pos) { STATIC mp_obj_t mod_uheapq_heappush(mp_obj_t heap_in, mp_obj_t item) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); +#if NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif mp_obj_list_append(heap_in, item); uheapq_heap_siftdown(heap, 0, heap->len - 1); return mp_const_none; @@ -80,6 +85,11 @@ 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 NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif if (heap->len == 0) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap")); } @@ -96,6 +106,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_uheapq_heappop_obj, mod_uheapq_heappop); STATIC mp_obj_t mod_uheapq_heapify(mp_obj_t heap_in) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); +#if NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif for (mp_uint_t i = heap->len / 2; i > 0;) { uheapq_heap_siftup(heap, --i); } diff --git a/extmod/modujson.c b/extmod/modujson.c index 8dff67358..0ece40dc0 100644 --- a/extmod/modujson.c +++ b/extmod/modujson.c @@ -35,7 +35,13 @@ #if MICROPY_PY_UJSON STATIC mp_obj_t mod_ujson_dump(mp_obj_t obj, mp_obj_t stream) { +#if NO_NLR + if (mp_get_stream_raise(stream, MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } +#else mp_get_stream_raise(stream, MP_STREAM_OP_WRITE); +#endif mp_print_t print = {MP_OBJ_TO_PTR(stream), mp_stream_write_adaptor}; mp_obj_print_helper(&print, obj, PRINT_JSON); return mp_const_none; @@ -79,7 +85,12 @@ typedef struct _ujson_stream_t { STATIC byte ujson_stream_next(ujson_stream_t *s) { mp_uint_t ret = s->read(s->stream_obj, &s->cur, 1, &s->errcode); if (s->errcode != 0) { +#if NO_NLR +#pragma message "TODO deal with raising exception" + mp_raise_OSError_or_return(s->errcode, 0xff); +#else mp_raise_OSError(s->errcode); +#endif } if (ret == 0) { s->cur = S_EOF; diff --git a/extmod/modure.c b/extmod/modure.c index 1847ec288..de2ca1125 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -53,6 +53,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; @@ -64,7 +68,7 @@ STATIC mp_obj_t match_group(mp_obj_t self_in, mp_obj_t no_in) { mp_obj_match_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t no = mp_obj_get_int(no_in); if (no < 0 || no >= self->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, no_in)); + mp_raise_or_return_value(mp_obj_new_exception_arg1(&mp_type_IndexError, no_in), MP_OBJ_NULL); } const char *start = self->caps[no * 2]; @@ -96,14 +100,18 @@ MP_DEFINE_CONST_FUN_OBJ_1(match_groups_obj, match_groups); #if MICROPY_PY_URE_MATCH_SPAN_START_END +#if NO_NLR +STATIC int match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span[2]) { +#else STATIC void match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span[2]) { +#endif mp_obj_match_t *self = MP_OBJ_TO_PTR(args[0]); mp_int_t no = 0; if (n_args == 2) { no = mp_obj_get_int(args[1]); if (no < 0 || no >= self->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, args[1])); + mp_raise_or_return(mp_obj_new_exception_arg1(&mp_type_IndexError, args[1]), 1); } } @@ -119,25 +127,46 @@ STATIC void match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span span[0] = mp_obj_new_int(s); span[1] = mp_obj_new_int(e); +#if NO_NLR + return 0; +#endif } STATIC mp_obj_t match_span(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return mp_obj_new_tuple(2, span); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_span_obj, 1, 2, match_span); STATIC mp_obj_t match_start(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return span[0]; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_start_obj, 1, 2, match_start); STATIC mp_obj_t match_end(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return span[1]; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_end_obj, 1, 2, match_end); @@ -175,7 +204,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); @@ -185,6 +219,12 @@ STATIC mp_obj_t ure_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { // cast is a workaround for a bug in msvc: it treats const char** as a const pointer instead of a pointer to pointer to const char memset((char *)match->caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, match->caps, caps_num, is_anchored); + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif if (res == 0) { m_del_var(mp_obj_match_t, char *, caps_num, match); return mp_const_none; @@ -227,6 +267,12 @@ STATIC mp_obj_t re_split(size_t n_args, const mp_obj_t *args) { memset((char **)caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, caps, caps_num, false); + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif // if we didn't have a match, or had an empty match, it's time to stop if (!res || caps[0] == caps[1]) { break; @@ -253,8 +299,13 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_split_obj, 2, 3, re_split); #if MICROPY_PY_URE_SUB -STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *args) { - mp_obj_re_t *self = MP_OBJ_TO_PTR(self_in); +STATIC mp_obj_t re_sub_helper(size_t n_args, const mp_obj_t *args) { + 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)); + } mp_obj_t replace = args[1]; mp_obj_t where = args[2]; mp_int_t count = 0; @@ -281,7 +332,12 @@ STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *a // cast is a workaround for a bug in msvc: it treats const char** as a const pointer instead of a pointer to pointer to const char memset((char *)match->caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, match->caps, caps_num, false); - + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif // If we didn't have a match, or had an empty match, it's time to stop if (!res || match->caps[0] == match->caps[1]) { break; @@ -320,7 +376,10 @@ STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *a } if (match_no >= (unsigned int)match->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, MP_OBJ_NEW_SMALL_INT(match_no))); + mp_raise_or_return_value( + mp_obj_new_exception_arg1(&mp_type_IndexError, MP_OBJ_NEW_SMALL_INT(match_no)), + MP_OBJ_NULL + ); } const char *start_match = match->caps[match_no * 2]; @@ -358,10 +417,7 @@ STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *a return mp_obj_new_str_from_vstr(mp_obj_get_type(where), &vstr_return); } -STATIC mp_obj_t re_sub(size_t n_args, const mp_obj_t *args) { - return re_sub_helper(args[0], n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_sub_obj, 3, 5, re_sub); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_sub_obj, 3, 5, re_sub_helper); #endif @@ -414,41 +470,14 @@ STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_compile_obj, 1, 2, mod_re_compile); -STATIC mp_obj_t mod_re_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { - (void)n_args; - mp_obj_t self = mod_re_compile(1, args); - - const mp_obj_t args2[] = {self, args[1]}; - mp_obj_t match = ure_exec(is_anchored, 2, args2); - return match; -} - -STATIC mp_obj_t mod_re_match(size_t n_args, const mp_obj_t *args) { - return mod_re_exec(true, n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_match_obj, 2, 4, mod_re_match); - -STATIC mp_obj_t mod_re_search(size_t n_args, const mp_obj_t *args) { - return mod_re_exec(false, n_args, args); -} -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); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_sub_obj, 3, 5, mod_re_sub); -#endif - #if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_re_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ure) }, { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mod_re_compile_obj) }, - { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&mod_re_match_obj) }, - { MP_ROM_QSTR(MP_QSTR_search), MP_ROM_PTR(&mod_re_search_obj) }, + { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&re_match_obj) }, + { MP_ROM_QSTR(MP_QSTR_search), MP_ROM_PTR(&re_search_obj) }, #if MICROPY_PY_URE_SUB - { MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&mod_re_sub_obj) }, + { MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&re_sub_obj) }, #endif #if MICROPY_PY_URE_DEBUG { MP_ROM_QSTR(MP_QSTR_DEBUG), MP_ROM_INT(FLAG_DEBUG) }, @@ -471,7 +500,11 @@ const mp_obj_module_t mp_module_ure = { #if MICROPY_PY_URE_DEBUG #include "re1.5/dumpcode.c" #endif +#if NO_NLR +#include "re1.5/recursiveloop_no_nlr.c" +#else #include "re1.5/recursiveloop.c" +#endif #include "re1.5/charclass.c" #endif // MICROPY_PY_URE diff --git a/extmod/modussl_axtls.c b/extmod/modussl_axtls.c index 7b0e3cbcb..c1c3a0d35 100644 --- a/extmod/modussl_axtls.c +++ b/extmod/modussl_axtls.c @@ -29,6 +29,7 @@ #include "py/runtime.h" #include "py/stream.h" +#include "py/objstr.h" #if MICROPY_PY_USSL && MICROPY_SSL_AXTLS @@ -54,7 +55,73 @@ struct ssl_args { STATIC const mp_obj_type_t ussl_socket_type; +// Table of errors +struct ssl_errs { + int16_t errnum; + const char *errstr; +}; +STATIC const struct ssl_errs ssl_error_tab[] = { + { SSL_NOT_OK, "NOT_OK" }, + { SSL_ERROR_DEAD, "DEAD" }, + { SSL_CLOSE_NOTIFY, "CLOSE_NOTIFY" }, + { SSL_EAGAIN, "EAGAIN" }, + { SSL_ERROR_CONN_LOST, "CONN_LOST" }, + { SSL_ERROR_RECORD_OVERFLOW, "RECORD_OVERFLOW" }, + { SSL_ERROR_SOCK_SETUP_FAILURE, "SOCK_SETUP_FAILURE" }, + { SSL_ERROR_INVALID_HANDSHAKE, "INVALID_HANDSHAKE" }, + { SSL_ERROR_INVALID_PROT_MSG, "INVALID_PROT_MSG" }, + { SSL_ERROR_INVALID_HMAC, "INVALID_HMAC" }, + { SSL_ERROR_INVALID_VERSION, "INVALID_VERSION" }, + { SSL_ERROR_UNSUPPORTED_EXTENSION, "UNSUPPORTED_EXTENSION" }, + { SSL_ERROR_INVALID_SESSION, "INVALID_SESSION" }, + { SSL_ERROR_NO_CIPHER, "NO_CIPHER" }, + { SSL_ERROR_INVALID_CERT_HASH_ALG, "INVALID_CERT_HASH_ALG" }, + { SSL_ERROR_BAD_CERTIFICATE, "BAD_CERTIFICATE" }, + { SSL_ERROR_INVALID_KEY, "INVALID_KEY" }, + { SSL_ERROR_FINISHED_INVALID, "FINISHED_INVALID" }, + { SSL_ERROR_NO_CERT_DEFINED, "NO_CERT_DEFINED" }, + { SSL_ERROR_NO_CLIENT_RENOG, "NO_CLIENT_RENOG" }, + { SSL_ERROR_NOT_SUPPORTED, "NOT_SUPPORTED" }, +}; + +#if NO_NLR +STATIC void ussl_raise_error(int err) { +#else +STATIC NORETURN void ussl_raise_error(int err) { +#endif + for (size_t i = 0; i < MP_ARRAY_SIZE(ssl_error_tab); i++) { + if (ssl_error_tab[i].errnum == err) { + // construct string object + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + if (o_str == NULL) { + break; + } + o_str->base.type = &mp_type_str; + o_str->data = (const byte *)ssl_error_tab[i].errstr; + o_str->len = strlen((char *)o_str->data); + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + // raise + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; +#if NO_NLR + #pragma message "TODO: ussl_raise_error" +#else + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); +#endif + } + } +#if NO_NLR + #pragma message "TODO: ussl_raise_error" +#else + mp_raise_OSError(err); +#endif +} + + +#if NO_NLR +STATIC mp_obj_t ussl_socket_new(mp_obj_t sock, struct ssl_args *args) { +#else STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) { +#endif #if MICROPY_PY_USSL_FINALISER mp_obj_ssl_socket_t *o = m_new_obj_with_finaliser(mp_obj_ssl_socket_t); #else @@ -86,6 +153,11 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args } data = (const byte *)mp_obj_str_get_data(args->cert.u_obj, &len); + #if NO_NLR + if (data == NULL) { + return MP_OBJ_NULL; + } + #endif res = ssl_obj_memory_load(o->ssl_ctx, SSL_OBJ_X509_CERT, data, len, NULL); if (res != SSL_OK) { mp_raise_ValueError(MP_ERROR_TEXT("invalid cert")); @@ -107,15 +179,17 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args int res = ssl_handshake_status(o->ssl_sock); if (res != SSL_OK) { - printf("ssl_handshake_status: %d\n", res); - ssl_display_error(res); - mp_raise_OSError(MP_EIO); + ussl_raise_error(res); } } } +#if NO_NLR + return MP_OBJ_FROM_PTR(o); +#else return o; +#endif } STATIC void ussl_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -260,7 +334,11 @@ STATIC mp_obj_t mod_ssl_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); +#if NO_NLR + return ussl_socket_new(sock, &args); +#else return MP_OBJ_FROM_PTR(ussl_socket_new(sock, &args)); +#endif } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_ssl_wrap_socket_obj, 1, mod_ssl_wrap_socket); diff --git a/extmod/modussl_mbedtls.c b/extmod/modussl_mbedtls.c index 9e117c82c..1677dc6e1 100644 --- a/extmod/modussl_mbedtls.c +++ b/extmod/modussl_mbedtls.c @@ -34,6 +34,7 @@ #include "py/runtime.h" #include "py/stream.h" +#include "py/objstr.h" // mbedtls_time_t #include "mbedtls/platform.h" @@ -43,6 +44,7 @@ #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" #include "mbedtls/debug.h" +#include "mbedtls/error.h" typedef struct _mp_obj_ssl_socket_t { mp_obj_base_t base; @@ -74,6 +76,46 @@ STATIC void mbedtls_debug(void *ctx, int level, const char *file, int line, cons } #endif +STATIC NORETURN void mbedtls_raise_error(int err) { + // _mbedtls_ssl_send and _mbedtls_ssl_recv (below) turn positive error codes from the + // underlying socket into negative codes to pass them through mbedtls. Here we turn them + // positive again so they get interpreted as the OSError they really are. The + // cut-off of -256 is a bit hacky, sigh. + if (err < 0 && err > -256) { + mp_raise_OSError(-err); + } + + #if defined(MBEDTLS_ERROR_C) + // Including mbedtls_strerror takes about 1.5KB due to the error strings. + // MBEDTLS_ERROR_C is the define used by mbedtls to conditionally include mbedtls_strerror. + // It is set/unset in the MBEDTLS_CONFIG_FILE which is defined in the Makefile. + + // Try to allocate memory for the message + #define ERR_STR_MAX 80 // mbedtls_strerror truncates if it doesn't fit + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + byte *o_str_buf = m_new_maybe(byte, ERR_STR_MAX); + if (o_str == NULL || o_str_buf == NULL) { + mp_raise_OSError(err); + } + + // print the error message into the allocated buffer + mbedtls_strerror(err, (char *)o_str_buf, ERR_STR_MAX); + size_t len = strlen((char *)o_str_buf); + + // Put the exception object together + o_str->base.type = &mp_type_str; + o_str->data = o_str_buf; + o_str->len = len; + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + // raise + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); + #else + // mbedtls is compiled without error strings so we simply return the err number + mp_raise_OSError(err); // err is typically a large negative number + #endif +} + STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { mp_obj_t sock = *(mp_obj_t *)ctx; @@ -85,7 +127,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { if (mp_is_nonblocking_error(err)) { return MBEDTLS_ERR_SSL_WANT_WRITE; } - return -err; + return -err; // convert an MP_ERRNO to something mbedtls passes through as error } else { return out_sz; } @@ -197,7 +239,6 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) { if (args->do_handshake.u_bool) { while ((ret = mbedtls_ssl_handshake(&o->ssl)) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - printf("mbedtls_ssl_handshake error: -%x\n", -ret); goto cleanup; } } @@ -221,7 +262,7 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) { } else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) { mp_raise_ValueError(MP_ERROR_TEXT("invalid cert")); } else { - mp_raise_OSError(MP_EIO); + mbedtls_raise_error(ret); } } diff --git a/extmod/modutimeq.c b/extmod/modutimeq.c index c7467f3bf..8e3e23f29 100644 --- a/extmod/modutimeq.c +++ b/extmod/modutimeq.c @@ -75,7 +75,12 @@ STATIC bool time_less_than(struct qentry *item, struct qentry *parent) { } STATIC mp_obj_t utimeq_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if ( mp_arg_check_num(n_args, n_kw, 1, 1, false) ) + return MP_OBJ_NULL; +#else mp_arg_check_num(n_args, n_kw, 1, 1, false); +#endif mp_uint_t alloc = mp_obj_get_int(args[0]); mp_obj_utimeq_t *o = m_new_obj_var(mp_obj_utimeq_t, struct qentry, alloc); o->base.type = type; @@ -85,7 +90,7 @@ STATIC mp_obj_t utimeq_make_new(const mp_obj_type_t *type, size_t n_args, size_t return MP_OBJ_FROM_PTR(o); } -STATIC void utimeq_heap_siftdown(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_uint_t pos) { +STATIC void utimeq_heap_shift_down(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_uint_t pos) { struct qentry item = heap->items[pos]; while (pos > start_pos) { mp_uint_t parent_pos = (pos - 1) >> 1; @@ -101,7 +106,7 @@ STATIC void utimeq_heap_siftdown(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_ heap->items[pos] = item; } -STATIC void utimeq_heap_siftup(mp_obj_utimeq_t *heap, mp_uint_t pos) { +STATIC void utimeq_heap_shift_up(mp_obj_utimeq_t *heap, mp_uint_t pos) { mp_uint_t start_pos = pos; mp_uint_t end_pos = heap->len; struct qentry item = heap->items[pos]; @@ -118,7 +123,7 @@ STATIC void utimeq_heap_siftup(mp_obj_utimeq_t *heap, mp_uint_t pos) { pos = child_pos; } heap->items[pos] = item; - utimeq_heap_siftdown(heap, start_pos, pos); + utimeq_heap_shift_down(heap, start_pos, pos); } STATIC mp_obj_t mod_utimeq_heappush(size_t n_args, const mp_obj_t *args) { @@ -129,11 +134,15 @@ STATIC mp_obj_t mod_utimeq_heappush(size_t n_args, const mp_obj_t *args) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("queue overflow")); } mp_uint_t l = heap->len; +#if __EMSCRIPTEN__ + heap->items[l].time = (mp_uint_t)args[1]; +#else heap->items[l].time = MP_OBJ_SMALL_INT_VALUE(args[1]); +#endif heap->items[l].id = utimeq_id++; heap->items[l].callback = args[2]; heap->items[l].args = args[3]; - utimeq_heap_siftdown(heap, 0, heap->len); + utimeq_heap_shift_down(heap, 0, heap->len); heap->len++; return mp_const_none; } @@ -146,11 +155,16 @@ STATIC mp_obj_t mod_utimeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) { } mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref); if (!mp_obj_is_type(list_ref, &mp_type_list) || ret->len < 3) { - mp_raise_TypeError(NULL); + //mp_raise_TypeError(NULL); + mp_raise_msg(&mp_type_TypeError, MP_ERROR_TEXT("invalid arguments")); } struct qentry *item = &heap->items[0]; +#if __EMSCRIPTEN__ + ret->items[0] = (mp_uint_t *)(item->time); +#else ret->items[0] = MP_OBJ_NEW_SMALL_INT(item->time); +#endif ret->items[1] = item->callback; ret->items[2] = item->args; heap->len -= 1; @@ -158,7 +172,7 @@ STATIC mp_obj_t mod_utimeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) { heap->items[heap->len].callback = MP_OBJ_NULL; // so we don't retain a pointer heap->items[heap->len].args = MP_OBJ_NULL; if (heap->len) { - utimeq_heap_siftup(heap, 0); + utimeq_heap_shift_up(heap, 0); } return mp_const_none; } @@ -171,7 +185,11 @@ STATIC mp_obj_t mod_utimeq_peektime(mp_obj_t heap_in) { } struct qentry *item = &heap->items[0]; +#if __EMSCRIPTEN__ + return (mp_uint_t *)(item->time); +#else return MP_OBJ_NEW_SMALL_INT(item->time); +#endif } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_utimeq_peektime_obj, mod_utimeq_peektime); diff --git a/extmod/moduzlib.c b/extmod/moduzlib.c index ab70d6747..ca6cf5da1 100644 --- a/extmod/moduzlib.c +++ b/extmod/moduzlib.c @@ -58,10 +58,15 @@ STATIC int read_src_stream(TINF_DATA *data) { byte c; mp_uint_t out_sz = stream->read(self->src_stream, &c, 1, &err); if (out_sz == MP_STREAM_ERROR) { - mp_raise_OSError(err); +#if NO_NLR +#pragma message "TODO deal with raising exception" +#endif + mp_raise_OSError_or_return(err,-1); + return -1; } if (out_sz == 0) { - mp_raise_type(&mp_type_EOFError); + mp_raise_type_or_return(&mp_type_EOFError, -1); + return -1; } return c; } @@ -201,7 +206,7 @@ STATIC mp_obj_t mod_uzlib_decompress(size_t n_args, const mp_obj_t *args) { return res; error: - nlr_raise(mp_obj_new_exception_arg1(&mp_type_ValueError, MP_OBJ_NEW_SMALL_INT(st))); + mp_raise_or_return_value(mp_obj_new_exception_arg1(&mp_type_ValueError, MP_OBJ_NEW_SMALL_INT(st)), MP_OBJ_NULL); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_uzlib_decompress_obj, 1, 3, mod_uzlib_decompress); diff --git a/extmod/mpbthci.c b/extmod/mpbthci.c new file mode 100644 index 000000000..79a865424 --- /dev/null +++ b/extmod/mpbthci.c @@ -0,0 +1,38 @@ +/* + * 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. + */ + +// Default definitions in case a controller doesn't implement this. + +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +#include "extmod/mpbthci.h" + + +#endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/mpbthci.h b/extmod/mpbthci.h new file mode 100644 index 000000000..acb5b832b --- /dev/null +++ b/extmod/mpbthci.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H + +// --- Optionally can be implemented by the driver. --------------------------- + +// Start/stop the HCI controller. +// Requires the UART to this HCI controller is available. +int mp_bluetooth_hci_controller_init(void); +int mp_bluetooth_hci_controller_deinit(void); + +// Tell the controller to go to sleep (e.g. on RX if we don't think we're expecting anything more). +int mp_bluetooth_hci_controller_sleep_maybe(void); +// True if the controller woke us up. +bool mp_bluetooth_hci_controller_woken(void); +// Wake up the controller (e.g. we're about to TX). +int mp_bluetooth_hci_controller_wakeup(void); + +// --- Bindings that need to be implemented by the port. ---------------------- +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate); +int mp_bluetooth_hci_uart_deinit(void); +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate); +int mp_bluetooth_hci_uart_readchar(void); +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len); + +#endif // MICROPY_INCLUDED_EXTMOD_MPBTHCI_H diff --git a/extmod/nimble/logcfg/logcfg.h b/extmod/nimble/logcfg/logcfg.h new file mode 100644 index 000000000..e9023dae6 --- /dev/null +++ b/extmod/nimble/logcfg/logcfg.h @@ -0,0 +1,45 @@ +/** + * This file was generated by Apache newt version: 1.8.0-dev + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H +#define MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H + +#include "py/mphal.h" +#include "modlog/modlog.h" +#include "log_common/log_common.h" + +#define MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING (1) + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING +#define DFLT_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define DFLT_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING > 1 +#define BLE_HS_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define BLE_HS_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#define BLE_HS_LOG_INFO(...) MODLOG_INFO(4, __VA_ARGS__) +#define BLE_HS_LOG_WARN(...) MODLOG_WARN(4, __VA_ARGS__) +#define BLE_HS_LOG_ERROR(...) MODLOG_ERROR(4, __VA_ARGS__) +#define BLE_HS_LOG_CRITICAL(...) MODLOG_CRITICAL(4, __VA_ARGS__) +#define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__) + +#define DFLT_LOG_INFO(...) MODLOG_INFO(0, __VA_ARGS__) +#define DFLT_LOG_WARN(...) MODLOG_WARN(0, __VA_ARGS__) +#define DFLT_LOG_ERROR(...) MODLOG_ERROR(0, __VA_ARGS__) +#define DFLT_LOG_CRITICAL(...) MODLOG_CRITICAL(0, __VA_ARGS__) +#define DFLT_LOG_DISABLED(...) MODLOG_DISABLED(0, __VA_ARGS__) + +#define MFG_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_INFO(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_WARN(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_ERROR(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_CRITICAL(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_DISABLED(...) MODLOG_DISABLED(128, __VA_ARGS__) + +#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e2e95aa74..843989b2b 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-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 @@ -39,12 +39,13 @@ #include "nimble/ble.h" #include "nimble/nimble_port.h" #include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" #endif -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV @@ -74,19 +75,19 @@ STATIC int ble_hs_err_to_errno(int err) { } // Note: modbluetooth UUIDs store their data in LE. -STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) { +STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid, ble_uuid_any_t *storage) { if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { - ble_uuid16_t *result = m_new(ble_uuid16_t, 1); + ble_uuid16_t *result = storage ? &storage->u16 : m_new(ble_uuid16_t, 1); result->u.type = BLE_UUID_TYPE_16; result->value = (uuid->data[1] << 8) | uuid->data[0]; return (ble_uuid_t *)result; } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) { - ble_uuid32_t *result = m_new(ble_uuid32_t, 1); + ble_uuid32_t *result = storage ? &storage->u32 : m_new(ble_uuid32_t, 1); result->u.type = BLE_UUID_TYPE_32; result->value = (uuid->data[1] << 24) | (uuid->data[1] << 16) | (uuid->data[1] << 8) | uuid->data[0]; return (ble_uuid_t *)result; } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { - ble_uuid128_t *result = m_new(ble_uuid128_t, 1); + ble_uuid128_t *result = storage ? &storage->u128 : m_new(ble_uuid128_t, 1); result->u.type = BLE_UUID_TYPE_128; memcpy(result->value, uuid->data, 16); return (ble_uuid_t *)result; @@ -95,6 +96,13 @@ STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) { } } +// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE. +STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) { + for (int i = 0; i < 6; ++i) { + addr_out[i] = addr_in[5 - i]; + } +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) { @@ -122,13 +130,6 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) { return result; } -// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE. -STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) { - for (int i = 0; i < 6; ++i) { - addr_out[i] = addr_in[5 - i]; - } -} - STATIC ble_addr_t create_nimble_addr(uint8_t addr_type, const uint8_t *addr) { ble_addr_t addr_nimble; addr_nimble.type = addr_type; @@ -247,6 +248,15 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val); mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr); break; + + case BLE_GAP_EVENT_NOTIFY_TX: { + DEBUG_EVENT_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); + // This event corresponds to either a sent notify/indicate (status == 0), or an indication confirmation (status != 0). + if (event->notify_tx.indication && event->notify_tx.status != 0) { + // Map "done/ack" to 0, otherwise pass the status directly. + mp_bluetooth_gatts_on_indicate_complete(event->notify_tx.conn_handle, event->notify_tx.attr_handle, event->notify_tx.status == BLE_HS_EDONE ? 0 : event->notify_tx.status); + } + } } return 0; @@ -271,8 +281,9 @@ int mp_bluetooth_init(void) { nimble_port_init(); mp_bluetooth_nimble_port_postinit(); - // By default, just register the default gap service. + // By default, just register the default gap/gatt service. ble_svc_gap_init(); + ble_svc_gatt_init(); mp_bluetooth_nimble_port_start(); @@ -466,8 +477,9 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { // Reset the gatt characteristic value db. mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db); - // By default, just register the default gap service. + // By default, just register the default gap/gatt service. ble_svc_gap_init(); + ble_svc_gatt_init(); if (!append) { // Unref any previous service definitions. @@ -498,7 +510,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_chr_def *characteristics = m_new(struct ble_gatt_chr_def, num_characteristics + 1); for (size_t i = 0; i < num_characteristics; ++i) { - characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i]); + characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i], NULL); characteristics[i].access_cb = characteristic_access_cb; characteristics[i].arg = NULL; characteristics[i].flags = characteristic_flags[i]; @@ -512,7 +524,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_dsc_def *descriptors = m_new(struct ble_gatt_dsc_def, num_descriptors[i] + 1); for (size_t j = 0; j < num_descriptors[i]; ++j) { - descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index]); + descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index], NULL); descriptors[j].access_cb = characteristic_access_cb; descriptors[j].att_flags = descriptor_flags[descriptor_index]; descriptors[j].min_key_size = 0; @@ -530,7 +542,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_svc_def *service = m_new(struct ble_gatt_svc_def, 2); service[0].type = BLE_GATT_SVC_TYPE_PRIMARY; - service[0].uuid = create_nimble_uuid(service_uuid); + service[0].uuid = create_nimble_uuid(service_uuid, NULL); service[0].includes = NULL; service[0].characteristics = characteristics; service[1].type = 0; // no more services @@ -584,13 +596,13 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle)); } -int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) { +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - struct os_mbuf *om = ble_hs_mbuf_from_flat(value, *value_len); + struct os_mbuf *om = ble_hs_mbuf_from_flat(value, value_len); if (om == NULL) { - return -1; + return MP_ENOMEM; } // TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om). return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om)); @@ -600,6 +612,8 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } + // This will raise BLE_GAP_EVENT_NOTIFY_TX with a status when it is + // acknowledged (or timeout/error). return ble_hs_err_to_errno(ble_gattc_indicate(conn_handle, value_handle)); } @@ -612,7 +626,7 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC void gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { +STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { size_t len = OS_MBUF_PKTLEN(om); mp_uint_t atomic_state; len = mp_bluetooth_gattc_on_data_available_start(event, conn_handle, value_handle, len, &atomic_state); @@ -763,15 +777,24 @@ STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble if (error->status == 0) { mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(&service->uuid); mp_bluetooth_gattc_on_primary_service_result(conn_handle, service->start_handle, service->end_handle, &service_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) { +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL); + int err; + if (uuid) { + ble_uuid_any_t nimble_uuid; + create_nimble_uuid(uuid, &nimble_uuid); + err = ble_gattc_disc_svc_by_uuid(conn_handle, &nimble_uuid.u, &peripheral_discover_service_cb, NULL); + } else { + err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL); + } return ble_hs_err_to_errno(err); } @@ -783,15 +806,24 @@ STATIC int ble_gatt_characteristic_cb(uint16_t conn_handle, const struct ble_gat if (error->status == 0) { mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(&characteristic->uuid); mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic->def_handle, characteristic->val_handle, characteristic->properties, &characteristic_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } -int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { +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) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL); + int err; + if (uuid) { + ble_uuid_any_t nimble_uuid; + create_nimble_uuid(uuid, &nimble_uuid); + err = ble_gattc_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, &nimble_uuid.u, &ble_gatt_characteristic_cb, NULL); + } else { + err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL); + } return ble_hs_err_to_errno(err); } @@ -803,6 +835,8 @@ STATIC int ble_gatt_descriptor_cb(uint16_t conn_handle, const struct ble_gatt_er if (error->status == 0) { mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(&descriptor->uuid); mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor->handle, &descriptor_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } @@ -820,10 +854,10 @@ STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_err if (!mp_bluetooth_is_active()) { return 0; } - // TODO: Maybe send NULL if error->status non-zero. if (error->status == 0) { gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, attr->om); } + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_READ_DONE, conn_handle, attr ? attr->handle : -1, error->status); return 0; } @@ -841,7 +875,7 @@ STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_er if (!mp_bluetooth_is_active()) { return 0; } - mp_bluetooth_gattc_on_write_status(conn_handle, attr->handle, error->status); + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE, conn_handle, attr->handle, error->status); return 0; } diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c new file mode 100644 index 000000000..2ec012940 --- /dev/null +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -0,0 +1,515 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-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 "py/mphal.h" +#include "py/runtime.h" +#include "nimble/ble.h" +#include "nimble/nimble_npl.h" +#include "extmod/nimble/hal/hal_uart.h" + +#include "extmod/modbluetooth.h" +#include "extmod/nimble/modbluetooth_nimble.h" + +#define DEBUG_OS_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MALLOC_printf(...) // printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MUTEX_printf(...) // printf(__VA_ARGS__) +#define DEBUG_SEM_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CALLOUT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_TIME_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CRIT_printf(...) // printf(__VA_ARGS__) + +bool ble_npl_os_started(void) { + DEBUG_OS_printf("ble_npl_os_started\n"); + return true; +} + +void *ble_npl_get_current_task_id(void) { + DEBUG_OS_printf("ble_npl_get_current_task_id\n"); + return NULL; +} + +/******************************************************************************/ +// malloc + +// Maintain a linked list of heap memory that we've passed to Nimble, +// discoverable via the bluetooth_nimble_memory root pointer. + +typedef struct _mp_bluetooth_nimble_malloc_t { + struct _mp_bluetooth_nimble_malloc_t *prev; + struct _mp_bluetooth_nimble_malloc_t *next; + size_t size; + uint8_t data[]; +} mp_bluetooth_nimble_malloc_t; + +// TODO: This is duplicated from mbedtls. Perhaps make this a generic feature? +STATIC void *m_malloc_bluetooth(size_t size) { + size += sizeof(mp_bluetooth_nimble_malloc_t); + mp_bluetooth_nimble_malloc_t *alloc = m_malloc0(size); + alloc->size = size; + alloc->next = MP_STATE_PORT(bluetooth_nimble_memory); + if (alloc->next) { + alloc->next->prev = alloc; + } + MP_STATE_PORT(bluetooth_nimble_memory) = alloc; + return alloc->data; +} + +STATIC mp_bluetooth_nimble_malloc_t* get_nimble_malloc(void *ptr) { + return (mp_bluetooth_nimble_malloc_t*)((uintptr_t)ptr - sizeof(mp_bluetooth_nimble_malloc_t)); +} + +STATIC void m_free_bluetooth(void *ptr) { + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + if (alloc->next) { + alloc->next->prev = alloc->prev; + } + if (alloc->prev) { + alloc->prev->next = alloc->next; + } else { + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + } + m_free(alloc + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + , alloc->size + #endif + ); +} + +// Check if a nimble ptr is tracked. +// If it isn't, that means that it's from a previous soft-reset cycle. +STATIC bool is_valid_nimble_malloc(void *ptr) { + DEBUG_MALLOC_printf("NIMBLE is_valid_nimble_malloc(%p)\n", ptr); + mp_bluetooth_nimble_malloc_t *alloc = MP_STATE_PORT(bluetooth_nimble_memory); + while (alloc) { + DEBUG_MALLOC_printf("NIMBLE checking: %p\n", alloc->data); + if (alloc->data == ptr) { + return true; + } + alloc = alloc->next; + } + return false; +} + +void *nimble_malloc(size_t size) { + DEBUG_MALLOC_printf("NIMBLE malloc(%u)\n", (uint)size); + void* ptr = m_malloc_bluetooth(size); + DEBUG_MALLOC_printf(" --> %p\n", ptr); + return ptr; +} + +// Only free if it's still a valid pointer. +void nimble_free(void *ptr) { + DEBUG_MALLOC_printf("NIMBLE free(%p)\n", ptr); + + if (ptr) { + // After a stack re-init, NimBLE has variables in BSS that might be + // still pointing to old allocations from a previous init. We can't do + // anything about this (e.g. ble_gatts_free_mem is private). But we + // can identify that this is a non-null, invalid alloc because it + // won't be in our list, so ignore it because it is effectively free'd + // anyway (it's not referenced by anything the GC can find). + if (is_valid_nimble_malloc(ptr)) { + m_free_bluetooth(ptr); + } + } +} + +// Only realloc if it's still a valid pointer. Otherwise just malloc. +void *nimble_realloc(void *ptr, size_t new_size) { + DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)new_size); + + if (!ptr) { + return nimble_malloc(new_size); + } + + assert(is_valid_nimble_malloc(ptr)); + + // Existing alloc is big enough. + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + size_t old_size = alloc->size - sizeof(mp_bluetooth_nimble_malloc_t); + if (old_size >= new_size) { + return ptr; + } + + // Allocate a new, larger region. + void *ptr2 = m_malloc_bluetooth(new_size); + + // Copy old, smaller region into new region. + memcpy(ptr2, ptr, old_size); + m_free_bluetooth(ptr); + + DEBUG_MALLOC_printf(" --> %p\n", ptr2); + + return ptr2; +} + +// No-op implementation (only used by NimBLE logging). +int nimble_sprintf(char *str, const char *fmt, ...) { + str[0] = 0; + return 0; +} + +/******************************************************************************/ +// EVENTQ + +struct ble_npl_eventq *global_eventq = NULL; + +void mp_bluetooth_nimble_os_eventq_run_all(void) { + for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { + int n = 0; + while (evq->head != NULL && mp_bluetooth_nimble_ble_state > MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + struct ble_npl_event *ev = evq->head; + evq->head = ev->next; + if (ev->next) { + ev->next->prev = NULL; + ev->next = NULL; + } + ev->prev = NULL; + DEBUG_EVENT_printf("event_run(%p)\n", ev); + ev->fn(ev); + DEBUG_EVENT_printf("event_run(%p) done\n", ev); + + if (++n > 3) { + // Limit to running 3 tasks per queue. + // Some tasks (such as reset) can enqueue themselves + // making this an infinite loop (while in PENDSV). + break; + } + } + } +} + +void ble_npl_eventq_init(struct ble_npl_eventq *evq) { + DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq); + evq->head = NULL; + struct ble_npl_eventq **evq2; + for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) { + } + *evq2 = evq; + evq->nextq = NULL; +} + +void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { + DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg); + ev->next = NULL; + if (evq->head == NULL) { + evq->head = ev; + ev->prev = NULL; + } else { + struct ble_npl_event *ev2 = evq->head; + while (true) { + if (ev2 == ev) { + DEBUG_EVENT_printf(" --> already in queue\n"); + return; + } + if (ev2->next == NULL) { + break; + } + DEBUG_EVENT_printf(" --> %p\n", ev2->next); + ev2 = ev2->next; + } + ev2->next = ev; + ev->prev = ev2; + } +} + +void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) { + DEBUG_EVENT_printf("ble_npl_event_init(%p, %p, %p)\n", ev, fn, arg); + ev->fn = fn; + ev->arg = arg; + ev->next = NULL; +} + +void *ble_npl_event_get_arg(struct ble_npl_event *ev) { + DEBUG_EVENT_printf("ble_npl_event_get_arg(%p) -> %p\n", ev, ev->arg); + return ev->arg; +} + +void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { + DEBUG_EVENT_printf("ble_npl_event_set_arg(%p, %p)\n", ev, arg); + ev->arg = arg; +} + +/******************************************************************************/ +// MUTEX + +// This is what MICROPY_BEGIN_ATOMIC_SECTION returns on Unix (i.e. we don't +// need to preserve the atomic state to unlock). +#define ATOMIC_STATE_MUTEX_NOT_HELD 0xffffffff + +ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { + DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); + mu->locked = 0; + mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u irq=%d\n", mu, (uint)timeout, (uint)mu->locked); + + // This is a recursive mutex which we implement on top of the IRQ priority + // scheme. Unfortunately we have a single piece of global storage, where + // enter/exit critical needs an "atomic state". + + // There are two different acquirers, either running in a VM thread (i.e. + // a direct Python call into NimBLE), or in the NimBLE task (i.e. polling + // or UART RX). + + // On STM32 the NimBLE task runs in PENDSV, so cannot be interrupted by a VM thread. + // Therefore we only need to ensure that a VM thread that acquires a currently-unlocked mutex + // now raises the priority (thus preventing context switches to other VM threads and + // the PENDSV irq). If the mutex is already locked, then it must have been acquired + // by us. + + // On Unix, the critical section is completely recursive and doesn't require us to manage + // state so we just acquire and release every time. + + // TODO: The "volatile" on locked/atomic_state isn't enough to protect against memory re-ordering. + + // First acquirer of this mutex always enters the critical section, unless + // we're on Unix where it happens every time. + if (mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { + mu->atomic_state = mp_bluetooth_nimble_hci_uart_enter_critical(); + } + + ++mu->locked; + + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u irq=%d\n", mu, (uint)mu->locked); + assert(mu->locked > 0); + + --mu->locked; + + // Only exit the critical section for the final release, unless we're on Unix. + if (mu->locked == 0 || mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { + mp_bluetooth_nimble_hci_uart_exit_critical(mu->atomic_state); + mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; + } + + return BLE_NPL_OK; +} + +/******************************************************************************/ +// SEM + +ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { + DEBUG_SEM_printf("ble_npl_sem_init(%p, %u)\n", sem, (uint)tokens); + sem->count = tokens; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { + DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); + + // This is called by NimBLE to synchronously wait for an HCI ACK. The + // corresponding ble_npl_sem_release is called directly by the UART rx + // handler (i.e. hal_uart_rx_cb in extmod/nimble/hal/hal_uart.c). + + // So this implementation just polls the UART until either the semaphore + // is released, or the timeout occurs. + + if (sem->count == 0) { + uint32_t t0 = mp_hal_ticks_ms(); + while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { + // This can be called either from code running in NimBLE's "task" + // (i.e. PENDSV) or directly by application code, so for the + // latter case, prevent the "task" from running while we poll the + // UART directly. + MICROPY_PY_BLUETOOTH_ENTER + mp_bluetooth_nimble_hci_uart_process(); + MICROPY_PY_BLUETOOTH_EXIT + + if (sem->count != 0) { + break; + } + + // Because we're polling, might as well wait for a UART IRQ indicating + // more data available. + mp_bluetooth_nimble_hci_uart_wfi(); + } + + if (sem->count == 0) { + DEBUG_SEM_printf("ble_npl_sem_pend: semaphore timeout\n"); + return BLE_NPL_TIMEOUT; + } + + DEBUG_SEM_printf("ble_npl_sem_pend: acquired in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); + } + sem->count -= 1; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_sem_release(struct ble_npl_sem *sem) { + DEBUG_SEM_printf("ble_npl_sem_release(%p)\n", sem); + sem->count += 1; + return BLE_NPL_OK; +} + +uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { + DEBUG_SEM_printf("ble_npl_sem_get_count(%p)\n", sem); + return sem->count; +} + +/******************************************************************************/ +// CALLOUT + +static struct ble_npl_callout *global_callout = NULL; + +void mp_bluetooth_nimble_os_callout_process(void) { + uint32_t tnow = mp_hal_ticks_ms(); + for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { + if (!c->active) { + continue; + } + if ((int32_t)(tnow - c->ticks) >= 0) { + DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq); + c->active = false; + if (c->evq) { + ble_npl_eventq_put(c->evq, &c->ev); + } else { + c->ev.fn(&c->ev); + } + DEBUG_CALLOUT_printf("callout_run(%p) done\n", c); + } + } +} + +void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) { + DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg); + c->active = false; + c->ticks = 0; + c->evq = evq; + ble_npl_event_init(&c->ev, ev_cb, ev_arg); + + struct ble_npl_callout **c2; + for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) { + if (c == *c2) { + // callout already in linked list so don't link it in again + return; + } + } + *c2 = c; + c->nextc = NULL; +} + +ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) { + DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms()); + c->active = true; + c->ticks = ble_npl_time_get() + ticks; + return BLE_NPL_OK; +} + +void ble_npl_callout_stop(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_stop(%p)\n", c); + c->active = false; +} + +bool ble_npl_callout_is_active(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_is_active(%p)\n", c); + return c->active; +} + +ble_npl_time_t ble_npl_callout_get_ticks(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_get_ticks(%p)\n", c); + return c->ticks; +} + +ble_npl_time_t ble_npl_callout_remaining_ticks(struct ble_npl_callout *c, ble_npl_time_t now) { + DEBUG_CALLOUT_printf("ble_npl_callout_remaining_ticks(%p, %u)\n", c, (uint)now); + if (c->ticks > now) { + return c->ticks - now; + } else { + return 0; + } +} + +void *ble_npl_callout_get_arg(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_get_arg(%p)\n", c); + return ble_npl_event_get_arg(&c->ev); +} + +void ble_npl_callout_set_arg(struct ble_npl_callout *c, void *arg) { + DEBUG_CALLOUT_printf("ble_npl_callout_set_arg(%p, %p)\n", c, arg); + ble_npl_event_set_arg(&c->ev, arg); +} + +/******************************************************************************/ +// TIME + +uint32_t ble_npl_time_get(void) { + DEBUG_TIME_printf("ble_npl_time_get -> %u\n", (uint)mp_hal_ticks_ms()); + return mp_hal_ticks_ms(); +} + +ble_npl_error_t ble_npl_time_ms_to_ticks(uint32_t ms, ble_npl_time_t *out_ticks) { + DEBUG_TIME_printf("ble_npl_time_ms_to_ticks(%u)\n", (uint)ms); + *out_ticks = ms; + return BLE_NPL_OK; +} + +ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms) { + DEBUG_TIME_printf("ble_npl_time_ms_to_ticks32(%u)\n", (uint)ms); + return ms; +} + +uint32_t ble_npl_time_ticks_to_ms32(ble_npl_time_t ticks) { + DEBUG_TIME_printf("ble_npl_time_ticks_to_ms32(%u)\n", (uint)ticks); + return ticks; +} + +void ble_npl_time_delay(ble_npl_time_t ticks) { + mp_hal_delay_ms(ticks + 1); +} + +/******************************************************************************/ +// CRITICAL + +// This is used anywhere NimBLE modifies global data structures. +// We need to protect between: +// - A MicroPython VM thread. +// - The NimBLE "task" (e.g. PENDSV on STM32, pthread on Unix). +// On STM32, by disabling PENDSV, we ensure that either: +// - If we're in the NimBLE task, we're exclusive anyway. +// - If we're in a VM thread, we can't be interrupted by the NimBLE task, or switched to another thread. +// On Unix, there's a global mutex. + +// TODO: Both ports currently use MICROPY_PY_BLUETOOTH_ENTER in their implementation, +// maybe this doesn't need to be port-specific? + +uint32_t ble_npl_hw_enter_critical(void) { + DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); + return mp_bluetooth_nimble_hci_uart_enter_critical(); +} + +void ble_npl_hw_exit_critical(uint32_t ctx) { + DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); + mp_bluetooth_nimble_hci_uart_exit_critical(ctx); +} diff --git a/extmod/nimble/nimble/npl_os.c b/extmod/nimble/nimble/npl_os.c index 620dcb0ae..20c260405 100644 --- a/extmod/nimble/nimble/npl_os.c +++ b/extmod/nimble/nimble/npl_os.c @@ -127,6 +127,12 @@ void *nimble_realloc(void *ptr, size_t size) { return ptr2; } +// No-op implementation (only used by NimBLE logging). +int nimble_sprintf(char *str, const char *fmt, ...) { + str[0] = 0; + return 0; +} + /******************************************************************************/ // EVENTQ diff --git a/extmod/re1.5/recursiveloop_no_nlr.c b/extmod/re1.5/recursiveloop_no_nlr.c new file mode 100644 index 000000000..da4141cd1 --- /dev/null +++ b/extmod/re1.5/recursiveloop_no_nlr.c @@ -0,0 +1,87 @@ +// Copyright 2007-2009 Russ Cox. All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "re1.5.h" + +static int +recursiveloop(char *pc, const char *sp, Subject *input, const char **subp, int nsubp) +{ + const char *old; + int off; + int ret; + + if (re1_5_stack_chk()) return -1; + + for(;;) { + if(inst_is_consumer(*pc)) { + // If we need to match a character, but there's none left, it's fail + if(sp >= input->end) + return 0; + } + switch(*pc++) { + case Char: + if(*sp != *pc++) + return 0; + case Any: + sp++; + continue; + case Class: + case ClassNot: + if (!_re1_5_classmatch(pc, sp)) + return 0; + pc += *(unsigned char*)pc * 2 + 1; + sp++; + continue; + case NamedClass: + if (!_re1_5_namedclassmatch(pc, sp)) + return 0; + pc++; + sp++; + continue; + case Match: + return 1; + case Jmp: + off = (signed char)*pc++; + pc = pc + off; + continue; + case Split: + off = (signed char)*pc++; + if((ret = recursiveloop(pc, sp, input, subp, nsubp))) + return ret; + pc = pc + off; + continue; + case RSplit: + off = (signed char)*pc++; + if((ret = recursiveloop(pc + off, sp, input, subp, nsubp))) + return ret; + continue; + case Save: + off = (unsigned char)*pc++; + if(off >= nsubp) { + continue; + } + old = subp[off]; + subp[off] = sp; + if((ret = recursiveloop(pc, sp, input, subp, nsubp))) + return ret; + subp[off] = old; + return 0; + case Bol: + if(sp != input->begin) + return 0; + continue; + case Eol: + if(sp != input->end) + return 0; + continue; + } + re1_5_fatal("recursiveloop"); + } +} + +int +re1_5_recursiveloopprog(ByteProg *prog, Subject *input, const char **subp, int nsubp, int is_anchored) +{ + return recursiveloop(HANDLE_ANCHORED(prog->insts, is_anchored), input->begin, input, subp, nsubp); +} diff --git a/extmod/uasyncio/__init__.py b/extmod/uasyncio/__init__.py index da8b58061..08f924cf2 100644 --- a/extmod/uasyncio/__init__.py +++ b/extmod/uasyncio/__init__.py @@ -7,6 +7,7 @@ _attrs = { "wait_for": "funcs", + "wait_for_ms": "funcs", "gather": "funcs", "Event": "event", "Lock": "lock", diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index 7a4bddf25..6e1305c94 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -4,16 +4,16 @@ from . import core -async def wait_for(aw, timeout): +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): - await core.sleep(timeout) + def cancel(aw, timeout, sleep): + await sleep(timeout) aw.cancel() - cancel_task = core.create_task(cancel(aw, timeout)) + cancel_task = core.create_task(cancel(aw, timeout, sleep)) try: ret = await aw except core.CancelledError: @@ -29,6 +29,10 @@ def cancel(aw, timeout): 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)): diff --git a/extmod/uasyncio/stream.py b/extmod/uasyncio/stream.py index 2a1efd1a1..b6d787e4f 100644 --- a/extmod/uasyncio/stream.py +++ b/extmod/uasyncio/stream.py @@ -30,6 +30,18 @@ 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: diff --git a/extmod/uos_dupterm.c b/extmod/uos_dupterm.c index ad706d524..1ad46fc9a 100644 --- a/extmod/uos_dupterm.c +++ b/extmod/uos_dupterm.c @@ -93,6 +93,9 @@ uintptr_t mp_uos_dupterm_poll(uintptr_t poll_flags) { } int mp_uos_dupterm_rx_chr(void) { +#if __WASM__ + #pragma message "error: incompatible pointer to integer conversion returning 'mp_obj_t' (aka 'void *') from a function with result type 'int' [-Werror,-Wint-conversion]" +#else for (size_t idx = 0; idx < MICROPY_PY_OS_DUPTERM; ++idx) { if (MP_STATE_VM(dupterm_objs[idx]) == MP_OBJ_NULL) { continue; @@ -142,7 +145,7 @@ int mp_uos_dupterm_rx_chr(void) { mp_uos_deactivate(idx, "dupterm: Exception in read() method, deactivating: ", MP_OBJ_FROM_PTR(nlr.ret_val)); } } - +#endif // No chars available return -1; } diff --git a/extmod/vfs.c b/extmod/vfs.c index d1291068a..da2f0710a 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -141,12 +141,19 @@ mp_import_stat_t mp_vfs_import_stat(const char *path) { // delegate to vfs.stat() method mp_obj_t path_o = mp_obj_new_str(path_out, strlen(path_out)); +#if NO_NLR + mp_obj_t stat = mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_o); + if (stat == MP_OBJ_NULL) { + // assume an exception means that the path is not found + MP_STATE_THREAD(active_exception) = NULL; +#else mp_obj_t stat; nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { stat = mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_o); nlr_pop(); } else { +#endif // assume an exception means that the path is not found return MP_IMPORT_STAT_NO_EXIST; } @@ -162,6 +169,9 @@ mp_import_stat_t mp_vfs_import_stat(const char *path) { STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 +#if NO_NLR +#error LFS1/2 unsupported +#endif nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t vfs = MP_OBJ_NULL; diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index ae1bef57d..1bcd471f2 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -52,7 +52,7 @@ STATIC fs_user_mount_t *disk_get_device(void *bdev) { /* Read Sector(s) */ /*-----------------------------------------------------------------------*/ -DRESULT disk_read ( +DRESULT disk_read( bdev_t pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ @@ -72,7 +72,7 @@ DRESULT disk_read ( /* Write Sector(s) */ /*-----------------------------------------------------------------------*/ -DRESULT disk_write ( +DRESULT disk_write( bdev_t pdrv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address (LBA) */ @@ -98,7 +98,7 @@ DRESULT disk_write ( /* Miscellaneous Functions */ /*-----------------------------------------------------------------------*/ -DRESULT disk_ioctl ( +DRESULT disk_ioctl( bdev_t pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index c62409678..24816433b 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -442,7 +442,7 @@ 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, &path_obj); + 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_posix_file.c b/extmod/vfs_posix_file.c index 264764b4e..5398cef45 100644 --- a/extmod/vfs_posix_file.c +++ b/extmod/vfs_posix_file.c @@ -47,7 +47,11 @@ 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) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("I/O operation on closed file"))); +#else mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file")); +#endif } } #else diff --git a/extra-patches/4998-fstrings.diff b/extra-patches/4998-fstrings.diff new file mode 100644 index 000000000..03fa6bbdc --- /dev/null +++ b/extra-patches/4998-fstrings.diff @@ -0,0 +1,344 @@ +--- micropython-master-wapy/ports/bare-arm/mpconfigport.h 2020-05-27 19:38:53.586456962 +0200 ++++ micropython-master-wapy-fstrings/ports/bare-arm/mpconfigport.h 2020-06-01 09:53:03.154996698 +0200 +@@ -37,6 +37,7 @@ + #define MICROPY_PY_ARRAY (0) + #define MICROPY_PY_ATTRTUPLE (0) + #define MICROPY_PY_COLLECTIONS (0) ++#define MICROPY_PY_FSTRING (0) + #define MICROPY_PY_MATH (0) + #define MICROPY_PY_CMATH (0) + #define MICROPY_PY_IO (0) +--- micropython-master-wapy/ports/unix/mpconfigport.h 2020-05-27 19:38:53.670455849 +0200 ++++ micropython-master-wapy-fstrings/ports/unix/mpconfigport.h 2020-06-01 09:53:03.154996698 +0200 +@@ -122,6 +122,7 @@ + #define MICROPY_PY_SYS_EXC_INFO (1) + #define MICROPY_PY_COLLECTIONS_DEQUE (1) + #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) ++#define MICROPY_PY_FSTRING (1) + #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS + #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) + #endif +--- micropython-master-wapy/ports/windows/mpconfigport.h 2020-05-31 18:13:31.261254328 +0200 ++++ micropython-master-wapy-fstrings/ports/windows/mpconfigport.h 2020-06-01 09:53:03.154996698 +0200 +@@ -90,6 +90,7 @@ + #define MICROPY_PY_SYS_EXC_INFO (1) + #define MICROPY_PY_COLLECTIONS_DEQUE (1) + #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) ++#define MICROPY_PY_FSTRING (1) + #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) + #define MICROPY_PY_MATH_ISCLOSE (1) + #define MICROPY_PY_CMATH (1) +--- micropython-master-wapy/py/lexer.c 2020-06-01 17:50:09.054726674 +0200 ++++ micropython-master-wapy-fstrings/py/lexer.c 2020-06-01 09:53:25.920692221 +0200 +@@ -62,6 +62,10 @@ + return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3; + } + ++STATIC bool is_char_or4(mp_lexer_t *lex, byte c1, byte c2, byte c3, byte c4) { ++ return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3 || lex->chr0 == c4; ++} ++ + STATIC bool is_char_following(mp_lexer_t *lex, byte c) { + return lex->chr1 == c; + } +@@ -105,7 +109,9 @@ + + STATIC bool is_string_or_bytes(mp_lexer_t *lex) { + return is_char_or(lex, '\'', '\"') +- || (is_char_or3(lex, 'r', 'u', 'b') && is_char_following_or(lex, '\'', '\"')) ++ || (is_char_or4(lex, 'r', 'u', 'b', 'f') && is_char_following_or(lex, '\'', '\"')) ++ || (((is_char_and(lex, 'r', 'f') || is_char_and(lex, 'f', 'r')) ++ && is_char_following_following_or(lex, '\'', '\"'))) + || ((is_char_and(lex, 'r', 'b') || is_char_and(lex, 'b', 'r')) + && is_char_following_following_or(lex, '\'', '\"')); + } +@@ -119,6 +125,29 @@ + return is_head_of_identifier(lex) || is_digit(lex); + } + ++STATIC void swap_char_banks(mp_lexer_t *lex) { ++ if (lex->vstr_postfix_processing) { ++ lex->chr3 = lex->chr0; ++ lex->chr4 = lex->chr1; ++ lex->chr5 = lex->chr2; ++ lex->chr0 = lex->vstr_postfix.buf[0]; ++ lex->chr1 = lex->vstr_postfix.buf[1]; ++ lex->chr2 = lex->vstr_postfix.buf[2]; ++ ++ lex->vstr_postfix_idx = 3; ++ } else { ++ // blindly reset to the "backup" bank when done postfix processing ++ // this restores control to the mp_reader ++ lex->chr0 = lex->chr3; ++ lex->chr1 = lex->chr4; ++ lex->chr2 = lex->chr5; ++ // willfully ignoring setting chr3-5 here - WARNING consider those garbage data now ++ ++ vstr_reset(&lex->vstr_postfix); ++ lex->vstr_postfix_idx = 0; ++ } ++} ++ + STATIC void next_char(mp_lexer_t *lex) { + if (lex->chr0 == '\n') { + // a new line +@@ -134,7 +163,16 @@ + + lex->chr0 = lex->chr1; + lex->chr1 = lex->chr2; +- lex->chr2 = lex->reader.readbyte(lex->reader.data); ++ ++ if (lex->vstr_postfix_processing) { ++ if (lex->vstr_postfix_idx == lex->vstr_postfix.len) { ++ lex->chr2 = '\0'; ++ } else { ++ lex->chr2 = lex->vstr_postfix.buf[lex->vstr_postfix_idx++]; ++ } ++ } else { ++ lex->chr2 = lex->reader.readbyte(lex->reader.data); ++ } + + if (lex->chr1 == '\r') { + // CR is a new line, converted to LF +@@ -149,6 +187,11 @@ + if (lex->chr2 == MP_LEXER_EOF && lex->chr1 != MP_LEXER_EOF && lex->chr1 != '\n') { + lex->chr2 = '\n'; + } ++ ++ if (lex->vstr_postfix_processing && lex->chr0 == '\0') { ++ lex->vstr_postfix_processing = false; ++ swap_char_banks(lex); ++ } + } + + STATIC void indent_push(mp_lexer_t *lex, size_t indent) { +@@ -270,7 +313,7 @@ + return true; + } + +-STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { ++STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) { + // get first quoting character + char quote_char = '\''; + if (is_char(lex, '\"')) { +@@ -291,15 +334,69 @@ + } + + size_t n_closing = 0; ++ # if MICROPY_PY_FSTRING ++ bool in_expression = false; ++ bool expression_eat = true; ++ # endif ++ + while (!is_end(lex) && (num_quotes > 1 || !is_char(lex, '\n')) && n_closing < num_quotes) { + if (is_char(lex, quote_char)) { + n_closing += 1; + vstr_add_char(&lex->vstr, CUR_CHAR(lex)); + } else { + n_closing = 0; ++ ++ # if MICROPY_PY_FSTRING ++ if (is_fstring && is_char(lex, '{')) { ++ vstr_add_char(&lex->vstr, CUR_CHAR(lex)); ++ in_expression = !in_expression; ++ expression_eat = in_expression; ++ ++ if (lex->vstr_postfix.len == 0) { ++ vstr_add_str(&lex->vstr_postfix, ".format("); ++ } ++ ++ next_char(lex); ++ continue; ++ } ++ ++ if (is_fstring && is_char(lex, '}')) { ++ vstr_add_char(&lex->vstr, CUR_CHAR(lex)); ++ ++ if (in_expression) { ++ in_expression = false; ++ vstr_add_char(&lex->vstr_postfix, ','); ++ } ++ ++ next_char(lex); ++ continue; ++ } ++ ++ if (in_expression) { ++ // throw errors for illegal chars inside f-string expressions ++ if (is_char(lex, '#') || is_char(lex, '\\')) { ++ lex->tok_kind = MP_TOKEN_MALFORMED_FSTRING; ++ return; ++ } else if (is_char(lex, ':')) { ++ expression_eat = false; ++ } ++ ++ unichar c = CUR_CHAR(lex); ++ if (expression_eat) { ++ vstr_add_char(&lex->vstr_postfix, c); ++ } else { ++ vstr_add_char(&lex->vstr, c); ++ } ++ ++ next_char(lex); ++ continue; ++ } ++ # endif ++ + if (is_char(lex, '\\')) { + next_char(lex); + unichar c = CUR_CHAR(lex); ++ + if (is_raw) { + // raw strings allow escaping of quotes, but the backslash is also emitted + vstr_add_char(&lex->vstr, '\\'); +@@ -453,6 +550,13 @@ + } + + void mp_lexer_to_next(mp_lexer_t *lex) { ++ if (lex->vstr_postfix.len && !lex->vstr_postfix_processing) { ++ // end format call injection ++ vstr_add_char(&lex->vstr_postfix, ')'); ++ lex->vstr_postfix_processing = true; ++ swap_char_banks(lex); ++ } ++ + // start new token text + vstr_reset(&lex->vstr); + +@@ -508,6 +612,7 @@ + do { + // parse type codes + bool is_raw = false; ++ bool is_fstring = false; + mp_token_kind_t kind = MP_TOKEN_STRING; + int n_char = 0; + if (is_char(lex, 'u')) { +@@ -526,7 +631,23 @@ + kind = MP_TOKEN_BYTES; + n_char = 2; + } ++ # if MICROPY_PY_FSTRING ++ if (is_char_following(lex, 'f')) { ++ lex->tok_kind = MP_TOKEN_FSTRING_RAW; ++ break; ++ } ++ # endif + } ++ # if MICROPY_PY_FSTRING ++ else if (is_char(lex, 'f')) { ++ if (is_char_following(lex, 'r')) { ++ lex->tok_kind = MP_TOKEN_FSTRING_RAW; ++ break; ++ } ++ n_char = 1; ++ is_fstring = true; ++ } ++ # endif + + // Set or check token kind + if (lex->tok_kind == MP_TOKEN_END) { +@@ -545,13 +666,12 @@ + } + + // Parse the literal +- parse_string_literal(lex, is_raw); ++ parse_string_literal(lex, is_raw, is_fstring); + + // Skip whitespace so we can check if there's another string following + skip_whitespace(lex, true); + + } while (is_string_or_bytes(lex)); +- + } else if (is_head_of_identifier(lex)) { + lex->tok_kind = MP_TOKEN_NAME; + +@@ -715,6 +835,7 @@ + } + #endif + vstr_init(&lex->vstr, 32); ++ vstr_init(&lex->vstr_postfix, 0); + + // store sentinel for first indentation level + lex->indent_level[0] = 0; +--- micropython-master-wapy/py/lexer.h 2020-05-27 19:38:53.682455690 +0200 ++++ micropython-master-wapy-fstrings/py/lexer.h 2020-06-01 09:53:03.154996698 +0200 +@@ -44,6 +44,10 @@ + MP_TOKEN_INVALID, + MP_TOKEN_DEDENT_MISMATCH, + MP_TOKEN_LONELY_STRING_OPEN, ++ # if MICROPY_PY_FSTRING ++ MP_TOKEN_MALFORMED_FSTRING, ++ MP_TOKEN_FSTRING_RAW, ++ # endif + + MP_TOKEN_NEWLINE, + MP_TOKEN_INDENT, +@@ -157,6 +161,7 @@ + mp_reader_t reader; // stream source + + unichar chr0, chr1, chr2; // current cached characters from source ++ unichar chr3, chr4, chr5; // current cached characters from alt source + + size_t line; // current source line + size_t column; // current source column +@@ -172,6 +177,9 @@ + size_t tok_column; // token source column + mp_token_kind_t tok_kind; // token kind + vstr_t vstr; // token data ++ vstr_t vstr_postfix; // postfix to apply to string ++ bool vstr_postfix_processing; ++ uint16_t vstr_postfix_idx; + } mp_lexer_t; + + mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader); +--- micropython-master-wapy/py/mpconfig.h 2020-06-01 17:50:09.066726516 +0200 ++++ micropython-master-wapy-fstrings/py/mpconfig.h 2020-06-01 09:53:25.934692033 +0200 +@@ -1115,6 +1115,12 @@ + #define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (0) + #endif + ++// Whether to include support for PEP-498 f-strings ++#ifndef MICROPY_PY_FSTRING ++#define MICROPY_PY_FSTRING (0) ++#endif ++ ++ + // Whether to provide "math" module + #ifndef MICROPY_PY_MATH + #define MICROPY_PY_MATH (1) +--- micropython-master-wapy/py/parse.c 2020-06-01 17:50:09.108725959 +0200 ++++ micropython-master-wapy-fstrings/py/parse.c 2020-06-01 09:53:25.974691498 +0200 +@@ -1185,6 +1185,14 @@ + } else if (lex->tok_kind == MP_TOKEN_DEDENT_MISMATCH) { + exc = mp_obj_new_exception_msg(&mp_type_IndentationError, + MP_ERROR_TEXT("unindent doesn't match any outer indent level")); ++ # if MICROPY_PY_FSTRING ++ } else if (lex->tok_kind == MP_TOKEN_MALFORMED_FSTRING) { ++ exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, ++ MP_ERROR_TEXT("malformed f-string")); ++ } else if (lex->tok_kind == MP_TOKEN_FSTRING_RAW) { ++ exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, ++ MP_ERROR_TEXT("raw f-strings are not supported")); ++ # endif + } else { + exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, + MP_ERROR_TEXT("invalid syntax")); +--- micropython-master-wapy/tests/cmdline/cmd_parsetree.py.exp 2020-05-27 19:38:53.712455292 +0200 ++++ micropython-master-wapy-fstrings/tests/cmdline/cmd_parsetree.py.exp 2020-06-01 09:53:03.154996698 +0200 +@@ -1,6 +1,6 @@ + ---------------- + [ 4] rule(1) (n=9) +- tok(4) ++ tok(10) + [ 4] rule(22) (n=4) + id(i) + [ 4] rule(44) (n=1) +@@ -9,7 +9,7 @@ + NULL + [ 6] rule(5) (n=2) + id(a) +- tok(14) ++ tok(20) + [ 7] rule(5) (n=2) + id(b) + str(str) diff --git a/lib/asf4 b/lib/asf4 deleted file mode 160000 index d270f79aa..000000000 --- a/lib/asf4 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d270f79aa16dd8fd4ae3b6c14544283dcb992e9c diff --git a/lib/axtls b/lib/axtls deleted file mode 160000 index 43a6e6bd3..000000000 --- a/lib/axtls +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 43a6e6bd3bbc03dc501e16b89fba0ef042ed3ea0 diff --git a/lib/berkeley-db-1.xx b/lib/berkeley-db-1.xx deleted file mode 160000 index 35aaec441..000000000 --- a/lib/berkeley-db-1.xx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 35aaec4418ad78628a3b935885dd189d41ce779b diff --git a/lib/btstack b/lib/btstack deleted file mode 160000 index c8b9823f6..000000000 --- a/lib/btstack +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c8b9823f68c6af0fa52e2c4e009aba4dbf257232 diff --git a/lib/embed/abort_.c b/lib/embed/abort_.c index 3051eae81..9e6fccf8f 100644 --- a/lib/embed/abort_.c +++ b/lib/embed/abort_.c @@ -3,5 +3,10 @@ NORETURN void abort_(void); NORETURN void abort_(void) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called"))); + for(;;){} +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called")); +#endif } diff --git a/lib/libc/string0.c b/lib/libc/string0.c index 8c86bf65f..19ad14d0f 100644 --- a/lib/libc/string0.c +++ b/lib/libc/string0.c @@ -169,6 +169,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/libffi b/lib/libffi deleted file mode 160000 index e9de7e35f..000000000 --- a/lib/libffi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e9de7e35f2339598b16cbb375f9992643ed81209 diff --git a/lib/libm_dbl/round.c b/lib/libm_dbl/round.c new file mode 100644 index 000000000..130d58d25 --- /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/lwip b/lib/lwip deleted file mode 160000 index 159e31b68..000000000 --- a/lib/lwip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 159e31b689577dbf69cf0683bbaffbd71fa5ee10 diff --git a/lib/mbedtls b/lib/mbedtls deleted file mode 160000 index 3f8d78411..000000000 --- a/lib/mbedtls +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f8d78411a26e833db18d9fbde0e2f0baeda87f0 diff --git a/lib/mbedtls_errors/README.md b/lib/mbedtls_errors/README.md new file mode 100644 index 000000000..0e13021eb --- /dev/null +++ b/lib/mbedtls_errors/README.md @@ -0,0 +1,42 @@ +MBEDTLS Error Strings for MicroPython +===================================== + +This directory contains source code and tools to rework the Mbedtls error strings for +micropython to use less space. In short, instead of storing and printing something like +"SSL - Our own certificate(s) is/are too large to send in an SSL message" it prints +the name of the error #define, which would be "MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE" in +this case, and only stores `SSL_CERTIFICATE_TOO_LARGE` in flash. The exact Mbedtls error +defines are used because they're easy to search for to find more detailed information. + +Mbedtls defines a specific format for error value #defines and +includes a Perl script to gather all `MBEDTLS_ERR` defines from includes files together with +english error text. From that the Perl script generates `mbedtls_strerror()`. The files in this +directory modify this process to produce a more space efficient error lookup table with +shorter error strings. + +The files are as follows: +- `generate_errors.diff` - diff for original mbedtls perl script +- `error.fmt` - modified code template for MicroPython +- `mp_mbedtls_errors.c` - source file with `mbedtls_strerror` this is built using the include + files in `../mbedtls` +- `do-mp.sh` - shell script to produce `mp_mbedtls_errors.c` +- `tester.c` - simple C main to test `mp_mbedtls_errors.c` locally on a dev box +- `do-test.sh` - shell script to produce `mp_mbedtls_errors.c` and compile the `tester` app +- `do-esp32.sh` - shell script to produce `esp32_mbedtls_errors.c` -- see below + +In order not to store multiple copies of `mbedtls_errors.c` +([https://github.com/micropython/micropython/pull/5819#discussion_r445528006](see)) +it is assumed that all ports use the same version of mbedtls with the same error #defines. +This is true as of MP v1.13, and ESP-IDF versions 3.3.2 and 4.0.1. If anything changes in the +future the `do-esp32.sh` script can be used to generate an esp32-specific version. + +### How-to + +- To build MicroPython all that is needed is to include the `mp_mbedtls_errors.c` into the build + (the Makefiles do this automatically). Note that Perl is not needed for routine MicroPython + builds. +- When a new version of Mbedtls is pulled-in the `do-mp.sh` script should be run to + re-generate `mp_mbedtls_errors.c`. +- The `tester` app should be run if changes to the string handling in `error.fmt` are made: + it tests that there is not an off-by-one error in the string copying/appending, etc. +- To include `mbedtls_strerror` error strings define `MBEDTLS_ERROR_C` in the build. diff --git a/lib/mbedtls_errors/do-esp32.sh b/lib/mbedtls_errors/do-esp32.sh new file mode 100755 index 000000000..6fd468241 --- /dev/null +++ b/lib/mbedtls_errors/do-esp32.sh @@ -0,0 +1,7 @@ +#! /bin/bash -e +# Generate esp32_mbedtls_errors.c for use in the Esp32 port, with the ESP-IDF version of mbedtls +# The IDF_PATH env var must be set to the top-level dir of ESPIDF +echo "IDF_PATH=$IDF_PATH" +MBEDTLS=$IDF_PATH/components/mbedtls/mbedtls +patch -o esp32_generate_errors.pl $MBEDTLS/scripts/generate_errors.pl +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#define mbedtls_snprintf snprintf +#define mbedtls_time_t time_t +#endif + +#if defined(MBEDTLS_ERROR_C) + +#include + +HEADER_INCLUDED + +// Error code table type +struct ssl_errs { + int16_t errnum; + const char *errstr; +}; + +// Table of high level error codes +static const struct ssl_errs mbedtls_high_level_error_tab[] = { +// BEGIN generated code +HIGH_LEVEL_CODE_CHECKS +// END generated code +}; + +static const struct ssl_errs mbedtls_low_level_error_tab[] = { +// Low level error codes +// +// BEGIN generated code +LOW_LEVEL_CODE_CHECKS +// END generated code +}; + +static const char *mbedtls_err_prefix = "MBEDTLS_ERR_"; +#define MBEDTLS_ERR_PREFIX_LEN ( sizeof("MBEDTLS_ERR_")-1 ) + +// copy error text into buffer, ensure null termination, return strlen of result +static size_t mbedtls_err_to_str(int err, const struct ssl_errs tab[], int tab_len, char *buf, size_t buflen) { + if (buflen == 0) return 0; + + // prefix for all error names + strncpy(buf, mbedtls_err_prefix, buflen); + if (buflen <= MBEDTLS_ERR_PREFIX_LEN+1) { + buf[buflen-1] = 0; + return buflen-1; + } + + // append error name from table + for (int i = 0; i < tab_len; i++) { + if (tab[i].errnum == err) { + strncpy(buf+MBEDTLS_ERR_PREFIX_LEN, tab[i].errstr, buflen-MBEDTLS_ERR_PREFIX_LEN); + buf[buflen-1] = 0; + return strlen(buf); + } + } + + mbedtls_snprintf(buf+MBEDTLS_ERR_PREFIX_LEN, buflen-MBEDTLS_ERR_PREFIX_LEN, "UNKNOWN (0x%04X)", + err); + return strlen(buf); +} + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +void mbedtls_strerror(int ret, char *buf, size_t buflen) { + int use_ret; + + if (buflen == 0) return; + + buf[buflen-1] = 0; + + if (ret < 0) ret = -ret; + + // + // High-level error codes + // + uint8_t got_hl = (ret & 0xFF80) != 0; + if (got_hl) { + use_ret = ret & 0xFF80; + + // special case +#if defined(MBEDTLS_SSL_TLS_C) + if (use_ret == -(MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE)) { + strncpy(buf, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE", buflen); + buf[buflen-1] = 0; + return; + } +#endif + + size_t len = mbedtls_err_to_str(use_ret, mbedtls_high_level_error_tab, + ARRAY_SIZE(mbedtls_high_level_error_tab), buf, buflen); + + buf += len; + buflen -= len; + if (buflen == 0) return; + } + + // + // Low-level error codes + // + use_ret = ret & ~0xFF80; + + if (use_ret == 0) return; + + // If high level code is present, make a concatenation between both error strings. + if (got_hl) { + if (buflen < 2) return; + *buf++ = '+'; + buflen--; + } + + mbedtls_err_to_str(use_ret, mbedtls_low_level_error_tab, + ARRAY_SIZE(mbedtls_low_level_error_tab), buf, buflen); +} + +#else /* MBEDTLS_ERROR_C */ + +#if defined(MBEDTLS_ERROR_STRERROR_DUMMY) + +/* + * Provide an non-function in case MBEDTLS_ERROR_C is not defined + */ +void mbedtls_strerror( int ret, char *buf, size_t buflen ) +{ + ((void) ret); + + if( buflen > 0 ) + buf[0] = '\0'; +} + +#endif /* MBEDTLS_ERROR_STRERROR_DUMMY */ + +#endif /* MBEDTLS_ERROR_C */ diff --git a/lib/mbedtls_errors/generate_errors.diff b/lib/mbedtls_errors/generate_errors.diff new file mode 100644 index 000000000..ad24c372f --- /dev/null +++ b/lib/mbedtls_errors/generate_errors.diff @@ -0,0 +1,22 @@ +--- generate_errors_orig.pl 2020-06-20 08:40:38.819060379 -0700 ++++ generate_errors.pl 2020-06-20 08:47:26.511163591 -0700 +@@ -162,16 +162,12 @@ + + if ($error_name eq "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE") + { +- ${$code_check} .= "${white_space}if( use_ret == -($error_name) )\n". +- "${white_space}\{\n". +- "${white_space} mbedtls_snprintf( buf, buflen, \"$module_name - $description\" );\n". +- "${white_space} return;\n". +- "${white_space}}\n" ++ # no-op, this case is hard-coded in error.fmt + } + else + { +- ${$code_check} .= "${white_space}if( use_ret == -($error_name) )\n". +- "${white_space} mbedtls_snprintf( buf, buflen, \"$module_name - $description\" );\n" ++ my $error_text = $error_name =~ s/^MBEDTLS_ERR_//r; ++ ${$code_check} .= "${white_space}{ -($error_name), \"$error_text\" },\n" + } + }; + diff --git a/lib/mbedtls_errors/mp_mbedtls_errors.c b/lib/mbedtls_errors/mp_mbedtls_errors.c new file mode 100644 index 000000000..03a91f0dc --- /dev/null +++ b/lib/mbedtls_errors/mp_mbedtls_errors.c @@ -0,0 +1,705 @@ +/* + * Error message information + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#if !defined(MBEDTLS_CONFIG_FILE) +#include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif + +#if defined(MBEDTLS_ERROR_C) || defined(MBEDTLS_ERROR_STRERROR_DUMMY) +#include "mbedtls/error.h" +#include +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#define mbedtls_snprintf snprintf +#define mbedtls_time_t time_t +#endif + +#if defined(MBEDTLS_ERROR_C) + +#include + +#if defined(MBEDTLS_AES_C) +#include "mbedtls/aes.h" +#endif + +#if defined(MBEDTLS_ARC4_C) +#include "mbedtls/arc4.h" +#endif + +#if defined(MBEDTLS_ARIA_C) +#include "mbedtls/aria.h" +#endif + +#if defined(MBEDTLS_BASE64_C) +#include "mbedtls/base64.h" +#endif + +#if defined(MBEDTLS_BIGNUM_C) +#include "mbedtls/bignum.h" +#endif + +#if defined(MBEDTLS_BLOWFISH_C) +#include "mbedtls/blowfish.h" +#endif + +#if defined(MBEDTLS_CAMELLIA_C) +#include "mbedtls/camellia.h" +#endif + +#if defined(MBEDTLS_CCM_C) +#include "mbedtls/ccm.h" +#endif + +#if defined(MBEDTLS_CHACHA20_C) +#include "mbedtls/chacha20.h" +#endif + +#if defined(MBEDTLS_CHACHAPOLY_C) +#include "mbedtls/chachapoly.h" +#endif + +#if defined(MBEDTLS_CIPHER_C) +#include "mbedtls/cipher.h" +#endif + +#if defined(MBEDTLS_CMAC_C) +#include "mbedtls/cmac.h" +#endif + +#if defined(MBEDTLS_CTR_DRBG_C) +#include "mbedtls/ctr_drbg.h" +#endif + +#if defined(MBEDTLS_DES_C) +#include "mbedtls/des.h" +#endif + +#if defined(MBEDTLS_DHM_C) +#include "mbedtls/dhm.h" +#endif + +#if defined(MBEDTLS_ECP_C) +#include "mbedtls/ecp.h" +#endif + +#if defined(MBEDTLS_ENTROPY_C) +#include "mbedtls/entropy.h" +#endif + +#if defined(MBEDTLS_GCM_C) +#include "mbedtls/gcm.h" +#endif + +#if defined(MBEDTLS_HKDF_C) +#include "mbedtls/hkdf.h" +#endif + +#if defined(MBEDTLS_HMAC_DRBG_C) +#include "mbedtls/hmac_drbg.h" +#endif + +#if defined(MBEDTLS_MD_C) +#include "mbedtls/md.h" +#endif + +#if defined(MBEDTLS_MD2_C) +#include "mbedtls/md2.h" +#endif + +#if defined(MBEDTLS_MD4_C) +#include "mbedtls/md4.h" +#endif + +#if defined(MBEDTLS_MD5_C) +#include "mbedtls/md5.h" +#endif + +#if defined(MBEDTLS_NET_C) +#include "mbedtls/net_sockets.h" +#endif + +#if defined(MBEDTLS_OID_C) +#include "mbedtls/oid.h" +#endif + +#if defined(MBEDTLS_PADLOCK_C) +#include "mbedtls/padlock.h" +#endif + +#if defined(MBEDTLS_PEM_PARSE_C) || defined(MBEDTLS_PEM_WRITE_C) +#include "mbedtls/pem.h" +#endif + +#if defined(MBEDTLS_PK_C) +#include "mbedtls/pk.h" +#endif + +#if defined(MBEDTLS_PKCS12_C) +#include "mbedtls/pkcs12.h" +#endif + +#if defined(MBEDTLS_PKCS5_C) +#include "mbedtls/pkcs5.h" +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#endif + +#if defined(MBEDTLS_POLY1305_C) +#include "mbedtls/poly1305.h" +#endif + +#if defined(MBEDTLS_RIPEMD160_C) +#include "mbedtls/ripemd160.h" +#endif + +#if defined(MBEDTLS_RSA_C) +#include "mbedtls/rsa.h" +#endif + +#if defined(MBEDTLS_SHA1_C) +#include "mbedtls/sha1.h" +#endif + +#if defined(MBEDTLS_SHA256_C) +#include "mbedtls/sha256.h" +#endif + +#if defined(MBEDTLS_SHA512_C) +#include "mbedtls/sha512.h" +#endif + +#if defined(MBEDTLS_SSL_TLS_C) +#include "mbedtls/ssl.h" +#endif + +#if defined(MBEDTLS_THREADING_C) +#include "mbedtls/threading.h" +#endif + +#if defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_X509_CREATE_C) +#include "mbedtls/x509.h" +#endif + +#if defined(MBEDTLS_XTEA_C) +#include "mbedtls/xtea.h" +#endif + + +// Error code table type +struct ssl_errs { + int16_t errnum; + const char *errstr; +}; + +// Table of high level error codes +static const struct ssl_errs mbedtls_high_level_error_tab[] = { +// BEGIN generated code +#if defined(MBEDTLS_CIPHER_C) + { -(MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE), "CIPHER_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA), "CIPHER_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CIPHER_ALLOC_FAILED), "CIPHER_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_CIPHER_INVALID_PADDING), "CIPHER_INVALID_PADDING" }, + { -(MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED), "CIPHER_FULL_BLOCK_EXPECTED" }, + { -(MBEDTLS_ERR_CIPHER_AUTH_FAILED), "CIPHER_AUTH_FAILED" }, + { -(MBEDTLS_ERR_CIPHER_INVALID_CONTEXT), "CIPHER_INVALID_CONTEXT" }, + { -(MBEDTLS_ERR_CIPHER_HW_ACCEL_FAILED), "CIPHER_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CIPHER_C */ + +#if defined(MBEDTLS_DHM_C) + { -(MBEDTLS_ERR_DHM_BAD_INPUT_DATA), "DHM_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_DHM_READ_PARAMS_FAILED), "DHM_READ_PARAMS_FAILED" }, + { -(MBEDTLS_ERR_DHM_MAKE_PARAMS_FAILED), "DHM_MAKE_PARAMS_FAILED" }, + { -(MBEDTLS_ERR_DHM_READ_PUBLIC_FAILED), "DHM_READ_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_DHM_MAKE_PUBLIC_FAILED), "DHM_MAKE_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_DHM_CALC_SECRET_FAILED), "DHM_CALC_SECRET_FAILED" }, + { -(MBEDTLS_ERR_DHM_INVALID_FORMAT), "DHM_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_DHM_ALLOC_FAILED), "DHM_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_DHM_FILE_IO_ERROR), "DHM_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_DHM_HW_ACCEL_FAILED), "DHM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_DHM_SET_GROUP_FAILED), "DHM_SET_GROUP_FAILED" }, +#endif /* MBEDTLS_DHM_C */ + +#if defined(MBEDTLS_ECP_C) + { -(MBEDTLS_ERR_ECP_BAD_INPUT_DATA), "ECP_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL), "ECP_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE), "ECP_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_ECP_VERIFY_FAILED), "ECP_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_ECP_ALLOC_FAILED), "ECP_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_ECP_RANDOM_FAILED), "ECP_RANDOM_FAILED" }, + { -(MBEDTLS_ERR_ECP_INVALID_KEY), "ECP_INVALID_KEY" }, + { -(MBEDTLS_ERR_ECP_SIG_LEN_MISMATCH), "ECP_SIG_LEN_MISMATCH" }, + { -(MBEDTLS_ERR_ECP_HW_ACCEL_FAILED), "ECP_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_ECP_IN_PROGRESS), "ECP_IN_PROGRESS" }, +#endif /* MBEDTLS_ECP_C */ + +#if defined(MBEDTLS_MD_C) + { -(MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE), "MD_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_MD_BAD_INPUT_DATA), "MD_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_MD_ALLOC_FAILED), "MD_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_MD_FILE_IO_ERROR), "MD_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_MD_HW_ACCEL_FAILED), "MD_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD_C */ + +#if defined(MBEDTLS_PEM_PARSE_C) || defined(MBEDTLS_PEM_WRITE_C) + { -(MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT), "PEM_NO_HEADER_FOOTER_PRESENT" }, + { -(MBEDTLS_ERR_PEM_INVALID_DATA), "PEM_INVALID_DATA" }, + { -(MBEDTLS_ERR_PEM_ALLOC_FAILED), "PEM_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_PEM_INVALID_ENC_IV), "PEM_INVALID_ENC_IV" }, + { -(MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG), "PEM_UNKNOWN_ENC_ALG" }, + { -(MBEDTLS_ERR_PEM_PASSWORD_REQUIRED), "PEM_PASSWORD_REQUIRED" }, + { -(MBEDTLS_ERR_PEM_PASSWORD_MISMATCH), "PEM_PASSWORD_MISMATCH" }, + { -(MBEDTLS_ERR_PEM_FEATURE_UNAVAILABLE), "PEM_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PEM_BAD_INPUT_DATA), "PEM_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_PEM_PARSE_C || MBEDTLS_PEM_WRITE_C */ + +#if defined(MBEDTLS_PK_C) + { -(MBEDTLS_ERR_PK_ALLOC_FAILED), "PK_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_PK_TYPE_MISMATCH), "PK_TYPE_MISMATCH" }, + { -(MBEDTLS_ERR_PK_BAD_INPUT_DATA), "PK_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PK_FILE_IO_ERROR), "PK_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_PK_KEY_INVALID_VERSION), "PK_KEY_INVALID_VERSION" }, + { -(MBEDTLS_ERR_PK_KEY_INVALID_FORMAT), "PK_KEY_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PK_UNKNOWN_PK_ALG), "PK_UNKNOWN_PK_ALG" }, + { -(MBEDTLS_ERR_PK_PASSWORD_REQUIRED), "PK_PASSWORD_REQUIRED" }, + { -(MBEDTLS_ERR_PK_PASSWORD_MISMATCH), "PK_PASSWORD_MISMATCH" }, + { -(MBEDTLS_ERR_PK_INVALID_PUBKEY), "PK_INVALID_PUBKEY" }, + { -(MBEDTLS_ERR_PK_INVALID_ALG), "PK_INVALID_ALG" }, + { -(MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE), "PK_UNKNOWN_NAMED_CURVE" }, + { -(MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE), "PK_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PK_SIG_LEN_MISMATCH), "PK_SIG_LEN_MISMATCH" }, + { -(MBEDTLS_ERR_PK_HW_ACCEL_FAILED), "PK_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_PK_C */ + +#if defined(MBEDTLS_PKCS12_C) + { -(MBEDTLS_ERR_PKCS12_BAD_INPUT_DATA), "PKCS12_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PKCS12_FEATURE_UNAVAILABLE), "PKCS12_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PKCS12_PBE_INVALID_FORMAT), "PKCS12_PBE_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PKCS12_PASSWORD_MISMATCH), "PKCS12_PASSWORD_MISMATCH" }, +#endif /* MBEDTLS_PKCS12_C */ + +#if defined(MBEDTLS_PKCS5_C) + { -(MBEDTLS_ERR_PKCS5_BAD_INPUT_DATA), "PKCS5_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PKCS5_INVALID_FORMAT), "PKCS5_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PKCS5_FEATURE_UNAVAILABLE), "PKCS5_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PKCS5_PASSWORD_MISMATCH), "PKCS5_PASSWORD_MISMATCH" }, +#endif /* MBEDTLS_PKCS5_C */ + +#if defined(MBEDTLS_RSA_C) + { -(MBEDTLS_ERR_RSA_BAD_INPUT_DATA), "RSA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_RSA_INVALID_PADDING), "RSA_INVALID_PADDING" }, + { -(MBEDTLS_ERR_RSA_KEY_GEN_FAILED), "RSA_KEY_GEN_FAILED" }, + { -(MBEDTLS_ERR_RSA_KEY_CHECK_FAILED), "RSA_KEY_CHECK_FAILED" }, + { -(MBEDTLS_ERR_RSA_PUBLIC_FAILED), "RSA_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_RSA_PRIVATE_FAILED), "RSA_PRIVATE_FAILED" }, + { -(MBEDTLS_ERR_RSA_VERIFY_FAILED), "RSA_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE), "RSA_OUTPUT_TOO_LARGE" }, + { -(MBEDTLS_ERR_RSA_RNG_FAILED), "RSA_RNG_FAILED" }, + { -(MBEDTLS_ERR_RSA_UNSUPPORTED_OPERATION), "RSA_UNSUPPORTED_OPERATION" }, + { -(MBEDTLS_ERR_RSA_HW_ACCEL_FAILED), "RSA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_RSA_C */ + +#if defined(MBEDTLS_SSL_TLS_C) + { -(MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE), "SSL_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_SSL_BAD_INPUT_DATA), "SSL_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_SSL_INVALID_MAC), "SSL_INVALID_MAC" }, + { -(MBEDTLS_ERR_SSL_INVALID_RECORD), "SSL_INVALID_RECORD" }, + { -(MBEDTLS_ERR_SSL_CONN_EOF), "SSL_CONN_EOF" }, + { -(MBEDTLS_ERR_SSL_UNKNOWN_CIPHER), "SSL_UNKNOWN_CIPHER" }, + { -(MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN), "SSL_NO_CIPHER_CHOSEN" }, + { -(MBEDTLS_ERR_SSL_NO_RNG), "SSL_NO_RNG" }, + { -(MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE), "SSL_NO_CLIENT_CERTIFICATE" }, + { -(MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE), "SSL_CERTIFICATE_TOO_LARGE" }, + { -(MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED), "SSL_CERTIFICATE_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED), "SSL_PRIVATE_KEY_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED), "SSL_CA_CHAIN_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE), "SSL_UNEXPECTED_MESSAGE" }, + { -(MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED), "SSL_PEER_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY), "SSL_PEER_CLOSE_NOTIFY" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO), "SSL_BAD_HS_CLIENT_HELLO" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO), "SSL_BAD_HS_SERVER_HELLO" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE), "SSL_BAD_HS_CERTIFICATE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST), "SSL_BAD_HS_CERTIFICATE_REQUEST" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE), "SSL_BAD_HS_SERVER_KEY_EXCHANGE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE), "SSL_BAD_HS_SERVER_HELLO_DONE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY), "SSL_BAD_HS_CERTIFICATE_VERIFY" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC), "SSL_BAD_HS_CHANGE_CIPHER_SPEC" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_FINISHED), "SSL_BAD_HS_FINISHED" }, + { -(MBEDTLS_ERR_SSL_ALLOC_FAILED), "SSL_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_SSL_HW_ACCEL_FAILED), "SSL_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH), "SSL_HW_ACCEL_FALLTHROUGH" }, + { -(MBEDTLS_ERR_SSL_COMPRESSION_FAILED), "SSL_COMPRESSION_FAILED" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION), "SSL_BAD_HS_PROTOCOL_VERSION" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET), "SSL_BAD_HS_NEW_SESSION_TICKET" }, + { -(MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED), "SSL_SESSION_TICKET_EXPIRED" }, + { -(MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH), "SSL_PK_TYPE_MISMATCH" }, + { -(MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY), "SSL_UNKNOWN_IDENTITY" }, + { -(MBEDTLS_ERR_SSL_INTERNAL_ERROR), "SSL_INTERNAL_ERROR" }, + { -(MBEDTLS_ERR_SSL_COUNTER_WRAPPING), "SSL_COUNTER_WRAPPING" }, + { -(MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO), "SSL_WAITING_SERVER_HELLO_RENEGO" }, + { -(MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED), "SSL_HELLO_VERIFY_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL), "SSL_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE), "SSL_NO_USABLE_CIPHERSUITE" }, + { -(MBEDTLS_ERR_SSL_WANT_READ), "SSL_WANT_READ" }, + { -(MBEDTLS_ERR_SSL_WANT_WRITE), "SSL_WANT_WRITE" }, + { -(MBEDTLS_ERR_SSL_TIMEOUT), "SSL_TIMEOUT" }, + { -(MBEDTLS_ERR_SSL_CLIENT_RECONNECT), "SSL_CLIENT_RECONNECT" }, + { -(MBEDTLS_ERR_SSL_UNEXPECTED_RECORD), "SSL_UNEXPECTED_RECORD" }, + { -(MBEDTLS_ERR_SSL_NON_FATAL), "SSL_NON_FATAL" }, + { -(MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH), "SSL_INVALID_VERIFY_HASH" }, + { -(MBEDTLS_ERR_SSL_CONTINUE_PROCESSING), "SSL_CONTINUE_PROCESSING" }, + { -(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS), "SSL_ASYNC_IN_PROGRESS" }, + { -(MBEDTLS_ERR_SSL_EARLY_MESSAGE), "SSL_EARLY_MESSAGE" }, + { -(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS), "SSL_CRYPTO_IN_PROGRESS" }, +#endif /* MBEDTLS_SSL_TLS_C */ + +#if defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_X509_CREATE_C) + { -(MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE), "X509_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_OID), "X509_UNKNOWN_OID" }, + { -(MBEDTLS_ERR_X509_INVALID_FORMAT), "X509_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_X509_INVALID_VERSION), "X509_INVALID_VERSION" }, + { -(MBEDTLS_ERR_X509_INVALID_SERIAL), "X509_INVALID_SERIAL" }, + { -(MBEDTLS_ERR_X509_INVALID_ALG), "X509_INVALID_ALG" }, + { -(MBEDTLS_ERR_X509_INVALID_NAME), "X509_INVALID_NAME" }, + { -(MBEDTLS_ERR_X509_INVALID_DATE), "X509_INVALID_DATE" }, + { -(MBEDTLS_ERR_X509_INVALID_SIGNATURE), "X509_INVALID_SIGNATURE" }, + { -(MBEDTLS_ERR_X509_INVALID_EXTENSIONS), "X509_INVALID_EXTENSIONS" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_VERSION), "X509_UNKNOWN_VERSION" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG), "X509_UNKNOWN_SIG_ALG" }, + { -(MBEDTLS_ERR_X509_SIG_MISMATCH), "X509_SIG_MISMATCH" }, + { -(MBEDTLS_ERR_X509_CERT_VERIFY_FAILED), "X509_CERT_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT), "X509_CERT_UNKNOWN_FORMAT" }, + { -(MBEDTLS_ERR_X509_BAD_INPUT_DATA), "X509_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_X509_ALLOC_FAILED), "X509_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_X509_FILE_IO_ERROR), "X509_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_X509_BUFFER_TOO_SMALL), "X509_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_X509_FATAL_ERROR), "X509_FATAL_ERROR" }, +#endif /* MBEDTLS_X509_USE_C || MBEDTLS_X509_CREATE_C */ +// END generated code +}; + +static const struct ssl_errs mbedtls_low_level_error_tab[] = { +// Low level error codes +// +// BEGIN generated code +#if defined(MBEDTLS_AES_C) + { -(MBEDTLS_ERR_AES_INVALID_KEY_LENGTH), "AES_INVALID_KEY_LENGTH" }, + { -(MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH), "AES_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_AES_BAD_INPUT_DATA), "AES_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_AES_FEATURE_UNAVAILABLE), "AES_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_AES_HW_ACCEL_FAILED), "AES_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_AES_C */ + +#if defined(MBEDTLS_ARC4_C) + { -(MBEDTLS_ERR_ARC4_HW_ACCEL_FAILED), "ARC4_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_ARC4_C */ + +#if defined(MBEDTLS_ARIA_C) + { -(MBEDTLS_ERR_ARIA_BAD_INPUT_DATA), "ARIA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_ARIA_INVALID_INPUT_LENGTH), "ARIA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_ARIA_FEATURE_UNAVAILABLE), "ARIA_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_ARIA_HW_ACCEL_FAILED), "ARIA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_ARIA_C */ + +#if defined(MBEDTLS_ASN1_PARSE_C) + { -(MBEDTLS_ERR_ASN1_OUT_OF_DATA), "ASN1_OUT_OF_DATA" }, + { -(MBEDTLS_ERR_ASN1_UNEXPECTED_TAG), "ASN1_UNEXPECTED_TAG" }, + { -(MBEDTLS_ERR_ASN1_INVALID_LENGTH), "ASN1_INVALID_LENGTH" }, + { -(MBEDTLS_ERR_ASN1_LENGTH_MISMATCH), "ASN1_LENGTH_MISMATCH" }, + { -(MBEDTLS_ERR_ASN1_INVALID_DATA), "ASN1_INVALID_DATA" }, + { -(MBEDTLS_ERR_ASN1_ALLOC_FAILED), "ASN1_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_ASN1_BUF_TOO_SMALL), "ASN1_BUF_TOO_SMALL" }, +#endif /* MBEDTLS_ASN1_PARSE_C */ + +#if defined(MBEDTLS_BASE64_C) + { -(MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL), "BASE64_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_BASE64_INVALID_CHARACTER), "BASE64_INVALID_CHARACTER" }, +#endif /* MBEDTLS_BASE64_C */ + +#if defined(MBEDTLS_BIGNUM_C) + { -(MBEDTLS_ERR_MPI_FILE_IO_ERROR), "MPI_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_MPI_BAD_INPUT_DATA), "MPI_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_MPI_INVALID_CHARACTER), "MPI_INVALID_CHARACTER" }, + { -(MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL), "MPI_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_MPI_NEGATIVE_VALUE), "MPI_NEGATIVE_VALUE" }, + { -(MBEDTLS_ERR_MPI_DIVISION_BY_ZERO), "MPI_DIVISION_BY_ZERO" }, + { -(MBEDTLS_ERR_MPI_NOT_ACCEPTABLE), "MPI_NOT_ACCEPTABLE" }, + { -(MBEDTLS_ERR_MPI_ALLOC_FAILED), "MPI_ALLOC_FAILED" }, +#endif /* MBEDTLS_BIGNUM_C */ + +#if defined(MBEDTLS_BLOWFISH_C) + { -(MBEDTLS_ERR_BLOWFISH_BAD_INPUT_DATA), "BLOWFISH_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_BLOWFISH_INVALID_INPUT_LENGTH), "BLOWFISH_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_BLOWFISH_HW_ACCEL_FAILED), "BLOWFISH_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_BLOWFISH_C */ + +#if defined(MBEDTLS_CAMELLIA_C) + { -(MBEDTLS_ERR_CAMELLIA_BAD_INPUT_DATA), "CAMELLIA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CAMELLIA_INVALID_INPUT_LENGTH), "CAMELLIA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_CAMELLIA_HW_ACCEL_FAILED), "CAMELLIA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CAMELLIA_C */ + +#if defined(MBEDTLS_CCM_C) + { -(MBEDTLS_ERR_CCM_BAD_INPUT), "CCM_BAD_INPUT" }, + { -(MBEDTLS_ERR_CCM_AUTH_FAILED), "CCM_AUTH_FAILED" }, + { -(MBEDTLS_ERR_CCM_HW_ACCEL_FAILED), "CCM_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CCM_C */ + +#if defined(MBEDTLS_CHACHA20_C) + { -(MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA), "CHACHA20_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CHACHA20_FEATURE_UNAVAILABLE), "CHACHA20_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_CHACHA20_HW_ACCEL_FAILED), "CHACHA20_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CHACHA20_C */ + +#if defined(MBEDTLS_CHACHAPOLY_C) + { -(MBEDTLS_ERR_CHACHAPOLY_BAD_STATE), "CHACHAPOLY_BAD_STATE" }, + { -(MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED), "CHACHAPOLY_AUTH_FAILED" }, +#endif /* MBEDTLS_CHACHAPOLY_C */ + +#if defined(MBEDTLS_CMAC_C) + { -(MBEDTLS_ERR_CMAC_HW_ACCEL_FAILED), "CMAC_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CMAC_C */ + +#if defined(MBEDTLS_CTR_DRBG_C) + { -(MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED), "CTR_DRBG_ENTROPY_SOURCE_FAILED" }, + { -(MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG), "CTR_DRBG_REQUEST_TOO_BIG" }, + { -(MBEDTLS_ERR_CTR_DRBG_INPUT_TOO_BIG), "CTR_DRBG_INPUT_TOO_BIG" }, + { -(MBEDTLS_ERR_CTR_DRBG_FILE_IO_ERROR), "CTR_DRBG_FILE_IO_ERROR" }, +#endif /* MBEDTLS_CTR_DRBG_C */ + +#if defined(MBEDTLS_DES_C) + { -(MBEDTLS_ERR_DES_INVALID_INPUT_LENGTH), "DES_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_DES_HW_ACCEL_FAILED), "DES_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_DES_C */ + +#if defined(MBEDTLS_ENTROPY_C) + { -(MBEDTLS_ERR_ENTROPY_SOURCE_FAILED), "ENTROPY_SOURCE_FAILED" }, + { -(MBEDTLS_ERR_ENTROPY_MAX_SOURCES), "ENTROPY_MAX_SOURCES" }, + { -(MBEDTLS_ERR_ENTROPY_NO_SOURCES_DEFINED), "ENTROPY_NO_SOURCES_DEFINED" }, + { -(MBEDTLS_ERR_ENTROPY_NO_STRONG_SOURCE), "ENTROPY_NO_STRONG_SOURCE" }, + { -(MBEDTLS_ERR_ENTROPY_FILE_IO_ERROR), "ENTROPY_FILE_IO_ERROR" }, +#endif /* MBEDTLS_ENTROPY_C */ + +#if defined(MBEDTLS_GCM_C) + { -(MBEDTLS_ERR_GCM_AUTH_FAILED), "GCM_AUTH_FAILED" }, + { -(MBEDTLS_ERR_GCM_HW_ACCEL_FAILED), "GCM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_GCM_BAD_INPUT), "GCM_BAD_INPUT" }, +#endif /* MBEDTLS_GCM_C */ + +#if defined(MBEDTLS_HKDF_C) + { -(MBEDTLS_ERR_HKDF_BAD_INPUT_DATA), "HKDF_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_HKDF_C */ + +#if defined(MBEDTLS_HMAC_DRBG_C) + { -(MBEDTLS_ERR_HMAC_DRBG_REQUEST_TOO_BIG), "HMAC_DRBG_REQUEST_TOO_BIG" }, + { -(MBEDTLS_ERR_HMAC_DRBG_INPUT_TOO_BIG), "HMAC_DRBG_INPUT_TOO_BIG" }, + { -(MBEDTLS_ERR_HMAC_DRBG_FILE_IO_ERROR), "HMAC_DRBG_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_HMAC_DRBG_ENTROPY_SOURCE_FAILED), "HMAC_DRBG_ENTROPY_SOURCE_FAILED" }, +#endif /* MBEDTLS_HMAC_DRBG_C */ + +#if defined(MBEDTLS_MD2_C) + { -(MBEDTLS_ERR_MD2_HW_ACCEL_FAILED), "MD2_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD2_C */ + +#if defined(MBEDTLS_MD4_C) + { -(MBEDTLS_ERR_MD4_HW_ACCEL_FAILED), "MD4_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD4_C */ + +#if defined(MBEDTLS_MD5_C) + { -(MBEDTLS_ERR_MD5_HW_ACCEL_FAILED), "MD5_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD5_C */ + +#if defined(MBEDTLS_NET_C) + { -(MBEDTLS_ERR_NET_SOCKET_FAILED), "NET_SOCKET_FAILED" }, + { -(MBEDTLS_ERR_NET_CONNECT_FAILED), "NET_CONNECT_FAILED" }, + { -(MBEDTLS_ERR_NET_BIND_FAILED), "NET_BIND_FAILED" }, + { -(MBEDTLS_ERR_NET_LISTEN_FAILED), "NET_LISTEN_FAILED" }, + { -(MBEDTLS_ERR_NET_ACCEPT_FAILED), "NET_ACCEPT_FAILED" }, + { -(MBEDTLS_ERR_NET_RECV_FAILED), "NET_RECV_FAILED" }, + { -(MBEDTLS_ERR_NET_SEND_FAILED), "NET_SEND_FAILED" }, + { -(MBEDTLS_ERR_NET_CONN_RESET), "NET_CONN_RESET" }, + { -(MBEDTLS_ERR_NET_UNKNOWN_HOST), "NET_UNKNOWN_HOST" }, + { -(MBEDTLS_ERR_NET_BUFFER_TOO_SMALL), "NET_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_NET_INVALID_CONTEXT), "NET_INVALID_CONTEXT" }, + { -(MBEDTLS_ERR_NET_POLL_FAILED), "NET_POLL_FAILED" }, + { -(MBEDTLS_ERR_NET_BAD_INPUT_DATA), "NET_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_NET_C */ + +#if defined(MBEDTLS_OID_C) + { -(MBEDTLS_ERR_OID_NOT_FOUND), "OID_NOT_FOUND" }, + { -(MBEDTLS_ERR_OID_BUF_TOO_SMALL), "OID_BUF_TOO_SMALL" }, +#endif /* MBEDTLS_OID_C */ + +#if defined(MBEDTLS_PADLOCK_C) + { -(MBEDTLS_ERR_PADLOCK_DATA_MISALIGNED), "PADLOCK_DATA_MISALIGNED" }, +#endif /* MBEDTLS_PADLOCK_C */ + +#if defined(MBEDTLS_PLATFORM_C) + { -(MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED), "PLATFORM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED), "PLATFORM_FEATURE_UNSUPPORTED" }, +#endif /* MBEDTLS_PLATFORM_C */ + +#if defined(MBEDTLS_POLY1305_C) + { -(MBEDTLS_ERR_POLY1305_BAD_INPUT_DATA), "POLY1305_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_POLY1305_FEATURE_UNAVAILABLE), "POLY1305_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_POLY1305_HW_ACCEL_FAILED), "POLY1305_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_POLY1305_C */ + +#if defined(MBEDTLS_RIPEMD160_C) + { -(MBEDTLS_ERR_RIPEMD160_HW_ACCEL_FAILED), "RIPEMD160_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_RIPEMD160_C */ + +#if defined(MBEDTLS_SHA1_C) + { -(MBEDTLS_ERR_SHA1_HW_ACCEL_FAILED), "SHA1_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA1_BAD_INPUT_DATA), "SHA1_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA1_C */ + +#if defined(MBEDTLS_SHA256_C) + { -(MBEDTLS_ERR_SHA256_HW_ACCEL_FAILED), "SHA256_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA256_BAD_INPUT_DATA), "SHA256_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA256_C */ + +#if defined(MBEDTLS_SHA512_C) + { -(MBEDTLS_ERR_SHA512_HW_ACCEL_FAILED), "SHA512_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA512_BAD_INPUT_DATA), "SHA512_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA512_C */ + +#if defined(MBEDTLS_THREADING_C) + { -(MBEDTLS_ERR_THREADING_FEATURE_UNAVAILABLE), "THREADING_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_THREADING_BAD_INPUT_DATA), "THREADING_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_THREADING_MUTEX_ERROR), "THREADING_MUTEX_ERROR" }, +#endif /* MBEDTLS_THREADING_C */ + +#if defined(MBEDTLS_XTEA_C) + { -(MBEDTLS_ERR_XTEA_INVALID_INPUT_LENGTH), "XTEA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_XTEA_HW_ACCEL_FAILED), "XTEA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_XTEA_C */ +// END generated code +}; + +static const char *mbedtls_err_prefix = "MBEDTLS_ERR_"; +#define MBEDTLS_ERR_PREFIX_LEN ( sizeof("MBEDTLS_ERR_")-1 ) + +// copy error text into buffer, ensure null termination, return strlen of result +static size_t mbedtls_err_to_str(int err, const struct ssl_errs tab[], int tab_len, char *buf, size_t buflen) { + if (buflen == 0) return 0; + + // prefix for all error names + strncpy(buf, mbedtls_err_prefix, buflen); + if (buflen <= MBEDTLS_ERR_PREFIX_LEN+1) { + buf[buflen-1] = 0; + return buflen-1; + } + + // append error name from table + for (int i = 0; i < tab_len; i++) { + if (tab[i].errnum == err) { + strncpy(buf+MBEDTLS_ERR_PREFIX_LEN, tab[i].errstr, buflen-MBEDTLS_ERR_PREFIX_LEN); + buf[buflen-1] = 0; + return strlen(buf); + } + } + + mbedtls_snprintf(buf+MBEDTLS_ERR_PREFIX_LEN, buflen-MBEDTLS_ERR_PREFIX_LEN, "UNKNOWN (0x%04X)", + err); + return strlen(buf); +} + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +void mbedtls_strerror(int ret, char *buf, size_t buflen) { + int use_ret; + + if (buflen == 0) return; + + buf[buflen-1] = 0; + + if (ret < 0) ret = -ret; + + // + // High-level error codes + // + uint8_t got_hl = (ret & 0xFF80) != 0; + if (got_hl) { + use_ret = ret & 0xFF80; + + // special case +#if defined(MBEDTLS_SSL_TLS_C) + if (use_ret == -(MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE)) { + strncpy(buf, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE", buflen); + buf[buflen-1] = 0; + return; + } +#endif + + size_t len = mbedtls_err_to_str(use_ret, mbedtls_high_level_error_tab, + ARRAY_SIZE(mbedtls_high_level_error_tab), buf, buflen); + + buf += len; + buflen -= len; + if (buflen == 0) return; + } + + // + // Low-level error codes + // + use_ret = ret & ~0xFF80; + + if (use_ret == 0) return; + + // If high level code is present, make a concatenation between both error strings. + if (got_hl) { + if (buflen < 2) return; + *buf++ = '+'; + buflen--; + } + + mbedtls_err_to_str(use_ret, mbedtls_low_level_error_tab, + ARRAY_SIZE(mbedtls_low_level_error_tab), buf, buflen); +} + +#else /* MBEDTLS_ERROR_C */ + +#if defined(MBEDTLS_ERROR_STRERROR_DUMMY) + +/* + * Provide an non-function in case MBEDTLS_ERROR_C is not defined + */ +void mbedtls_strerror( int ret, char *buf, size_t buflen ) +{ + ((void) ret); + + if( buflen > 0 ) + buf[0] = '\0'; +} + +#endif /* MBEDTLS_ERROR_STRERROR_DUMMY */ + +#endif /* MBEDTLS_ERROR_C */ diff --git a/lib/mbedtls_errors/tester.c b/lib/mbedtls_errors/tester.c new file mode 100644 index 000000000..6f1c788f5 --- /dev/null +++ b/lib/mbedtls_errors/tester.c @@ -0,0 +1,58 @@ +#include "mbedtls/error.h" +#include +#include + +// test_code checks that the provided code results in the provided error string for any size +// buffer. It calls mbedtls_strerror() to fill a buffer that is from 1 to 100 bytes in length +// and then checks that the buffer contents is OK and that a few guard bytes before and after +// the buffer were not overwritten. +int test_code(int code, char *str) { + char buf[100]; + int ok = 1; + int res; + + // test zero-length buffer + memset(buf, -3, 100); + mbedtls_strerror(code, buf + 4, 0); + for (int i = 0; i < 10; i++) { + if (buf[i] != -3) { + printf("Error: guard overwritten buflen=0 i=%d buf[i]=%d\n", i, buf[i]); + ok = 0; + } + } + + // test + for (size_t buflen = 1; buflen < 90; buflen++) { + memset(buf, -3, 100); + mbedtls_strerror(code, buf + 4, buflen); + for (int i = 0; i < 4; i++) { + if (buf[i] != -3) { + printf("Error: pre-guard overwritten buflen=%d i=%d buf[i]=%d\n", buflen, i, buf[i]); + ok = 0; + } + } + for (int i = 4 + buflen; i < 100; i++) { + if (buf[i] != -3) { + printf("Error: post-guard overwritten buflen=%d i=%d buf[i]=%d\n", buflen, i, buf[i]); + ok = 0; + } + } + char exp[100]; + strncpy(exp, str, buflen); + exp[buflen - 1] = 0; + if (strcmp(buf + 4, exp) != 0) { + printf("Error: expected %s, got %s\n", exp, buf); + ok = 0; + } + } + + printf("Test %x -> %s is %s\n", code, str, ok?"OK":"*** BAD ***"); +} + +int main() { + test_code(0x7200, "MBEDTLS_ERR_SSL_INVALID_RECORD"); + test_code(0x7780, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE"); + test_code(0x0074, "MBEDTLS_ERR_SHA256_BAD_INPUT_DATA"); + test_code(0x6600 | 0x0074, "MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH+MBEDTLS_ERR_SHA256_BAD_INPUT_DATA"); + test_code(103, "MBEDTLS_ERR_UNKNOWN (0x0067)"); +} diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index 296c8aa4a..adbf82aa8 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -517,6 +517,35 @@ int readline(vstr_t *line, const char *prompt) { } } +#if MICROPY_UNIXSCHEDULE +#include +fd_set rfds; +struct timeval tv; + +int readline_select(vstr_t *line, const char *prompt, int resuming) { + if (resuming== -3) + readline_init(line, prompt); + + for (;;) { + // On Linux, select() modifies timeout to reflect the amount of time not slept + tv.tv_sec = 0; + tv.tv_usec = 16; + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + int rv=select(STDIN_FILENO+1, &rfds, NULL, NULL, &tv); + // 0-1 == -1 =>tmout -1-1 == -2=>failure + if (rv <=0) + return rv-1; + + int c = mp_hal_stdin_rx_chr(); + int r = readline_process_char(c); + if (r >= 0) { + return r; + } + } +} +#endif + void readline_push_history(const char *line) { if (line[0] != '\0' && (MP_STATE_PORT(readline_hist)[0] == NULL diff --git a/lib/mynewt-nimble b/lib/mynewt-nimble deleted file mode 160000 index 97ce3eaca..000000000 --- a/lib/mynewt-nimble +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 97ce3eacaaa79e8ed6cf71717149ced4f5328ee7 diff --git a/lib/nrfx b/lib/nrfx deleted file mode 160000 index 7a4c9d946..000000000 --- a/lib/nrfx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a4c9d946cf1801771fc180acdbf7b878f270093 diff --git a/lib/nxp_driver b/lib/nxp_driver deleted file mode 160000 index b618cb1d5..000000000 --- a/lib/nxp_driver +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b618cb1d521cc9e133bdcd0fca154dee2d925dfe diff --git a/lib/stm32lib b/lib/stm32lib deleted file mode 160000 index 668d7a9e5..000000000 --- a/lib/stm32lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 668d7a9e54aea98f8fe8a858eac1d3daa80fa824 diff --git a/lib/tinyusb b/lib/tinyusb deleted file mode 160000 index a6b916ba8..000000000 --- a/lib/tinyusb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a6b916ba85bef6aad50f1652532b02984dfe2484 diff --git a/lib/upytesthelper/upytesthelper.c b/lib/upytesthelper/upytesthelper.c index 326172be6..a9a4efa64 100644 --- a/lib/upytesthelper/upytesthelper.c +++ b/lib/upytesthelper/upytesthelper.c @@ -87,7 +87,42 @@ void upytest_output(const char *str, mp_uint_t len) { } mp_hal_stdout_tx_strn_cooked(str, len); } +#if NO_NLR +void upytest_execute_test(const char *src) { + // To provide clean room for each test, interpreter and heap are + // reinitialized before running each. + gc_init(heap_start, heap_end); + mp_init(); + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_init(mp_sys_argv, 0); + + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_call_function_0(module_fun); + } + if (MP_STATE_THREAD(active_exception) != NULL) { + mp_obj_t exc = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + if (mp_obj_is_subclass_fast(mp_obj_get_type(exc), &mp_type_SystemExit)) { + // Assume that sys.exit() is called to skip the test. That can be always true, we should +#pragma message "TODO: set up convention to use specific exit code as skip indicator." + tinytest_set_test_skipped_(); + goto end; + } + mp_obj_print_exception(&mp_plat_print, exc); + tt_abort_msg("Uncaught exception\n"); + } + + if (upytest_is_failed()) { + tinytest_set_test_failed_(); + } +end: + mp_deinit(); +} +#else void upytest_execute_test(const char *src) { // To provide clean room for each test, interpreter and heap are // reinitialized before running each. @@ -124,3 +159,4 @@ void upytest_execute_test(const char *src) { end: mp_deinit(); } +#endif diff --git a/lib/utils/mpirq.c b/lib/utils/mpirq.c index 8de13b0b6..663be1822 100644 --- a/lib/utils/mpirq.c +++ b/lib/utils/mpirq.c @@ -31,6 +31,8 @@ #include "py/gc.h" #include "lib/utils/mpirq.h" +#if MICROPY_ENABLE_SCHEDULER + /****************************************************************************** DECLARE PUBLIC DATA ******************************************************************************/ @@ -125,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/printf.c b/lib/utils/printf.c index 6266ab66d..e8db2b999 100644 --- a/lib/utils/printf.c +++ b/lib/utils/printf.c @@ -104,7 +104,7 @@ STATIC void strn_print_strn(void *data, const char *str, size_t len) { // when linkings against it statically. // GCC 9 gives a warning about missing attributes so it's excluded until // uClibc+GCC9 support is needed. -int __GI_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) __attribute__((weak, alias ("vsnprintf"))); +int __GI_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) __attribute__((weak, alias("vsnprintf"))); #endif int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 2c8ca2de0..a5f16f382 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -45,10 +45,7 @@ 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,11 +59,124 @@ STATIC bool repl_display_debugging_info = 0; // EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output // EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code // EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile) + + + +#if NO_NLR +#include "../wapy/upython.h" + +#if __EMSCRIPTEN__ +#pragma message "TODO: pyexec->no_nlr is crude" +#include "emscripten.h" +#endif + + +#define FORCED_EXIT (0x100) +// If exc is SystemExit, return value where FORCED_EXIT bit set, +// and lower 8 bits are SystemExit value. For all other exceptions, +// return 1. + +STATIC void stderr_print_strn(void *env, const char *str, size_t len) { + (void)env; + mp_hal_stdout_tx_strn(str,len); +} + +const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; + + +STATIC int handle_uncaught_exception(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + //clog("89:SystemExit"); + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + #if __EMSCRIPTEN__ + EM_ASM({console.log("91:SystemExit");}); + #endif + + + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(exc)); + return 0; +} + STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { - int ret = 0; - #if MICROPY_REPL_INFO + int retval = 0; uint32_t start = 0; + + // by default a SystemExit exception returns 0 + pyexec_system_exit = 0; + + + mp_obj_t module_fun; + #if MICROPY_MODULE_FROZEN_MPY + if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { + // source is a raw_code object, create the function + module_fun = mp_make_function_from_raw_code(source, MP_OBJ_NULL, MP_OBJ_NULL); + } else #endif + { + #if MICROPY_ENABLE_COMPILER + mp_lexer_t *lex; + if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) { + const vstr_t *vstr = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0); + } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { + lex = mp_lexer_new_from_file(source); + } else { + lex = (mp_lexer_t*)source; + } + // source is a lexer, parse and compile the script + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); + #else + mp_raise_msg(&mp_type_RuntimeError, "script compilation not supported"); + #endif + } + + + if ( module_fun != MP_OBJ_NULL) { + // execute code + mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us + start = mp_hal_ticks_ms(); + mp_obj_t ret = mp_call_function_0(module_fun); + mp_hal_set_interrupt_char(-1); // disable interrupt + + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); + } + + if (ret != MP_OBJ_NULL && 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; + mp_raise_o(obj); + } + + if (MP_STATE_THREAD(active_exception) != NULL) { + // uncaught exception + return handle_uncaught_exception(); + } + } + + return retval; +} + +#else +STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { + int ret = 0; + uint32_t start = 0; // by default a SystemExit exception returns 0 pyexec_system_exit = 0; @@ -89,25 +199,22 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { lex = mp_lexer_new_from_file(source); } else { - lex = (mp_lexer_t *)source; + lex = (mp_lexer_t*)source; } // source is a lexer, parse and compile the script qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); #else - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); + mp_raise_msg(&mp_type_RuntimeError, "script compilation not supported"); #endif } // execute code mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us - #if MICROPY_REPL_INFO start = mp_hal_ticks_ms(); - #endif mp_call_function_0(module_fun); mp_hal_set_interrupt_char(-1); // disable interrupt - mp_handle_pending(true); // handle any pending exceptions (and any callbacks) nlr_pop(); ret = 1; if (exec_flags & EXEC_FLAG_PRINT_EOF) { @@ -115,14 +222,14 @@ 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); } // check for SystemExit - if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // at the moment, the value of SystemExit is unused ret = pyexec_system_exit; } else { @@ -131,7 +238,6 @@ 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 @@ -141,8 +247,8 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); printf("qstr:\n n_pool=%u\n n_qstr=%u\n " - "n_str_data_bytes=%u\n n_total_bytes=%u\n", - (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); + "n_str_data_bytes=%u\n n_total_bytes=%u\n", + (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); } #if MICROPY_ENABLE_GC @@ -151,7 +257,6 @@ 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); @@ -159,6 +264,8 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input return ret; } +#endif + #if MICROPY_ENABLE_COMPILER #if MICROPY_REPL_EVENT_DRIVEN @@ -168,7 +275,7 @@ typedef struct _repl_t { // but it was moved to MP_STATE_VM(repl_line) as containing // root pointer. Still keep structure in case more state // will be added later. - // vstr_t line; + //vstr_t line; bool cont_line; bool paste_mode; } repl_t; @@ -238,7 +345,21 @@ STATIC int pyexec_raw_repl_process_char(int c) { return 0; } +STATIC int pyexec_repl_repl_restart(int ret) { + if ((ret)>=0) { + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + readline_init(MP_STATE_VM(repl_line), ">>> "); + } + return ret; +} + + + STATIC int pyexec_friendly_repl_process_char(int c) { + int ret = 0; + if (repl.paste_mode) { if (c == CHAR_CTRL_C) { // cancel everything @@ -247,7 +368,8 @@ STATIC int pyexec_friendly_repl_process_char(int c) { } else if (c == CHAR_CTRL_D) { // end of input mp_hal_stdout_tx_str("\r\n"); - int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + + ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } @@ -265,7 +387,8 @@ STATIC int pyexec_friendly_repl_process_char(int c) { } } - int ret = readline_process_char(c); +// int + ret = readline_process_char(c); if (!repl.cont_line) { @@ -316,10 +439,10 @@ STATIC int pyexec_friendly_repl_process_char(int c) { } else { if (ret == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - repl.cont_line = false; - goto input_restart; + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + repl.cont_line = false; + goto input_restart; } else if (ret == CHAR_CTRL_D) { // stop entering compound statement goto exec; @@ -335,18 +458,26 @@ STATIC int pyexec_friendly_repl_process_char(int c) { return 0; } - exec:; +exec: ; +#if 1 + char *data = MP_STATE_VM(repl_line)->buf; +// int dlen = MP_STATE_VM(repl_line)->len; + + if (i_main.shm_stdio) { + strcpy( i_main.shm_stdio, data ); + return -1; + } else + cdbg("463: REPL space not allocated!"); + +#else 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; } +#endif - input_restart: - vstr_reset(MP_STATE_VM(repl_line)); - repl.cont_line = false; - repl.paste_mode = false; - readline_init(MP_STATE_VM(repl_line), ">>> "); - return 0; +input_restart: + return pyexec_repl_repl_restart(ret); } } @@ -364,187 +495,9 @@ int pyexec_event_repl_process_char(int c) { } #else // MICROPY_REPL_EVENT_DRIVEN - -int pyexec_raw_repl(void) { - vstr_t line; - vstr_init(&line, 32); - -raw_repl_reset: - mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); - - for (;;) { - vstr_reset(&line); - mp_hal_stdout_tx_str(">"); - for (;;) { - int c = mp_hal_stdin_rx_chr(); - if (c == CHAR_CTRL_A) { - // reset raw REPL - goto raw_repl_reset; - } else if (c == CHAR_CTRL_B) { - // change to friendly REPL - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; - return 0; - } else if (c == CHAR_CTRL_C) { - // clear line - vstr_reset(&line); - } else if (c == CHAR_CTRL_D) { - // input finished - break; - } else { - // let through any other raw 8-bit value - vstr_add_byte(&line, c); - } - } - - // indicate reception of command - mp_hal_stdout_tx_str("OK"); - - if (line.len == 0) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - return PYEXEC_FORCED_EXIT; - } - - int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; - } - } -} - -int pyexec_friendly_repl(void) { - vstr_t line; - vstr_init(&line, 32); - - #if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD - // in host mode, we enable the LCD for the repl - mp_obj_t lcd_o = mp_call_function_0(mp_load_name(qstr_from_str("LCD"))); - mp_call_function_1(mp_load_attr(lcd_o, qstr_from_str("light")), mp_const_true); - #endif - -friendly_repl_reset: - mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); - #if MICROPY_PY_BUILTINS_HELP - mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); - #endif - - // to test ctrl-C - /* - { - uint32_t x[4] = {0x424242, 0xdeaddead, 0x242424, 0xdeadbeef}; - for (;;) { - nlr_buf_t nlr; - printf("pyexec_repl: %p\n", x); - mp_hal_set_interrupt_char(CHAR_CTRL_C); - if (nlr_push(&nlr) == 0) { - for (;;) { - } - } else { - printf("break\n"); - } - } - } - */ - - for (;;) { - input_restart: - - #if MICROPY_HW_ENABLE_USB - if (usb_vcp_is_enabled()) { - // If the user gets to here and interrupts are disabled then - // they'll never see the prompt, traceback etc. The USB REPL needs - // interrupts to be enabled or no transfers occur. So we try to - // do the user a favor and reenable interrupts. - if (query_irq() == IRQ_STATE_DISABLED) { - enable_irq(IRQ_STATE_ENABLED); - mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n"); - } - } - #endif - - // If the GC is locked at this point there is no way out except a reset, - // so force the GC to be unlocked to help the user debug what went wrong. - if (MP_STATE_MEM(gc_lock_depth) != 0) { - MP_STATE_MEM(gc_lock_depth) = 0; - } - - vstr_reset(&line); - int ret = readline(&line, ">>> "); - mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; - - if (ret == CHAR_CTRL_A) { - // change to raw REPL - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; - return 0; - } else if (ret == CHAR_CTRL_B) { - // reset friendly REPL - mp_hal_stdout_tx_str("\r\n"); - goto friendly_repl_reset; - } else if (ret == CHAR_CTRL_C) { - // break - mp_hal_stdout_tx_str("\r\n"); - continue; - } else if (ret == CHAR_CTRL_D) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - return PYEXEC_FORCED_EXIT; - } else if (ret == CHAR_CTRL_E) { - // paste mode - mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); - vstr_reset(&line); - for (;;) { - char c = mp_hal_stdin_rx_chr(); - if (c == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (c == CHAR_CTRL_D) { - // end of input - mp_hal_stdout_tx_str("\r\n"); - break; - } else { - // add char to buffer and echo - vstr_add_byte(&line, c); - if (c == '\r') { - mp_hal_stdout_tx_str("\r\n=== "); - } else { - mp_hal_stdout_tx_strn(&c, 1); - } - } - } - parse_input_kind = MP_PARSE_FILE_INPUT; - } else if (vstr_len(&line) == 0) { - continue; - } else { - // got a line with non-zero length, see if it needs continuing - while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { - vstr_add_byte(&line, '\n'); - ret = readline(&line, "... "); - if (ret == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // stop entering compound statement - break; - } - } - } - - ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; - } - } -} - + #error "[redacted]" #endif // MICROPY_REPL_EVENT_DRIVEN + #endif // MICROPY_ENABLE_COMPILER int pyexec_file(const char *filename) { @@ -586,10 +539,9 @@ int pyexec_frozen_module(const char *name) { } #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/mpy-cross/Makefile b/mpy-cross/Makefile index f80ee761b..8fd2afa4d 100644 --- a/mpy-cross/Makefile +++ b/mpy-cross/Makefile @@ -44,6 +44,8 @@ LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections endif LDFLAGS = $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) +include ../ports/wapy/wapy.mk + # source files SRC_C = \ main.c \ diff --git a/mpy-cross/main.c b/mpy-cross/main.c index a403c0504..bdf8f58ef 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -47,14 +47,92 @@ 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, size_t len) { +#if __EMSCRIPTEN__ +#include "emscripten.h" + +STATIC void stderr_print_strn(void *env, const char *str, mp_uint_t len) { +EM_ASM{ + console.error("ERROR"); +} +} +#else +STATIC void stderr_print_strn(void *env, const char *str, mp_uint_t len) { (void)env; ssize_t dummy = write(STDERR_FILENO, str, len); (void)dummy; } +#endif STATIC const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; +#if NO_NLR + + +STATIC void stderr_print_strn2(void *env, const char *str, size_t len) { + (void)env; + fprintf(stderr, "%s", str); +} + +const mp_print_t mp_stderr_print2 = {NULL, stderr_print_strn2}; + +int uncaught_exception_handler(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + #if __EMSCRIPTEN__ + EM_ASM({console.log("91:SystemExit");}); + #endif + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print2, MP_OBJ_FROM_PTR(exc)); + return 0; +} +STATIC int compile_and_save(const char *file, const char *output_file, const char *source_file) { + mp_lexer_t *lex = mp_lexer_new_from_file(file); + if (lex == NULL) { + uncaught_exception_handler(); + return 1; + } else { + qstr source_name; + if (source_file == NULL) { + source_name = lex->source_name; + } else { + source_name = qstr_from_str(source_file); + } + + #if MICROPY_PY___FILE__ + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, source_name, false); + + if ( rc != MP_OBJ_NULL) { + vstr_t vstr; + vstr_init(&vstr, 16); + if (output_file == NULL) { + vstr_add_str(&vstr, file); + vstr_cut_tail_bytes(&vstr, 2); + vstr_add_str(&vstr, "mpy"); + } else { + vstr_add_str(&vstr, output_file); + } + mp_raw_code_save_file(rc, vstr_null_terminated_str(&vstr)); + vstr_clear(&vstr); +#pragma message "//TODO: MP_STATE_VM(mp_pending_exception)" + return 0; + } + + } + if (MP_STATE_THREAD(active_exception) != NULL) { + uncaught_exception_handler(); + } + return 1; +} + +#else STATIC int compile_and_save(const char *file, const char *output_file, const char *source_file) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -94,6 +172,7 @@ STATIC int compile_and_save(const char *file, const char *output_file, const cha return 1; } } +#endif STATIC int usage(char **argv) { printf( @@ -202,7 +281,7 @@ MP_NOINLINE int main_(int argc, char **argv) { #else (void)emit_opt; #endif - +#if MICROPY_DYNAMIC_COMPILER // set default compiler configuration mp_dynamic_compiler.small_int_bits = 31; mp_dynamic_compiler.opt_cache_map_lookup_in_bytecode = 0; @@ -220,7 +299,7 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; mp_dynamic_compiler.nlr_buf_num_regs = 0; #endif - +#endif const char *input_file = NULL; const char *output_file = NULL; const char *source_file = NULL; @@ -256,6 +335,7 @@ MP_NOINLINE int main_(int argc, char **argv) { } a += 1; source_file = argv[a]; +#if MICROPY_DYNAMIC_COMPILER } else if (strncmp(argv[a], "-msmall-int-bits=", sizeof("-msmall-int-bits=") - 1) == 0) { char *end; mp_dynamic_compiler.small_int_bits = @@ -304,6 +384,12 @@ MP_NOINLINE int main_(int argc, char **argv) { } else { return usage(argv); } +#else + } else if (strcmp(argv[a], "-mno-cache-lookup-bc") == 0) { + + } else if (strcmp(argv[a], "-mcache-lookup-bc") == 0) { + +#endif } else { return usage(argv); } @@ -321,7 +407,14 @@ MP_NOINLINE int main_(int argc, char **argv) { exit(1); } - int ret = compile_and_save(input_file, output_file, source_file); + char pytmpfile[1024]; + + sprintf(pytmpfile, "future-fstrings-show %s > /tmp/mpy-cross.py", input_file); + system(pytmpfile); + sprintf(pytmpfile, "/tmp/mpy-cross.py"); + + //int ret = compile_and_save(input_file, output_file, source_file); + int ret = compile_and_save(pytmpfile, output_file, source_file); #if MICROPY_PY_MICROPYTHON_MEM_INFO if (mp_verbose_flag) { @@ -345,6 +438,6 @@ uint mp_import_stat(const char *path) { } void nlr_jump_fail(void *val) { - fprintf(stderr, "FATAL: uncaught NLR %p\n", val); + printf("FATAL: uncaught NLR %p\n", val); exit(1); } diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 21d3e12ed..9a1c2c5f6 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -24,24 +24,35 @@ * THE SOFTWARE. */ +// options to control how WAPY is built + +#define NO_NLR (1) +#if NO_NLR + #define MICROPY_ROM_TEXT_COMPRESSION (0) + #define MICROPY_PY_FUNCTION_ATTRS (1) + #define MICROPY_EMIT_WASM (1) + #define MICROPY_PY_BUILTINS_NEXT2 (1) + #define MICROPY_PY_FSTRING (1) +#endif + // options to control how MicroPython is built #define MICROPY_ALLOC_PATH_MAX (PATH_MAX) #define MICROPY_PERSISTENT_CODE_LOAD (0) #define MICROPY_PERSISTENT_CODE_SAVE (1) -#define MICROPY_EMIT_X64 (1) -#define MICROPY_EMIT_X86 (1) -#define MICROPY_EMIT_THUMB (1) -#define MICROPY_EMIT_INLINE_THUMB (1) -#define MICROPY_EMIT_INLINE_THUMB_ARMV7M (1) -#define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) -#define MICROPY_EMIT_ARM (1) -#define MICROPY_EMIT_XTENSA (1) -#define MICROPY_EMIT_INLINE_XTENSA (1) -#define MICROPY_EMIT_XTENSAWIN (1) - -#define MICROPY_DYNAMIC_COMPILER (1) +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_X86 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB_ARMV7M (0) +#define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#define MICROPY_EMIT_ARM (0) +#define MICROPY_EMIT_XTENSA (0) +#define MICROPY_EMIT_INLINE_XTENSA (0) +#define MICROPY_EMIT_XTENSAWIN (0) + +#define MICROPY_DYNAMIC_COMPILER (0) #define MICROPY_COMP_CONST_FOLDING (1) #define MICROPY_COMP_MODULE_CONST (1) #define MICROPY_COMP_CONST (1) @@ -73,7 +84,7 @@ #define MICROPY_GCREGS_SETJMP (1) #endif -#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY___FILE__ (1) #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ATTRTUPLE (0) #define MICROPY_PY_COLLECTIONS (0) diff --git a/ports/bare-arm/mpconfigport.h b/ports/bare-arm/mpconfigport.h index 456767658..7fd236bfb 100644 --- a/ports/bare-arm/mpconfigport.h +++ b/ports/bare-arm/mpconfigport.h @@ -21,6 +21,7 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) #define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_PY_ASSIGN_EXPR (0) #define MICROPY_PY_BUILTINS_BYTEARRAY (0) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0) #define MICROPY_PY_BUILTINS_MEMORYVIEW (0) diff --git a/ports/cc3200/main.c b/ports/cc3200/main.c index b9f67194b..bdd73e7bf 100644 --- a/ports/cc3200/main.c +++ b/ports/cc3200/main.c @@ -51,8 +51,8 @@ ******************************************************************************/ // This is the static memory (TCB and stack) for the idle task -static StaticTask_t xIdleTaskTCB __attribute__ ((section (".rtos_heap"))); -static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +static StaticTask_t xIdleTaskTCB __attribute__ ((section(".rtos_heap"))); +static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); /****************************************************************************** DECLARE PUBLIC DATA @@ -62,18 +62,18 @@ OsiTaskHandle mpTaskHandle; #endif // This is the FreeRTOS heap, defined here so we can put it in a special segment -uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); // This is the static memory (TCB and stack) for the main MicroPython task -StaticTask_t mpTaskTCB __attribute__ ((section (".rtos_heap"))); -StackType_t mpTaskStack[MICROPY_TASK_STACK_LEN] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +StaticTask_t mpTaskTCB __attribute__ ((section(".rtos_heap"))); +StackType_t mpTaskStack[MICROPY_TASK_STACK_LEN] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); /****************************************************************************** DEFINE PUBLIC FUNCTIONS ******************************************************************************/ -__attribute__ ((section (".boot"))) -int main (void) { +__attribute__ ((section(".boot"))) +int main(void) { // Initialize the clocks and the interrupt system HAL_SystemInit(); diff --git a/ports/cc3200/misc/mpexception.c b/ports/cc3200/misc/mpexception.c new file mode 100644 index 000000000..72d4a155f --- /dev/null +++ b/ports/cc3200/misc/mpexception.c @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Daniel Campora + * + * 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 "mpexception.h" + + +/****************************************************************************** +DECLARE EXPORTED DATA + ******************************************************************************/ +const char mpexception_value_invalid_arguments[] = "invalid argument(s) value"; +const char mpexception_num_type_invalid_arguments[] = "invalid argument(s) num/type"; +const char mpexception_uncaught[] = "uncaught exception"; diff --git a/ports/cc3200/misc/mpexception.h b/ports/cc3200/misc/mpexception.h new file mode 100644 index 000000000..e84a1edb2 --- /dev/null +++ b/ports/cc3200/misc/mpexception.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Daniel Campora + * + * 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_CC3200_MISC_MPEXCEPTION_H +#define MICROPY_INCLUDED_CC3200_MISC_MPEXCEPTION_H + +extern const char mpexception_value_invalid_arguments[]; +extern const char mpexception_num_type_invalid_arguments[]; +extern const char mpexception_uncaught[]; + +#endif // MICROPY_INCLUDED_CC3200_MISC_MPEXCEPTION_H diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index f70b399c0..ddecd47f2 100644 --- a/ports/cc3200/mods/modmachine.c +++ b/ports/cc3200/mods/modmachine.c @@ -29,7 +29,6 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "irq.h" #include "inc/hw_types.h" #include "inc/hw_gpio.h" #include "inc/hw_ints.h" @@ -69,6 +68,9 @@ extern OsiTaskHandle xSimpleLinkSpawnTaskHndl; /// \module machine - functions related to the SoC /// +MP_DECLARE_CONST_FUN_OBJ_0(machine_disable_irq_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj); + /******************************************************************************/ // MicroPython bindings; @@ -176,8 +178,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, diff --git a/ports/cc3200/mods/modubinascii.c b/ports/cc3200/mods/modubinascii.c new file mode 100644 index 000000000..6b020ab39 --- /dev/null +++ b/ports/cc3200/mods/modubinascii.c @@ -0,0 +1,60 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Paul Sokolovsky + * Copyright (c) 2015 Daniel Campora + * + * 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/binary.h" +#include "extmod/modubinascii.h" +#include "modubinascii.h" +#include "inc/hw_types.h" +#include "inc/hw_ints.h" +#include "inc/hw_nvic.h" +#include "inc/hw_dthe.h" +#include "hw_memmap.h" +#include "rom_map.h" +#include "prcm.h" +#include "crc.h" +#include "cryptohash.h" +#include "mpexception.h" + + +/******************************************************************************/ +// MicroPython bindings + +STATIC const mp_rom_map_elem_t mp_module_binascii_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubinascii) }, + { MP_ROM_QSTR(MP_QSTR_hexlify), MP_ROM_PTR(&mod_binascii_hexlify_obj) }, + { MP_ROM_QSTR(MP_QSTR_unhexlify), MP_ROM_PTR(&mod_binascii_unhexlify_obj) }, + { MP_ROM_QSTR(MP_QSTR_a2b_base64), MP_ROM_PTR(&mod_binascii_a2b_base64_obj) }, + { MP_ROM_QSTR(MP_QSTR_b2a_base64), MP_ROM_PTR(&mod_binascii_b2a_base64_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_binascii_globals, mp_module_binascii_globals_table); + +const mp_obj_module_t mp_module_ubinascii = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_binascii_globals, +}; diff --git a/ports/cc3200/mods/modubinascii.h b/ports/cc3200/mods/modubinascii.h new file mode 100644 index 000000000..eb9fc4f21 --- /dev/null +++ b/ports/cc3200/mods/modubinascii.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Paul Sokolovsky + * + * 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_CC3200_MODS_MODUBINASCII_H +#define MICROPY_INCLUDED_CC3200_MODS_MODUBINASCII_H + + +#endif // MICROPY_INCLUDED_CC3200_MODS_MODUBINASCII_H diff --git a/ports/cc3200/mptask.c b/ports/cc3200/mptask.c index f06a50277..71b650ff8 100644 --- a/ports/cc3200/mptask.c +++ b/ports/cc3200/mptask.c @@ -113,7 +113,7 @@ static const char fresh_boot_py[] = "# boot.py -- run on boot-up\r\n" uintptr_t cortex_m3_get_sp(void); -void TASK_MicroPython (void *pvParameters) { +void TASK_MicroPython(void *pvParameters) { // get the top of the stack to initialize the garbage collector uint32_t sp = cortex_m3_get_sp(); @@ -262,16 +262,16 @@ void TASK_MicroPython (void *pvParameters) { /****************************************************************************** DEFINE PRIVATE FUNCTIONS ******************************************************************************/ -__attribute__ ((section (".boot"))) -STATIC void mptask_pre_init (void) { +__attribute__ ((section(".boot"))) +STATIC void mptask_pre_init(void) { // this one only makes sense after a poweron reset pyb_rtc_pre_init(); // Create the simple link spawn task - ASSERT (OSI_OK == VStartSimpleLinkSpawnTask(SIMPLELINK_SPAWN_TASK_PRIORITY)); + ASSERT(OSI_OK == VStartSimpleLinkSpawnTask(SIMPLELINK_SPAWN_TASK_PRIORITY)); // Allocate memory for the flash file system - ASSERT ((sflash_vfs_fat = mem_Malloc(sizeof(*sflash_vfs_fat))) != NULL); + ASSERT((sflash_vfs_fat = mem_Malloc(sizeof(*sflash_vfs_fat))) != NULL); // this one allocates memory for the nvic vault pyb_sleep_pre_init(); @@ -295,7 +295,7 @@ STATIC void mptask_pre_init (void) { ASSERT(svTaskHandle != NULL); } -STATIC void mptask_init_sflash_filesystem (void) { +STATIC void mptask_init_sflash_filesystem(void) { FILINFO fno; // Initialise the local flash filesystem. @@ -378,16 +378,16 @@ STATIC void mptask_init_sflash_filesystem (void) { } } -STATIC void mptask_enter_ap_mode (void) { +STATIC void mptask_enter_ap_mode(void) { // append the mac only if it's not the first boot bool add_mac = !PRCMGetSpecialBit(PRCM_FIRST_BOOT_BIT); // enable simplelink in ap mode (use the MAC address to make the ssid unique) - wlan_sl_init (ROLE_AP, MICROPY_PORT_WLAN_AP_SSID, strlen(MICROPY_PORT_WLAN_AP_SSID), + wlan_sl_init(ROLE_AP, MICROPY_PORT_WLAN_AP_SSID, strlen(MICROPY_PORT_WLAN_AP_SSID), MICROPY_PORT_WLAN_AP_SECURITY, MICROPY_PORT_WLAN_AP_KEY, strlen(MICROPY_PORT_WLAN_AP_KEY), MICROPY_PORT_WLAN_AP_CHANNEL, ANTENNA_TYPE_INTERNAL, add_mac); } -STATIC void mptask_create_main_py (void) { +STATIC void mptask_create_main_py(void) { // create empty main.py FIL fp; f_open(&sflash_vfs_fat->fatfs, &fp, "/main.py", FA_WRITE | FA_CREATE_ALWAYS); diff --git a/ports/cc3200/serverstask.c b/ports/cc3200/serverstask.c index 517a7a228..03eed8eeb 100644 --- a/ports/cc3200/serverstask.c +++ b/ports/cc3200/serverstask.c @@ -68,8 +68,8 @@ static volatile bool sleep_sockets = false; ******************************************************************************/ // This is the static memory (TCB and stack) for the servers task -StaticTask_t svTaskTCB __attribute__ ((section (".rtos_heap"))); -StackType_t svTaskStack[SERVERS_STACK_LEN] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +StaticTask_t svTaskTCB __attribute__ ((section(".rtos_heap"))); +StackType_t svTaskStack[SERVERS_STACK_LEN] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); char servers_user[SERVERS_USER_PASS_LEN_MAX + 1]; char servers_pass[SERVERS_USER_PASS_LEN_MAX + 1]; @@ -77,12 +77,12 @@ char servers_pass[SERVERS_USER_PASS_LEN_MAX + 1]; /****************************************************************************** DECLARE PUBLIC FUNCTIONS ******************************************************************************/ -void TASK_Servers (void *pvParameters) { +void TASK_Servers(void *pvParameters) { bool cycle = false; - strcpy (servers_user, SERVERS_DEF_USER); - strcpy (servers_pass, SERVERS_DEF_PASS); + strcpy(servers_user, SERVERS_DEF_USER); + strcpy(servers_pass, SERVERS_DEF_PASS); telnet_init(); ftp_init(); @@ -143,12 +143,12 @@ void TASK_Servers (void *pvParameters) { } } -void servers_start (void) { +void servers_start(void) { servers_data.do_enable = true; mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS * 3); } -void servers_stop (void) { +void servers_stop(void) { servers_data.do_disable = true; do { mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS); @@ -156,24 +156,24 @@ void servers_stop (void) { mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS * 3); } -void servers_reset (void) { +void servers_reset(void) { servers_data.do_reset = true; } -void servers_wlan_cycle_power (void) { +void servers_wlan_cycle_power(void) { servers_data.do_wlan_cycle_power = true; } -bool servers_are_enabled (void) { +bool servers_are_enabled(void) { return servers_data.enabled; } -void server_sleep_sockets (void) { +void server_sleep_sockets(void) { sleep_sockets = true; mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS + 1); } -void servers_close_socket (int16_t *sd) { +void servers_close_socket(int16_t *sd) { if (*sd > 0) { modusocket_socket_delete(*sd); sl_Close(*sd); @@ -181,7 +181,7 @@ void servers_close_socket (int16_t *sd) { } } -void servers_set_login (char *user, char *pass) { +void servers_set_login(char *user, char *pass) { if (strlen(user) > SERVERS_USER_PASS_LEN_MAX || strlen(pass) > SERVERS_USER_PASS_LEN_MAX) { mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s) value")); } @@ -189,7 +189,7 @@ void servers_set_login (char *user, char *pass) { memcpy(servers_pass, pass, SERVERS_USER_PASS_LEN_MAX); } -void servers_set_timeout (uint32_t timeout) { +void servers_set_timeout(uint32_t timeout) { if (timeout < SERVERS_MIN_TIMEOUT_MS) { // timeout is too low mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s) value")); @@ -197,7 +197,7 @@ void servers_set_timeout (uint32_t timeout) { servers_data.timeout = timeout; } -uint32_t servers_get_timeout (void) { +uint32_t servers_get_timeout(void) { return servers_data.timeout; } diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index bd89e8b0c..2cbe9f6be 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -54,9 +54,9 @@ SDKCONFIG_COMBINED = $(BUILD)/sdkconfig.combined SDKCONFIG_H = $(BUILD)/sdkconfig.h # The git hash of the currently supported ESP IDF version. -# These correspond to v3.3.2 and v4.0. +# These correspond to v3.3.2 and v4.0.1. ESPIDF_SUPHASH_V3 := 9e70825d1e1cbf7988cf36981774300066580ea7 -ESPIDF_SUPHASH_V4 := 463a9d8b7f9af8205222b80707f9bdbba7c530e1 +ESPIDF_SUPHASH_V4 := 4c81978a3e2220674a432a588292a4c860eef27b define print_supported_git_hash $(info Supported git hash (v3.3): $(ESPIDF_SUPHASH_V3)) @@ -362,6 +362,7 @@ EXTMOD_SRC_C += $(addprefix extmod/,\ ) LIB_SRC_C = $(addprefix lib/,\ + mbedtls_errors/mp_mbedtls_errors.c \ mp-readline/readline.c \ netutils/netutils.c \ timeutils/timeutils.c \ @@ -506,16 +507,17 @@ ESPIDF_LWIP_O = $(patsubst %.c,%.o,\ $(wildcard $(ESPCOMP)/lwip/port/esp32/*/*.c) \ ) -ESPIDF_MBEDTLS_O = $(patsubst %.c,%.o,\ +# Mbedtls source files, exclude error.c in favor of lib/mbedtls_errors/mp_mbedtls_errors.c +ESPIDF_MBEDTLS_O = $(patsubst %.c,%.o, $(filter-out %/error.c,\ $(wildcard $(ESPCOMP)/mbedtls/mbedtls/library/*.c) \ $(wildcard $(ESPCOMP)/mbedtls/port/*.c) \ $(wildcard $(ESPCOMP)/mbedtls/port/esp32/*.c) \ - ) + )) ESPIDF_MDNS_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/mdns/*.c)) ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DESP_SUPPLICANT -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_TLS -DEAP_TTLS -DEAP_PEAP -DEAP_MSCHAPv2 -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DCONFIG_ECC -D__ets__ -Wno-strict-aliasing -I$(ESPCOMP)/wpa_supplicant/src -Wno-implicit-function-declaration +$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DCONFIG_WPA3_SAE -DCONFIG_IEEE80211W -DESP_SUPPLICANT -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_TLS -DEAP_TTLS -DEAP_PEAP -DEAP_MSCHAPv2 -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DCONFIG_ECC -D__ets__ -Wno-strict-aliasing -I$(ESPCOMP)/wpa_supplicant/src -Wno-implicit-function-declaration else $(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DEMBEDDED_SUPP -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_MSCHAPv2 -DEAP_TTLS -DEAP_TLS -DEAP_PEAP -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DALLOW_EVEN_MOD -D__ets__ -Wno-strict-aliasing endif @@ -555,6 +557,7 @@ ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/src/*.c) \ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/src/*.c) \ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/src/*.c) \ + $(wildcard $(ESPCOMP)/bt/host/nimble/port/src/*.c) \ ) endif diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 25b7b3808..7971ca5d1 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -52,8 +52,11 @@ typedef struct _esp32_rmt_obj_t { uint8_t channel_id; gpio_num_t pin; uint8_t clock_div; + uint16_t carrier_duty_percent; + uint32_t carrier_freq; mp_uint_t num_items; rmt_item32_t *items; + bool loop_en; } esp32_rmt_obj_t; STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -61,6 +64,8 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution + { MP_QSTR_carrier_duty_percent, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 50} }, + { MP_QSTR_carrier_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -68,6 +73,16 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); mp_uint_t clock_div = args[2].u_int; + bool carrier_en = false; + mp_uint_t carrier_duty_percent = 0; + mp_uint_t carrier_freq = 0; + + if (args[4].u_int > 0) { + carrier_en = true; + carrier_duty_percent = args[3].u_int; + carrier_freq = args[4].u_int; + } + if (clock_div < 1 || clock_div > 255) { mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); } @@ -77,6 +92,9 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz self->channel_id = channel_id; self->pin = pin_id; self->clock_div = clock_div; + self->carrier_duty_percent = carrier_duty_percent; + self->carrier_freq = carrier_freq; + self->loop_en = false; rmt_config_t config; config.rmt_mode = RMT_MODE_TX; @@ -85,11 +103,11 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.mem_block_num = 1; config.tx_config.loop_en = 0; - config.tx_config.carrier_en = 0; + config.tx_config.carrier_en = carrier_en; config.tx_config.idle_output_en = 1; config.tx_config.idle_level = 0; - config.tx_config.carrier_duty_percent = 0; - config.tx_config.carrier_freq_hz = 0; + config.tx_config.carrier_duty_percent = self->carrier_duty_percent; + config.tx_config.carrier_freq_hz = self->carrier_freq; config.tx_config.carrier_level = 1; config.clk_div = self->clock_div; @@ -103,8 +121,14 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->pin != -1) { - mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u)", + mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u", self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div); + if (self->carrier_freq > 0) { + mp_printf(print, ", carrier_freq=%u, carrier_duty_percent=%u)", + self->carrier_freq, self->carrier_duty_percent); + } else { + mp_printf(print, ")"); + } } else { mp_printf(print, "RMT()"); } @@ -158,7 +182,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_don STATIC mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, mp_obj_get_int(loop))); + self->loop_en = mp_obj_get_int(loop); + if (!self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + } + } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); @@ -200,8 +232,24 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, self->items[item_index].level1 = start++; } } + + if (self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + } + check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY)); + check_esp_err(rmt_set_tx_intr_en(self->channel_id, false)); + } + check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); + if (self->loop_en) { + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true)); + } + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_write_pulses_obj, 2, esp32_rmt_write_pulses); diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 81e4a6434..6f9ab82d0 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -65,7 +65,6 @@ // MicroPython runs as a task under FreeRTOS #define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) #define MP_TASK_STACK_SIZE (16 * 1024) -#define MP_TASK_STACK_LEN (MP_TASK_STACK_SIZE / sizeof(StackType_t)) int vprintf_null(const char *format, va_list ap) { // do nothing: this is used as a log target during raw repl mode @@ -75,7 +74,7 @@ int vprintf_null(const char *format, va_list ap) { void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)get_sp(); #if MICROPY_PY_THREAD - mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_LEN); + mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_SIZE / sizeof(uintptr_t)); #endif uart_init(); @@ -169,7 +168,7 @@ void app_main(void) { nvs_flash_erase(); nvs_flash_init(); } - xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_LEN, NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); + xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } void nlr_jump_fail(void *val) { diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 975029121..325b27f74 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -776,6 +776,7 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, { MP_ROM_QSTR(MP_QSTR_PHY_TLK110), MP_ROM_INT(PHY_TLK110) }, + { MP_ROM_QSTR(MP_QSTR_PHY_IP101), MP_ROM_INT(PHY_IP101) }, // ETH Clock modes from ESP-IDF #if !MICROPY_ESP_IDF_4 diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 64d2da018..db1b3d130 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H #define MICROPY_INCLUDED_ESP32_MODNETWORK_H -enum { PHY_LAN8720, PHY_TLK110 }; +enum { PHY_LAN8720, PHY_TLK110, PHY_IP101 }; MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); MP_DECLARE_CONST_FUN_OBJ_1(ppp_make_new_obj); diff --git a/ports/esp32/mpnimbleport.c b/ports/esp32/mpnimbleport.c new file mode 100644 index 000000000..a58fcbdbf --- /dev/null +++ b/ports/esp32/mpnimbleport.c @@ -0,0 +1,77 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) + +#include "esp_nimble_hci.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" + +#include "extmod/nimble/modbluetooth_nimble.h" + +STATIC void ble_host_task(void *param) { + DEBUG_printf("ble_host_task\n"); + nimble_port_run(); // This function will return only when nimble_port_stop() is executed. + nimble_port_freertos_deinit(); +} + +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); + esp_nimble_hci_and_controller_init(); +} + +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); + + esp_nimble_hci_and_controller_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start\n"); + nimble_port_freertos_init(ble_host_task); +} + +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); + + // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). + // Calls ble_hs_stop() and waits for stack shutdown. + nimble_port_stop(); + + // Shuts down the event queue. + nimble_port_deinit(); + + // Mark stack as shutdown. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +#endif diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index f39c99b68..d294c9272 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -83,7 +83,7 @@ void mp_thread_gc_others(void) { if (!th->ready) { continue; } - gc_collect_root(th->stack, th->stack_len); // probably not needed + gc_collect_root(th->stack, th->stack_len); } mp_thread_mutex_unlock(&thread_mutex); } @@ -140,17 +140,17 @@ void mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread")); } - // adjust the stack_size to provide room to recover from hitting the limit - *stack_size -= 1024; - // add thread to linked list of all threads th->ready = 0; th->arg = arg; th->stack = pxTaskGetStackStart(th->id); - th->stack_len = *stack_size / sizeof(StackType_t); + th->stack_len = *stack_size / sizeof(uintptr_t); th->next = thread; thread = th; + // adjust the stack_size to provide room to recover from hitting the limit + *stack_size -= 1024; + mp_thread_mutex_unlock(&thread_mutex); } diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 1f9a733a7..7a4ad49e0 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -33,6 +33,7 @@ #include "eth_phy/phy.h" #include "eth_phy/phy_tlk110.h" #include "eth_phy/phy_lan8720.h" +#include "eth_phy/phy_ip101.h" #include "tcpip_adapter.h" #include "modnetwork.h" @@ -123,7 +124,9 @@ STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); } - if (args[ARG_phy_type].u_int != PHY_LAN8720 && args[ARG_phy_type].u_int != PHY_TLK110) { + if (args[ARG_phy_type].u_int != PHY_LAN8720 && + args[ARG_phy_type].u_int != PHY_TLK110 && + args[ARG_phy_type].u_int != PHY_IP101) { mp_raise_ValueError(MP_ERROR_TEXT("invalid phy type")); } @@ -145,6 +148,9 @@ STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar case PHY_LAN8720: config = phy_lan8720_default_ethernet_config; break; + case PHY_IP101: + config = phy_ip101_default_ethernet_config; + break; } self->link_func = config.phy_check_link; diff --git a/ports/esp32/nimble.c b/ports/esp32/nimble.c index e60e08bad..16829732c 100644 --- a/ports/esp32/nimble.c +++ b/ports/esp32/nimble.c @@ -44,7 +44,6 @@ void mp_bluetooth_nimble_port_preinit(void) { } void mp_bluetooth_nimble_port_postinit(void) { - nimble_port_freertos_init(ble_host_task); } void mp_bluetooth_nimble_port_deinit(void) { @@ -52,6 +51,7 @@ void mp_bluetooth_nimble_port_deinit(void) { } void mp_bluetooth_nimble_port_start(void) { + nimble_port_freertos_init(ble_host_task); } #endif diff --git a/ports/esp8266/boards/esp8266.ld b/ports/esp8266/boards/esp8266.ld new file mode 100644 index 000000000..745edaadb --- /dev/null +++ b/ports/esp8266/boards/esp8266.ld @@ -0,0 +1,12 @@ +/* GNU linker script for ESP8266 */ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 0x10 + dram0_0_seg : org = 0x3ffe8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x8000 + irom0_0_seg : org = 0x40209000, len = 0x8f000 +} + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/boards/manifest_minimal.py b/ports/esp8266/boards/manifest_minimal.py new file mode 100644 index 000000000..99bea9199 --- /dev/null +++ b/ports/esp8266/boards/manifest_minimal.py @@ -0,0 +1,5 @@ +freeze('$(PORT_DIR)/modules', ( + '_boot.py', 'flashbdev.py', 'inisetup.py', 'port_diag.py', + 'webrepl.py', 'webrepl_setup.py', 'websocket_helper.py' +)) +freeze('$(MPY_DIR)/tools', ('upip.py', 'upip_utarfile.py')) diff --git a/ports/esp8266/intr.c b/ports/esp8266/intr.c new file mode 100644 index 000000000..456d6cb04 --- /dev/null +++ b/ports/esp8266/intr.c @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include "etshal.h" +#include "ets_alt_task.h" + +#include "modmachine.h" + +// this is in a separate file so it can go in iRAM +void pin_intr_handler_iram(void *arg) { + uint32_t status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, status); + pin_intr_handler(status); +} diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index cbd2b8595..c4ea70128 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -88,9 +88,13 @@ SRC_TINYUSB_IMX_C += \ SRC_C = \ main.c \ + led.c \ + pin.c \ tusb_port.c \ board_init.c \ $(BOARD_DIR)/flash_config.c \ + $(BOARD_DIR)/pins.c \ + machine_led.c \ modutime.c \ modmachine.c \ mphalport.c \ @@ -108,7 +112,12 @@ SRC_SS = $(MCU_DIR)/gcc/startup_$(MCU_SERIES).S SRC_S = lib/utils/gchelper_m3.s \ # List of sources for qstr extraction -SRC_QSTR += modutime.c modmachine.c +SRC_QSTR += \ + machine_led.c \ + modutime.c \ + modmachine.c \ + pin.c \ + $(BOARD_DIR)/pins.c \ OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) diff --git a/ports/mimxrt/board_init.c b/ports/mimxrt/board_init.c index a3c27b045..6f5235d3b 100644 --- a/ports/mimxrt/board_init.c +++ b/ports/mimxrt/board_init.c @@ -45,8 +45,6 @@ volatile uint32_t systick_ms = 0; const uint8_t dcd_data[] = { 0x00 }; -void board_led_write(bool state); - void board_init(void) { // Init clock BOARD_BootClockRUN(); @@ -58,14 +56,6 @@ void board_init(void) { // 1ms tick timer SysTick_Config(SystemCoreClock / 1000); - // LED - IOMUXC_SetPinMux(MICROPY_HW_LED_PINMUX, 0U); - IOMUXC_SetPinConfig(MICROPY_HW_LED_PINMUX, 0x10B0U); - - gpio_pin_config_t led_config = { kGPIO_DigitalOutput, 0, kGPIO_NoIntmode }; - GPIO_PinInit(MICROPY_HW_LED_PORT, MICROPY_HW_LED_PIN, &led_config); - board_led_write(true); - // ------------- USB0 ------------- // // Clock @@ -95,10 +85,6 @@ void board_init(void) { // CLOCK_EnableUsbhs1Clock(kCLOCK_Usb480M, 480000000U); } -void board_led_write(bool state) { - GPIO_PinWrite(MICROPY_HW_LED_PORT, MICROPY_HW_LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON)); -} - void SysTick_Handler(void) { systick_ms++; } diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h index 02d34fc42..eeddd0e02 100644 --- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h @@ -3,6 +3,7 @@ #define BOARD_FLASH_SIZE (16 * 1024 * 1024) -#define MICROPY_HW_LED_PINMUX IOMUXC_GPIO_11_GPIOMUX_IO11 -#define MICROPY_HW_LED_PORT GPIO1 -#define MICROPY_HW_LED_PIN 11 +// i.MX RT1010 EVK has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_11) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c new file mode 100644 index 000000000..e0397e09e --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_11_af[] = { + PIN_AF(GPIOMUX_IO11, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_11 = PIN(GPIO_11, GPIO1, 5, GPIO_11_af); diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h new file mode 100644 index 000000000..99534932a --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_11; diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index 6b59649fc..0512c48a7 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -3,6 +3,9 @@ __stack_size__ = 0x6000; _estack = __StackTop; _sstack = __StackLimit; +/* Do not use the traditional C heap. */ +__heap_size__ = 0; + /* Use second OCRAM bank for GC heap. */ _gc_heap_start = ORIGIN(m_data2); _gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h new file mode 100644 index 000000000..c0c6ea243 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h @@ -0,0 +1,270 @@ +/* + * Copyright 2019 NXP. + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Based on tinyusb/hw/bsp/teensy_40/evkmimxrt1020_flexspi_nor_config.h + +#ifndef __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_133MHz = 7, + kFlexSpiSerialClk_166MHz = 8, + kFlexSpiSerialClk_200MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c new file mode 100644 index 000000000..56007931a --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c @@ -0,0 +1,51 @@ +/* + * Copyright 2019 NXP. + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Based on tinyusb/hw/bsp/teensy_40/evkmimxrt1010_flexspi_nor_config.c + +#include "evkmimxrt1020_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 256u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h new file mode 100644 index 000000000..8598f76bf --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h @@ -0,0 +1,9 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1020 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1021DAG5A" + +#define BOARD_FLASH_SIZE (8 * 1024 * 1024) + +// i.MX RT1020 EVK has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_05) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk new file mode 100644 index 000000000..f3b168952 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = MIMXRT1021 +MCU_VARIANT = MIMXRT1021DAG5A + +JLINK_PATH ?= /media/RT1020-EVK/ + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c new file mode 100644 index 000000000..946b6efca --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_05_af[] = { + PIN_AF(GPIO1_IO05, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_05 = PIN(GPIO_AD_B0_05, GPIO1, 5, GPIO_AD_B0_05_af); diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h new file mode 100644 index 000000000..158929911 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_05; diff --git a/ports/mimxrt/boards/MIMXRT1021.ld b/ports/mimxrt/boards/MIMXRT1021.ld new file mode 100644 index 000000000..6b59649fc --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1021.ld @@ -0,0 +1,8 @@ +/* 24kiB stack. */ +__stack_size__ = 0x6000; +_estack = __StackTop; +_sstack = __StackLimit; + +/* Use second OCRAM bank for GC heap. */ +_gc_heap_start = ORIGIN(m_data2); +_gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h index 714f987a5..8bd2a5791 100644 --- a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h @@ -3,6 +3,7 @@ #define BOARD_FLASH_SIZE (8 * 1024 * 1024) -#define MICROPY_HW_LED_PINMUX IOMUXC_GPIO_AD_B0_09_GPIO1_IO09 -#define MICROPY_HW_LED_PORT GPIO1 -#define MICROPY_HW_LED_PIN 9 +// MIMXRT1060_EVK has 1 user LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_09) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c new file mode 100644 index 000000000..d5da9c6f9 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_09_af[] = { + PIN_AF(GPIO1_IO09, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_09 = PIN(GPIO_AD_B0_09, GPIO1, 9, GPIO_AD_B0_09_af); diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h new file mode 100644 index 000000000..baef51c6c --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_09; diff --git a/ports/mimxrt/boards/MIMXRT1064.ld b/ports/mimxrt/boards/MIMXRT1064.ld new file mode 100644 index 000000000..bf37c2180 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064.ld @@ -0,0 +1,8 @@ +/* 24kiB stack. */ +__stack_size__ = 0x6000; +_estack = __StackTop; +_sstack = __StackLimit; + +/* Use second OCRAM bank for GC heap. */ +_gc_heap_start = ORIGIN(m_data2); +_gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); \ No newline at end of file diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h new file mode 100644 index 000000000..efdfe583f --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h @@ -0,0 +1,268 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_120MHz = 7, + kFlexSpiSerialClk_133MHz = 8, + kFlexSpiSerialClk_166MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c new file mode 100644 index 000000000..bfb1c2d59 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c @@ -0,0 +1,49 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "evkmimxrt1064_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__CC_ARM) || defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 256u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h new file mode 100644 index 000000000..e05c8824d --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1064 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1064DVL6A" + + +// MIMXRT1064_EVK has 1 user LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_09) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk new file mode 100644 index 000000000..700988919 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk @@ -0,0 +1,9 @@ +MCU_SERIES = MIMXRT1064 +MCU_VARIANT = MIMXRT1064DVL6A + +JLINK_PATH ?= /media/RT1064-EVK/ + +CFLAGS += -DBOARD_FLASH_SIZE=0x400000 + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c new file mode 100644 index 000000000..d5da9c6f9 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_09_af[] = { + PIN_AF(GPIO1_IO09, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_09 = PIN(GPIO_AD_B0_09, GPIO1, 9, GPIO_AD_B0_09_af); diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h new file mode 100644 index 000000000..baef51c6c --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_09; diff --git a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h index 10c8b7a6d..7d5647857 100644 --- a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h +++ b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h @@ -3,6 +3,7 @@ #define BOARD_FLASH_SIZE (2 * 1024 * 1024) -#define MICROPY_HW_LED_PINMUX IOMUXC_GPIO_B0_03_GPIO2_IO03 // D13 -#define MICROPY_HW_LED_PORT GPIO2 -#define MICROPY_HW_LED_PIN 3 +// Teensy 4.0 has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_B0_03) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) diff --git a/ports/mimxrt/boards/TEENSY40/pins.c b/ports/mimxrt/boards/TEENSY40/pins.c new file mode 100644 index 000000000..c7bfc102d --- /dev/null +++ b/ports/mimxrt/boards/TEENSY40/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_B0_03_af[] = { + PIN_AF(GPIO2_IO03, PIN_AF_MODE_ALT5, GPIO2, 0x10B0U), +}; + +pin_obj_t GPIO_B0_03 = PIN(GPIO_B0_03, GPIO2, 3, GPIO_B0_03_af); diff --git a/ports/mimxrt/boards/TEENSY40/pins.h b/ports/mimxrt/boards/TEENSY40/pins.h new file mode 100644 index 000000000..c0b6dcdfa --- /dev/null +++ b/ports/mimxrt/boards/TEENSY40/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_B0_03; diff --git a/ports/mimxrt/led.c b/ports/mimxrt/led.c new file mode 100644 index 000000000..bec97dd8f --- /dev/null +++ b/ports/mimxrt/led.c @@ -0,0 +1,102 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "fsl_gpio.h" +#include "fsl_iomuxc.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "led.h" + +#if NUM_LEDS + +const machine_led_obj_t machine_led_obj[NUM_LEDS] = { + { + .base = {&machine_led_type}, + .led_id = 1U, + .led_pin = &MICROPY_HW_LED1_PIN, + } +}; + +void led_init(void) { + // Turn off LEDs and initialize + for (mp_int_t led = 0; led < NUM_LEDS; led++) { + const pin_obj_t *led_pin = machine_led_obj[led].led_pin; + + gpio_pin_config_t pin_config = { + .outputLogic = 1U, + .direction = kGPIO_DigitalOutput, + .interruptMode = kGPIO_NoIntmode, + }; + + GPIO_PinInit(led_pin->gpio, led_pin->pin, &pin_config); + + // ALT mode for GPIO is always 5 + IOMUXC_SetPinMux(led_pin->muxRegister, 5U, 0, 0, led_pin->configRegister, + 1U); // Software Input On Field: Input Path is determined by functionality + IOMUXC_SetPinConfig(led_pin->muxRegister, 5U, 0, 0, led_pin->configRegister, 0x10B0U); + MICROPY_HW_LED_OFF(led_pin); + } +} + +void led_state(machine_led_t led, int state) { + if (led < 1 || led > NUM_LEDS) { + return; + } + + const pin_obj_t *led_pin = machine_led_obj[led - 1].led_pin; + + if (state == 0) { + // turn LED off + MICROPY_HW_LED_OFF(led_pin); + } else { + // turn LED on + MICROPY_HW_LED_ON(led_pin); + } +} + +void led_toggle(machine_led_t led) { + if (led < 1 || led > NUM_LEDS) { + return; + } + + const pin_obj_t *led_pin = machine_led_obj[led - 1].led_pin; + mp_hal_pin_toggle(led_pin); +} + +void led_debug(int value, int delay) { + for (mp_int_t i = 0; i < NUM_LEDS; i++) { + led_state(i + 1, (value & (1 << i))); + } + mp_hal_delay_ms(delay); +} + +#else + +void led_init(void) { +} + +#endif diff --git a/ports/mimxrt/led.h b/ports/mimxrt/led.h new file mode 100644 index 000000000..35418321d --- /dev/null +++ b/ports/mimxrt/led.h @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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_MIMXRT_LED_H +#define MICROPY_INCLUDED_MIMXRT_LED_H + +#include "pin.h" + +#if defined(MICROPY_HW_LED1_PIN) +#define NUM_LEDS (1) +#else +#define NUM_LEDS (0) +#endif + +typedef enum { + MACHINE_BOARD_LED = 1, +} machine_led_t; + +typedef struct _machine_led_obj_t { + mp_obj_base_t base; + mp_uint_t led_id; + const pin_obj_t *led_pin; +} machine_led_obj_t; + +void led_init(void); +void led_state(machine_led_t led, int state); +void led_toggle(machine_led_t led); +void led_debug(int value, int delay); + +extern const mp_obj_type_t machine_led_type; +extern const machine_led_obj_t machine_led_obj[NUM_LEDS]; + +#endif // MICROPY_INCLUDED_MIMXRT_LED_H diff --git a/ports/mimxrt/machine_led.c b/ports/mimxrt/machine_led.c new file mode 100644 index 000000000..07c7b180a --- /dev/null +++ b/ports/mimxrt/machine_led.c @@ -0,0 +1,91 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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/mphal.h" +#include "led.h" + +#if NUM_LEDS + +STATIC void led_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "LED(%u)", self->led_id); +} + +STATIC mp_obj_t led_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + // Extract arguments + mp_int_t led_id = mp_obj_get_int(args[0]); + + // Check led id is in range + if (!(1 <= led_id && led_id <= NUM_LEDS)) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "LED(%d) doesn't exist", led_id)); + } + + // Return reference to static object + return MP_OBJ_FROM_PTR(&machine_led_obj[led_id - 1]); +} + +STATIC mp_obj_t led_obj_on(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + MICROPY_HW_LED_ON(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_on_obj, led_obj_on); + +STATIC mp_obj_t led_obj_off(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + MICROPY_HW_LED_OFF(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_off_obj, led_obj_off); + +STATIC mp_obj_t led_obj_toggle(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_pin_toggle(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_toggle_obj, led_obj_toggle); + +STATIC const mp_rom_map_elem_t led_locals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&led_obj_on_obj)}, + {MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&led_obj_off_obj)}, + {MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&led_obj_toggle_obj)}, +}; + +STATIC MP_DEFINE_CONST_DICT(led_locals_dict, led_locals_dict_table); + +const mp_obj_type_t machine_led_type = { + {&mp_type_type}, + .name = MP_QSTR_LED, + .print = led_obj_print, + .make_new = led_obj_make_new, + .locals_dict = (mp_obj_dict_t *)&led_locals_dict, +}; + +#endif diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index d94bf2441..e06283a8a 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -33,6 +33,7 @@ #include "lib/utils/gchelper.h" #include "lib/utils/pyexec.h" #include "tusb.h" +#include "led.h" extern uint8_t _sstack, _estack, _gc_heap_start, _gc_heap_end; @@ -41,6 +42,7 @@ void board_init(void); int main(void) { board_init(); tusb_init(); + led_init(); mp_stack_set_top(&_estack); mp_stack_set_limit(&_estack - &_sstack - 1024); diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 747b7bc27..a6aab7b4a 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "extmod/machine_mem.h" +#include "led.h" #include CPU_HEADER_H @@ -48,6 +49,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + #if NUM_LEDS + { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&machine_led_type) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index e1cf84f6c..2b9e49432 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -29,6 +29,10 @@ #include +#define mp_hal_pin_high(p) (GPIO_PinWrite(p->gpio, p->pin, 1U)) +#define mp_hal_pin_low(p) (GPIO_PinWrite(p->gpio, p->pin, 0U)) +#define mp_hal_pin_toggle(p) (GPIO_PortToggle(p->gpio, (1 << p->pin))) + extern volatile uint32_t systick_ms; void mp_hal_set_interrupt_char(int c); diff --git a/ports/mimxrt/pin.c b/ports/mimxrt/pin.c new file mode 100644 index 000000000..b30f4be41 --- /dev/null +++ b/ports/mimxrt/pin.c @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +const mp_obj_type_t pin_type = { + .base = {&mp_type_type}, + .name = MP_QSTR_Pin, +}; + +const mp_obj_type_t pin_af_type = { + {&mp_type_type}, + .name = MP_QSTR_PinAF, +}; diff --git a/ports/mimxrt/pin.h b/ports/mimxrt/pin.h new file mode 100644 index 000000000..cfb1e6b57 --- /dev/null +++ b/ports/mimxrt/pin.h @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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_MIMXRT_PIN_H +#define MICROPY_INCLUDED_MIMXRT_PIN_H + +#include "fsl_gpio.h" +#include "py/obj.h" + +enum { + PIN_MODE_IN = 0, + PIN_MODE_OUT, + PIN_MODE_ALT, +}; + +enum { + PIN_AF_MODE_ALT1 = 1, + PIN_AF_MODE_ALT2, + PIN_AF_MODE_ALT3, + PIN_AF_MODE_ALT4, + PIN_AF_MODE_ALT5, + PIN_AF_MODE_ALT6, + PIN_AF_MODE_ALT7, + PIN_AF_MODE_ALT8, +}; + +typedef struct { + mp_obj_base_t base; + qstr name; // port name + uint32_t af_mode; // alternate function + void *instance; // pointer to peripheral instance for alternate function + uint32_t pad_config; // pad configuration for alternate function +} pin_af_obj_t; + +typedef struct { + mp_obj_base_t base; + qstr name; // pad name + GPIO_Type *gpio; // gpio instance for pin + uint32_t pin; // pin number + uint32_t muxRegister; + uint32_t configRegister; + uint32_t mode; // current pin mode + uint32_t af_mode; // current alternate function mode + size_t af_list_len; // length of available alternate functions list + const pin_af_obj_t *af_list; // pointer tolist with alternate functions +} pin_obj_t; + +extern const mp_obj_type_t pin_type; +extern const mp_obj_type_t pin_af_type; + +#define PIN_AF(_name, _af_mode, _instance, _pad_config) \ + { \ + .base = { &pin_af_type }, \ + .name = MP_QSTR_##_name, \ + .af_mode = (uint32_t)(_af_mode), \ + .instance = (void *)(_instance), \ + .pad_config = (uint32_t)(_pad_config), \ + } \ + +#define PIN(_name, _gpio, _pin, _af_list) \ + { \ + .base = { &pin_type }, \ + .name = MP_QSTR_##_name, \ + .gpio = (_gpio), \ + .pin = (uint32_t)(_pin), \ + .muxRegister = (uint32_t)&(IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_##_name]), \ + .configRegister = (uint32_t)&(IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_PAD_CTL_PAD_##_name]), \ + .mode = PIN_MODE_IN, \ + .af_mode = PIN_AF_MODE_ALT5, \ + .af_list_len = (size_t)(sizeof((_af_list)) / sizeof(pin_af_obj_t)), \ + .af_list = (_af_list), \ + } \ + +// Include board specific pins +#include "pins.h" + +#endif // MICROPY_INCLUDED_MIMXRT_PIN_H diff --git a/ports/mimxrt/tusb_config.h b/ports/mimxrt/tusb_config.h index f2367c204..c7ec05e63 100644 --- a/ports/mimxrt/tusb_config.h +++ b/ports/mimxrt/tusb_config.h @@ -30,7 +30,8 @@ #define CFG_TUSB_OS (OPT_OS_NONE) #define CFG_TUD_CDC (1) -#define CFG_TUD_CDC_RX_BUFSIZE (256) -#define CFG_TUD_CDC_TX_BUFSIZE (256) +#define CFG_TUD_CDC_RX_BUFSIZE (512) +#define CFG_TUD_CDC_TX_BUFSIZE (512) +#define CFG_TUD_CDC_EPSIZE (512) #endif // MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H diff --git a/ports/minimal/mpconfigport.h b/ports/minimal/mpconfigport.h index c3bdf66c1..b34217f68 100644 --- a/ports/minimal/mpconfigport.h +++ b/ports/minimal/mpconfigport.h @@ -19,6 +19,7 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) #define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_PY_ASSIGN_EXPR (0) #define MICROPY_PY_BUILTINS_BYTEARRAY (0) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0) #define MICROPY_PY_BUILTINS_ENUMERATE (0) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 23d5cd20d..8f73336e0 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -39,7 +39,9 @@ endif QSTR_DEFS = qstrdefsport.h $(BUILD)/pins_qstr.h # MicroPython feature configurations +ifeq ($(DEBUG), 0) MICROPY_ROM_TEXT_COMPRESSION ?= 1 +endif # include py core make definitions include ../../py/py.mk diff --git a/ports/nrf/README.md b/ports/nrf/README.md index b5f39267e..b08a03456 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -41,8 +41,10 @@ This is a port of MicroPython to the Nordic Semiconductor nRF series of chips. * [PCA10056](http://www.nordicsemi.com/eng/Products/nRF52840-Preview-DK) * [PCA10059](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) * [Particle Xenon](https://docs.particle.io/xenon/) + * [nRF52840 MDK USB Dongle](boards/nrf52840-mdk-usb-dongle/README.md) * nRF9160 * [PCA10090](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF9160-DK) + * [Actinius Icarus](https://www.actinius.com/icarus) ## Compile and Flash @@ -135,6 +137,7 @@ pca10056 | s140 | Peripheral and Central | [Segge pca10059 | s140 | Peripheral and Central | Manual, SWDIO and SWCLK solder points on the sides. particle_xenon | s140 | Peripheral and Central | [Black Magic Probe](#black-magic-probe-targets) pca10090 | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) +actinius_icarus | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) ## IDAP-M/IDAP-Link Targets diff --git a/ports/nrf/boards/actinius_icarus/mpconfigboard.h b/ports/nrf/boards/actinius_icarus/mpconfigboard.h new file mode 100644 index 000000000..e99d731df --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/mpconfigboard.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Actinius + * + * 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 ACTINIUS_ICARUS + +#define MICROPY_HW_BOARD_NAME "Actinius Icarus" +#define MICROPY_HW_MCU_NAME "NRF9160" +#define MICROPY_PY_SYS_PLATFORM "nrf9160" + +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_HW_PWM (0) +#define MICROPY_PY_MACHINE_HW_SPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_ADC (0) +#define MICROPY_PY_MACHINE_TEMP (0) +#define MICROPY_PY_RANDOM_HW_RNG (0) + +#define MICROPY_MBFS (0) +#define MICROPY_VFS (1) + +#define MICROPY_HW_HAS_LED (1) +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (0) +#define MICROPY_HW_HAS_SDCARD (0) +#define MICROPY_HW_HAS_MMA7660 (0) +#define MICROPY_HW_HAS_LIS3DSH (0) +#define MICROPY_HW_HAS_LCD (0) +#define MICROPY_HW_ENABLE_RNG (0) +#define MICROPY_HW_ENABLE_RTC (0) +#define MICROPY_HW_ENABLE_TIMER (0) +#define MICROPY_HW_ENABLE_SERVO (0) +#define MICROPY_HW_ENABLE_DAC (0) +#define MICROPY_HW_ENABLE_CAN (0) + +#define MICROPY_HW_LED_TRICOLOR (1) +#define MICROPY_HW_LED_PULLUP (1) + +#define MICROPY_HW_LED_RED (10) // LED1 / RED +#define MICROPY_HW_LED_GREEN (11) // LED2 / GREEN +#define MICROPY_HW_LED_BLUE (12) // LED3 / BLUE + +// UART config +#define MICROPY_HW_UART1_RX (6) +#define MICROPY_HW_UART1_TX (9) +#define MICROPY_HW_UART1_CTS (25) +#define MICROPY_HW_UART1_RTS (7) +#define MICROPY_HW_UART1_HWFC (1) + +// SPI0 config +#define MICROPY_HW_SPI0_NAME "SPI0" + +#define MICROPY_HW_SPI0_SCK (20) +#define MICROPY_HW_SPI0_MOSI (21) +#define MICROPY_HW_SPI0_MISO (22) + +#define HELP_TEXT_BOARD_LED "1,2,3" diff --git a/ports/nrf/boards/actinius_icarus/mpconfigboard.mk b/ports/nrf/boards/actinius_icarus/mpconfigboard.mk new file mode 100644 index 000000000..c1e05fd30 --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/mpconfigboard.mk @@ -0,0 +1,6 @@ +MCU_SERIES = m33 +MCU_VARIANT = nrf91 +MCU_SUB_VARIANT = nrf9160 +LD_FILES += boards/nrf9160_1M_256k.ld + +NRF_DEFINES += -DNRF9160_XXAA -DNRF_TRUSTZONE_NONSECURE diff --git a/ports/nrf/boards/actinius_icarus/pins.csv b/ports/nrf/boards/actinius_icarus/pins.csv new file mode 100644 index 000000000..50c284dfd --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/pins.csv @@ -0,0 +1,32 @@ +P0,P0 +P1,P1 +P2,P2 +P3,P3 +P4,P4 +BUTTON,P5 +UART_RX,P6 +UART_RTS,P7 +SIM_SELECT,P8 +UART_TX,P9 +LED1,P10 +LED2,P11 +LED3,P12 +BAT_SENSE,P13 +A1,P14 +A2,P15 +A3,P16 +A4,P17 +A5,P18 +A6,P19 +SPI_SCK,P20 +SPI_MOSI,P21 +SPI_MISO,P22 +P23,P23 +P24,P24 +UART_CTS,P25 +I2C_SDA,P26 +I2C_SCL,P27 +ACCEL_INT1,P28 +ACCEL_INT2,P29 +P30,P30 +P31,P31 diff --git a/ports/nrf/boards/memory.ld b/ports/nrf/boards/memory.ld index c95daf3d9..f1f9a2a4c 100644 --- a/ports/nrf/boards/memory.ld +++ b/ports/nrf/boards/memory.ld @@ -1,14 +1,17 @@ /* Flash layout: softdevice | application | filesystem */ /* RAM layout: softdevice RAM | application RAM */ -_sd_size = DEFINED(_sd_size) ? _sd_size : 0; + +_ram_start = DEFINED(_ram_start) ? _ram_start : 0x20000000; +_flash_start = DEFINED(_flash_start) ? _flash_start : 0; +_sd_size = DEFINED(_sd_size) ? _sd_size : _flash_start; _sd_ram = DEFINED(_sd_ram) ? _sd_ram : 0; _fs_size = DEFINED(_fs_size) ? _fs_size : 64K; /* TODO: set to 0 if not using the filesystem */ _app_size = _flash_size - _sd_size - _fs_size; _app_start = _sd_size; _fs_start = _sd_size + _app_size; _fs_end = _fs_start + _fs_size; -_app_ram_start = 0x20000000 + _sd_ram; +_app_ram_start = _ram_start + _sd_ram; _app_ram_size = _ram_size - _sd_ram; _heap_start = _ebss; _heap_end = _ram_end - _stack_size; diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md new file mode 100644 index 000000000..c39a800d7 --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md @@ -0,0 +1,49 @@ +nRF52840 MDK USB Dongle +======================= + +The *[nRF52840 MDK USB +Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle)* is a small, +low-cost development board in a USB dongle form-factor powered by an nRF52840 +with 1MB flash and 256KB RAM. + +This device is pre-installed with [Open +Bootloader](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/programming/), +allowing DFU upgrades over USB using Nordic [nRF +Connect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-desktop) +or [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil/). To support +Open Bootloader, the flash and memory layout must be adjusted slightly (details +[here](https://devzone.nordicsemi.com/nordic/short-range-guides/b/getting-started/posts/nrf52840-dongle-programming-tutorial)) +from the typical nRF build; this board definition ensure the appropriate build +configuration is used for MicroPython. + + +Pinout +------ + +The [pinout +diagram](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/#pinout-diagram) +provides an overview of the available pins and their capabilities. All pins are +available in MicroPython, using the pin numbers labelled in the diagram +(excluding the leading port number, *P0*). + +The three LEDs are available either through the usual `Pin` mechanism - pins +22-24 - or by `board.LED(n)` where n can be 1, 2 or 3. + + +Build instructions +------------------ + +Follow the standard [nRF Port build instructions](../../README.md); but use +`nrf52840-mdk-usb-dongle` as the value for `BOARD`: + + make BOARD=nrf52840-mdk-usb-dongle + +The build artifacts will be created in `build-nrf52840-mdk-usb-dongle`. Once +built, the easiest way to deploy to the device is to open `firmware.hex` using +*nRF Connect* and select *Write*. Detailed instructions can be found on the +[developer +wiki](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/programming/). + +**Note** that the regular method of deployment for the MicroPython nRF port +(using `make deploy`) will *not* operate correctly and will overwrite the +bootloader. diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h new file mode 100644 index 000000000..f048c39e0 --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h @@ -0,0 +1,71 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Matt Trentini + * + * 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_HW_BOARD_NAME "MDK-USB-DONGLE" +#define MICROPY_HW_MCU_NAME "NRF52840" +#define MICROPY_PY_SYS_PLATFORM "nrf52840-MDK-USB-Dongle" + +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_HW_PWM (1) +#define MICROPY_PY_MACHINE_HW_SPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_ADC (1) +#define MICROPY_PY_MACHINE_TEMP (1) + +#define MICROPY_HW_ENABLE_RNG (1) + +#define MICROPY_HW_USB_CDC (1) + +#define MICROPY_HW_HAS_LED (1) +#define MICROPY_HW_LED_COUNT (3) +#define MICROPY_HW_LED_PULLUP (1) + +#define MICROPY_HW_LED1 (22) // LED1 GREEN +#define MICROPY_HW_LED2 (23) // LED2 RED +#define MICROPY_HW_LED3 (24) // LED3 BLUE + +// UART config +#define MICROPY_HW_UART1_RX (7) +#define MICROPY_HW_UART1_TX (8) +#define MICROPY_HW_UART1_CTS (9) +#define MICROPY_HW_UART1_RTS (10) +#define MICROPY_HW_UART1_HWFC (1) + +// SPI0 config +#define MICROPY_HW_SPI0_NAME "SPI0" + +#define MICROPY_HW_SPI0_SCK (19) +#define MICROPY_HW_SPI0_MOSI (20) +#define MICROPY_HW_SPI0_MISO (21) + +#define MICROPY_HW_PWM0_NAME "PWM0" +#define MICROPY_HW_PWM1_NAME "PWM1" +#define MICROPY_HW_PWM2_NAME "PWM2" +#define MICROPY_HW_PWM3_NAME "PWM3" + +#define HELP_TEXT_BOARD_LED "1,2,3" diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk new file mode 100644 index 000000000..f98d5c88a --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = m4 +MCU_VARIANT = nrf52 +MCU_SUB_VARIANT = nrf52840 +SOFTDEV_VERSION = 6.1.1 +LD_FILES += boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld boards/nrf52840_1M_256k.ld + +NRF_DEFINES += -DNRF52840_XXAA diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld b/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld new file mode 100644 index 000000000..d6c6e743a --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld @@ -0,0 +1,2 @@ +_ram_start = 0x20000008; +_flash_start = 0x1000; diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv b/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv new file mode 100644 index 000000000..571628637 --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv @@ -0,0 +1,17 @@ +P2,P2,ADC1_CH0 +P3,P3,ADC1_CH1 +P4,P4,ADC1_CH2 +P5,P5,ADC1_CH3 +P6,P6 +P7,P7 +P8,P8 +P9,P9 +P10,P10 +USER,P18,P18 +P19,P19 +P20,P20 +P21,P21 +LED_G,P22,P22 +LED_R,P23,P23 +LED_B,P24,P24 +P25,P25 diff --git a/ports/nrf/boards/pca10090/mpconfigboard.h b/ports/nrf/boards/pca10090/mpconfigboard.h index e4e514290..28381c40d 100644 --- a/ports/nrf/boards/pca10090/mpconfigboard.h +++ b/ports/nrf/boards/pca10090/mpconfigboard.h @@ -34,7 +34,7 @@ #define MICROPY_PY_MACHINE_HW_PWM (0) #define MICROPY_PY_MACHINE_HW_SPI (1) #define MICROPY_PY_MACHINE_TIMER (0) -#define MICROPY_PY_MACHINE_RTCOUNTER (0) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_ADC (0) #define MICROPY_PY_MACHINE_TEMP (0) diff --git a/ports/nrf/drivers/bluetooth/ble_drv.c b/ports/nrf/drivers/bluetooth/ble_drv.c index 7619dc039..1a64cdfbd 100644 --- a/ports/nrf/drivers/bluetooth/ble_drv.c +++ b/ports/nrf/drivers/bluetooth/ble_drv.c @@ -607,12 +607,12 @@ bool ble_drv_advertise_data(ubluepy_advertise_data_t * p_adv_params) { #if (BLUETOOTH_SD == 110) if ((err_code = sd_ble_gap_adv_data_set(adv_data, byte_pos, NULL, 0)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not apply advertisment data. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not apply advertisment data. status: 0x" HEX2_FMT), (uint16_t)err_code); } #else if ((err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not apply advertisment data. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not apply advertisment data. status: 0x" HEX2_FMT), (uint16_t)err_code); } #endif BLE_DRIVER_LOG("Set Adv data size: " UINT_FMT "\n", byte_pos); @@ -627,7 +627,7 @@ bool ble_drv_advertise_data(ubluepy_advertise_data_t * p_adv_params) { #endif if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not start advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not start advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } m_adv_in_progress = true; @@ -642,12 +642,12 @@ void ble_drv_advertise_stop(void) { #if (BLUETOOTH_SD == 110) if ((err_code = sd_ble_gap_adv_stop()) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not stop advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not stop advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } #else if ((err_code = sd_ble_gap_adv_stop(m_adv_handle)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not stop advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not stop advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } #endif } @@ -667,7 +667,7 @@ void ble_drv_attr_s_read(uint16_t conn_handle, uint16_t handle, uint16_t len, ui &gatts_value); if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not read attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not read attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -684,7 +684,7 @@ void ble_drv_attr_s_write(uint16_t conn_handle, uint16_t handle, uint16_t len, u if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not write attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not write attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -708,7 +708,7 @@ void ble_drv_attr_s_notify(uint16_t conn_handle, uint16_t handle, uint16_t len, uint32_t err_code; if ((err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not notify attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not notify attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } m_tx_in_progress++; BLE_DRIVER_LOG("Queued TX, m_tx_in_progress: %u\n", m_tx_in_progress); @@ -747,7 +747,7 @@ void ble_drv_attr_c_read(uint16_t conn_handle, uint16_t handle, mp_obj_t obj, bl 0); if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not read attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not read attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } while (gattc_char_data_handle != NULL) { @@ -777,7 +777,7 @@ void ble_drv_attr_c_write(uint16_t conn_handle, uint16_t handle, uint16_t len, u if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not write attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not write attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } while (m_write_done != true) { @@ -808,7 +808,7 @@ void ble_drv_scan_start(bool cont) { } if ((err_code = sd_ble_gap_scan_start(p_scan_params, &scan_buffer)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not start scanning. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not start scanning. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -854,7 +854,7 @@ void ble_drv_connect(uint8_t * p_addr, uint8_t addr_type) { &conn_params, conn_tag)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not connect. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not connect. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -1131,6 +1131,12 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { BLE_DRIVER_LOG("GATTS EVT EXCHANGE MTU REQUEST\n"); (void)sd_ble_gatts_exchange_mtu_reply(p_ble_evt->evt.gatts_evt.conn_handle, 23); // MAX MTU size break; + + case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST: + BLE_DRIVER_LOG("BLE GAP EVT DATA LENGTH UPDATE REQUEST\n"); + sd_ble_gap_data_length_update(p_ble_evt->evt.gap_evt.conn_handle, NULL, NULL); + break; + #endif // (BLUETOOTH_SD == 132) || (BLUETOOTH_SD == 140) default: diff --git a/ports/nrf/drivers/bluetooth/ble_uart.c b/ports/nrf/drivers/bluetooth/ble_uart.c index b94780e26..64e3a05f9 100644 --- a/ports/nrf/drivers/bluetooth/ble_uart.c +++ b/ports/nrf/drivers/bluetooth/ble_uart.c @@ -31,6 +31,11 @@ #include "ringbuffer.h" #include "mphalport.h" #include "lib/utils/interrupt_char.h" +#include "py/runtime.h" + +#if MICROPY_PY_SYS_STDFILES +#include "py/stream.h" +#endif #if MICROPY_PY_BLE_NUS @@ -131,10 +136,38 @@ void mp_hal_stdout_tx_strn(const char *str, size_t len) { } } +void ble_uart_tx_char(char c) { + // Not connected: drop output + if (!ble_uart_enabled()) return; + + ubluepy_characteristic_obj_t * p_char = &ble_uart_char_tx; + + ble_drv_attr_s_notify(p_char->p_service->p_periph->conn_handle, + p_char->handle, + 1, + (uint8_t *)&c); +} + void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len) { - mp_hal_stdout_tx_strn(str, len); + for (const char *top = str + len; str < top; str++) { + if (*str == '\n') { + ble_uart_tx_char('\r'); + } + ble_uart_tx_char(*str); + } } +#if MICROPY_PY_SYS_STDFILES +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if ((poll_flags & MP_STREAM_POLL_RD) && ble_uart_enabled() + && !isBufferEmpty(mp_rx_ring_buffer)) { + ret |= MP_STREAM_POLL_RD; + } + return ret; +} +#endif + STATIC void gap_event_handler(mp_obj_t self_in, uint16_t event_id, uint16_t conn_handle, uint16_t length, uint8_t * data) { ubluepy_peripheral_obj_t * self = MP_OBJ_TO_PTR(self_in); @@ -212,7 +245,7 @@ void ble_uart_init0(void) { ble_uart_peripheral.conn_handle = 0xFFFF; - char device_name[] = "mpus"; + static char device_name[] = "mpus"; mp_obj_t service_list = mp_obj_new_list(0, NULL); mp_obj_list_append(service_list, MP_OBJ_FROM_PTR(&ble_uart_service)); diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 3c5d0a05d..670c88e7c 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -52,6 +52,8 @@ #include "i2c.h" #include "adc.h" #include "rtcounter.h" +#include "mphalport.h" + #if MICROPY_PY_MACHINE_HW_PWM #include "pwm.h" #endif @@ -101,6 +103,9 @@ int main(int argc, char **argv) { soft_reset: + #if MICROPY_PY_TIME_TICKS + rtc1_init_time_ticks(); + #endif led_init(); diff --git a/ports/nrf/modules/machine/rtcounter.c b/ports/nrf/modules/machine/rtcounter.c index 5fb28557d..c9f907774 100644 --- a/ports/nrf/modules/machine/rtcounter.c +++ b/ports/nrf/modules/machine/rtcounter.c @@ -153,6 +153,13 @@ STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s int rtc_id = rtc_find(args[ARG_id].u_obj); + #if MICROPY_PY_TIME_TICKS + if (rtc_id == 1) { + // time module uses RTC1, prevent using it + mp_raise_ValueError(MP_ERROR_TEXT("RTC1 reserved by time module")); + } + #endif + // const and non-const part of the RTC object. const machine_rtc_obj_t * self = &machine_rtc_obj[rtc_id]; machine_rtc_config_t *config = self->config; diff --git a/ports/nrf/modules/utime/modutime.c b/ports/nrf/modules/utime/modutime.c index 60cdbe4f3..bb2914101 100644 --- a/ports/nrf/modules/utime/modutime.c +++ b/ports/nrf/modules/utime/modutime.c @@ -42,6 +42,10 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { 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) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/nrf/mpconfigdevice_nrf51822.h b/ports/nrf/mpconfigdevice_nrf51822.h new file mode 100644 index 000000000..2f85c9f4c --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf51822.h @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) +#else +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_SYS_STDFILES (0) +#else +#define MICROPY_PY_SYS_STDFILES (1) +#endif +#endif + +#ifndef MICROPY_PY_UBINASCII +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_UBINASCII (0) +#else +#define MICROPY_PY_UBINASCII (1) +#endif +#endif diff --git a/ports/nrf/mpconfigdevice_nrf52832.h b/ports/nrf/mpconfigdevice_nrf52832.h new file mode 100644 index 000000000..2bfd047ca --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf52832.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigdevice_nrf52840.h b/ports/nrf/mpconfigdevice_nrf52840.h new file mode 100644 index 000000000..2bfd047ca --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf52840.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigdevice_nrf9160.h b/ports/nrf/mpconfigdevice_nrf9160.h new file mode 100644 index 000000000..2bfd047ca --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf9160.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 28076114f..1197df016 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -29,6 +29,18 @@ #include +#if defined(NRF51822) + #include "mpconfigdevice_nrf51822.h" +#elif defined(NRF52832) + #include "mpconfigdevice_nrf52832.h" +#elif defined(NRF52840) + #include "mpconfigdevice_nrf52840.h" +#elif defined(NRF9160) + #include "mpconfigdevice_nrf9160.h" +#else + #pragma error "Device not defined" +#endif + // options to control how MicroPython is built #ifndef MICROPY_VFS #define MICROPY_VFS (0) @@ -105,11 +117,9 @@ #define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) -#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) #define MICROPY_PY_BUILTINS_SLICE_ATTRS (0) #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_MAXSIZE (1) -#define MICROPY_PY_SYS_STDFILES (0) #define MICROPY_PY_SYS_STDIO_BUFFER (0) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (0) #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (0) @@ -117,7 +127,6 @@ #define MICROPY_PY_IO (0) #define MICROPY_PY_IO_FILEIO (0) #define MICROPY_PY_UERRNO (0) -#define MICROPY_PY_UBINASCII (0) #define MICROPY_PY_URANDOM (1) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) #define MICROPY_PY_UCTYPES (0) @@ -174,6 +183,9 @@ #define MICROPY_PY_MACHINE_RTCOUNTER (0) #endif +#ifndef MICROPY_PY_TIME_TICKS +#define MICROPY_PY_TIME_TICKS (1) +#endif #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) @@ -317,6 +329,13 @@ extern const struct _mp_obj_module_t ble_module; /* micro:bit root pointers */ \ void *async_data[2]; \ +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + __WFI(); \ + } while (0); + #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) // We need to provide a declaration/definition of alloca() diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 4469adc80..b8e4c2e4d 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -35,6 +35,121 @@ #include "nrfx_errors.h" #include "nrfx_config.h" +#if MICROPY_PY_TIME_TICKS +#include "nrfx_rtc.h" +#include "nrf_clock.h" +#endif + +#if MICROPY_PY_TIME_TICKS + +// Use RTC1 for time ticks generation (ms and us) with 32kHz tick resolution +// and overflow handling in RTC IRQ. + +#define RTC_TICK_INCREASE_MSEC (33) + +#define RTC_RESCHEDULE_CC(rtc, cc_nr, ticks) \ + do { \ + nrfx_rtc_cc_set(&rtc, cc_nr, nrfx_rtc_counter_get(&rtc) + ticks, true); \ + } while (0); + +// RTC overflow irq handling notes: +// - If has_overflowed is set it could be before or after COUNTER is read. +// If before then an adjustment must be made, if after then no adjustment is necessary. +// - The before case is when COUNTER is very small (because it just overflowed and was set to zero), +// the after case is when COUNTER is very large (because it's just about to overflow +// but we read it right before it overflows). +// - The extra check for counter is to distinguish these cases. 1<<23 because it's halfway +// between min and max values of COUNTER. +#define RTC1_GET_TICKS_ATOMIC(rtc, overflows, counter) \ + do { \ + rtc.p_reg->INTENCLR = RTC_INTENCLR_OVRFLW_Msk; \ + overflows = rtc_overflows; \ + counter = rtc.p_reg->COUNTER; \ + uint32_t has_overflowed = rtc.p_reg->EVENTS_OVRFLW; \ + if (has_overflowed && counter < (1 << 23)) { \ + overflows += 1; \ + } \ + rtc.p_reg->INTENSET = RTC_INTENSET_OVRFLW_Msk; \ + } while (0); + +nrfx_rtc_t rtc1 = NRFX_RTC_INSTANCE(1); +volatile mp_uint_t rtc_overflows = 0; + +const nrfx_rtc_config_t rtc_config_time_ticks = { + .prescaler = 0, + .reliable = 0, + .tick_latency = 0, + #ifdef NRF51 + .interrupt_priority = 1, + #else + .interrupt_priority = 3, + #endif +}; + +STATIC void rtc_irq_time(nrfx_rtc_int_type_t event) { + // irq handler for overflow + if (event == NRFX_RTC_INT_OVERFLOW) { + rtc_overflows += 1; + } + // irq handler for wakeup from WFI (~1msec) + if (event == NRFX_RTC_INT_COMPARE0) { + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + } +} + +void rtc1_init_time_ticks(void) { + // Start the low-frequency clock (if it hasn't been started already) + if (!nrf_clock_lf_is_running(NRF_CLOCK)) { + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); + } + // Uninitialize first, then set overflow IRQ and first CC event + nrfx_rtc_uninit(&rtc1); + nrfx_rtc_init(&rtc1, &rtc_config_time_ticks, rtc_irq_time); + nrfx_rtc_overflow_enable(&rtc1, true); + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + nrfx_rtc_enable(&rtc1); +} + +mp_uint_t mp_hal_ticks_ms(void) { + // Compute: (rtc_overflows << 24 + COUNTER) * 1000 / 32768 + // + // Note that COUNTER * 1000 / 32768 would overflow during calculation, so use + // the less obvious * 125 / 4096 calculation (overflow secure). + // + // Make sure not to call this function within an irq with higher prio than the + // RTC's irq. This would introduce the danger of preempting the RTC irq and + // calling mp_hal_ticks_ms() at that time would return a false result. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + return (overflows << 9) * 1000 + (counter * 125 / 4096); +} + +mp_uint_t mp_hal_ticks_us(void) { + // Compute: ticks_us = (overflows << 24 + counter) * 1000000 / 32768 + // = (overflows << 15 * 15625) + (counter * 15625 / 512) + // Since this function is likely to be called in a poll loop it must + // be fast, using an optimized 64bit mult/divide. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + // first compute counter * 15625 + uint32_t counter_lo = (counter & 0xffff) * 15625; + uint32_t counter_hi = (counter >> 16) * 15625; + // actual value is counter_hi << 16 + counter_lo + return ((overflows << 15) * 15625) + ((counter_hi << 7) + (counter_lo >> 9)); +} + +#else + +mp_uint_t mp_hal_ticks_ms(void) { + return 0; +} + +#endif + // this table converts from HAL_StatusTypeDef to POSIX errno const byte mp_hal_status_to_errno_table[4] = { [HAL_OK] = 0, @@ -70,7 +185,7 @@ int mp_hal_stdin_rx_chr(void) { if (MP_STATE_PORT(board_stdio_uart) != NULL && uart_rx_any(MP_STATE_PORT(board_stdio_uart))) { return uart_rx_char(MP_STATE_PORT(board_stdio_uart)); } - __WFI(); + MICROPY_EVENT_POLL_HOOK } return 0; @@ -93,6 +208,31 @@ void mp_hal_stdout_tx_str(const char *str) { mp_hal_stdout_tx_strn(str, strlen(str)); } +#if MICROPY_PY_TIME_TICKS + +void mp_hal_delay_us(mp_uint_t us) { + uint32_t now; + if (us == 0) { + return; + } + now = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - now < us) { + } +} + +void mp_hal_delay_ms(mp_uint_t ms) { + uint32_t now; + if (ms == 0) { + return; + } + now = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - now < ms) { + MICROPY_EVENT_POLL_HOOK + } +} + +#else + void mp_hal_delay_us(mp_uint_t us) { if (us == 0) { return; @@ -175,6 +315,7 @@ void mp_hal_delay_ms(mp_uint_t ms) { mp_hal_delay_us(999); } } +#endif #if defined(NRFX_LOG_ENABLED) && (NRFX_LOG_ENABLED == 1) diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 5614be29f..15b37b7ef 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -41,12 +41,6 @@ typedef enum HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef; -static inline uint32_t hal_tick_fake(void) { - return 0; -} - -#define mp_hal_ticks_ms hal_tick_fake // TODO: implement. Right now, return 0 always - extern const unsigned char mp_hal_status_to_errno_table[4]; NORETURN void mp_hal_raise(HAL_StatusTypeDef status); @@ -70,10 +64,15 @@ const char *nrfx_error_code_lookup(uint32_t err_code); #define mp_hal_pin_od_high(p) mp_hal_pin_high(p) #define mp_hal_pin_open_drain(p) nrf_gpio_cfg_input(p->pin, NRF_GPIO_PIN_NOPULL) +#if MICROPY_PY_TIME_TICKS +void rtc1_init_time_ticks(); +#else +mp_uint_t mp_hal_ticks_ms(void); +#define mp_hal_ticks_us() (0) +#endif // TODO: empty implementation for now. Used by machine_spi.c:69 #define mp_hal_delay_us_fast(p) -#define mp_hal_ticks_us() (0) #define mp_hal_ticks_cpu() (0) #endif diff --git a/ports/nrf/nrfx_config.h b/ports/nrf/nrfx_config.h index 19c744510..beb6b34ab 100644 --- a/ports/nrf/nrfx_config.h +++ b/ports/nrf/nrfx_config.h @@ -130,7 +130,7 @@ #define NRFX_RTC_ENABLED (MICROPY_PY_MACHINE_RTCOUNTER) #define NRFX_RTC0_ENABLED 1 #define NRFX_RTC1_ENABLED 1 -#define NRFX_RTC2_ENABLED (!NRF51) +#define NRFX_RTC2_ENABLED (!NRF51) && (!NRF9160_XXAA) #define NRFX_TIMER_ENABLED (MICROPY_PY_MACHINE_TIMER) #define NRFX_TIMER0_ENABLED 1 diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index 430740ccf..aebc5afae 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -42,10 +42,11 @@ INC += -I$(BUILD) CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Werror -std=gnu99 $(COPT) \ -ffunction-sections -fdata-sections +CFLAGS += $(CFLAGS_EXTRA) -#Debugging/Optimization +# Debugging/Optimization ifeq ($(DEBUG), 1) -CFLAGS += -g -DPENDSV_DEBUG +CFLAGS += -g COPT = -O0 else COPT += -Os -DNDEBUG diff --git a/ports/qemu-arm/startup.c b/ports/qemu-arm/startup.c index 002d5ef62..58bdf7af9 100644 --- a/ports/qemu-arm/startup.c +++ b/ports/qemu-arm/startup.c @@ -1,4 +1,5 @@ #include +#include #include #include "uart.h" @@ -73,6 +74,14 @@ __attribute__((naked)) void exit(int status) { } } +#ifndef NDEBUG +void __assert_func(const char *file, int line, const char *func, const char *expr) { + (void)func; + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + exit(1); +} +#endif + // The following are needed for tinytest #include diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 2614d4aa0..e7d2e2abc 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -191,6 +191,7 @@ LIBM_SRC_C = $(addprefix lib/libm_dbl/,\ nearbyint.c \ pow.c \ rint.c \ + round.c \ scalbn.c \ sin.c \ sinh.c \ @@ -472,6 +473,9 @@ endif ifeq ($(MICROPY_SSL_MBEDTLS),1) CFLAGS_MOD += -DMBEDTLS_CONFIG_FILE='"mbedtls/mbedtls_config.h"' SRC_MOD += mbedtls/mbedtls_port.c +# replace mbedtls' error.c by ours +SRC_MOD := $(filter-out %/mbedtls/library/error.c, $(SRC_MOD)) +LIB_SRC_C += lib/mbedtls_errors/mp_mbedtls_errors.c endif ifeq ($(MICROPY_PY_BLUETOOTH),1) diff --git a/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h b/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h index eb622cd29..71af3ce03 100644 --- a/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h +++ b/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h @@ -83,3 +83,4 @@ #define MBOOT_BOOTPIN_PULL (MP_HAL_PIN_PULL_NONE) #define MBOOT_BOOTPIN_ACTIVE (0) #define MBOOT_FSLOAD (1) +#define MBOOT_VFS_FAT (1) diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk index 416364df9..dcec788ed 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk @@ -1,9 +1,18 @@ MCU_SERIES = wb CMSIS_MCU = STM32WB55xx AF_FILE = boards/stm32wb55_af.csv -LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld STARTUP_FILE = lib/stm32lib/CMSIS/STM32WBxx/Source/Templates/gcc/startup_stm32wb55xx_cm4.o +ifeq ($(USE_MBOOT),1) +# When using Mboot all the text goes together after the bootloader +LD_FILES = boards/stm32wb55xg.ld boards/common_bl.ld +TEXT0_ADDR = 0x08004000 +else +# When not using Mboot the text goes at the start of flash +LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld +TEXT0_ADDR = 0x08000000 +endif + # MicroPython settings MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 diff --git a/ports/stm32/boards/NUCLEO_WB55/pins.csv b/ports/stm32/boards/NUCLEO_WB55/pins.csv index 49fdab0c2..d7e0babf3 100644 --- a/ports/stm32/boards/NUCLEO_WB55/pins.csv +++ b/ports/stm32/boards/NUCLEO_WB55/pins.csv @@ -6,6 +6,14 @@ ,PA5 ,PA6 ,PA7 +,PA8 +,PA9 +,PA10 +,PA11 +,PA12 +,PA13 +,PA14 +,PA15 ,PB0 ,PB1 ,PB2 @@ -26,7 +34,20 @@ ,PC1 ,PC2 ,PC3 +,PC4 +,PC5 +,PC6 +,PC10 +,PC11 +,PC12 +,PC13 +,PD0 +,PD1 +,PE4 SW,PC4 +SW1,PC4 +SW2,PD0 +SW3,PD1 LED_GREEN,PB0 LED_RED,PB1 LED_BLUE,PB5 diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore.py new file mode 100644 index 000000000..e612499a7 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore.py @@ -0,0 +1,347 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Damien P. George +# 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. + +# This script provides some helpers to allow Python code to access the IPCC +# mechanism in the WB55, and works with the memory layout configured in +# ports/stm32/rfcore.c -- i.e. it expects that rfcore_init() has been run. + +# At this stage this is useful for debugging, but can be extended to support +# FUS/WS firmware updates. +# e.g. +# ../../tools/pyboard.py --device /dev/ttyACM0 boards/NUCLEO_WB55/rfcore.py +# to print out SRAM2A, register state and FUS/WS info. + +from machine import mem8, mem16, mem32 +import time, struct, uctypes +import stm + + +class Flash: + FLASH_KEY1 = 0x45670123 + FLASH_KEY2 = 0xCDEF89AB + + def wait_not_busy(self): + while mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16: + machine.idle() + + def unlock(self): + mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY1 + mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY2 + + def lock(self): + mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK + + def erase_page(self, page): + print("erase", page) + assert 0 <= page <= 255 # 1MiB range (4k page) + self.wait_not_busy() + cr = page << 3 | 1 << 1 # PNB # PER + mem32[stm.FLASH + stm.FLASH_CR] = cr + mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT + self.wait_not_busy() + mem32[stm.FLASH + stm.FLASH_CR] = 0 + + def write(self, addr, buf): + assert len(buf) % 4 == 0 + self.wait_not_busy() + cr = 1 << 0 # PG + mem32[stm.FLASH + stm.FLASH_CR] = cr + buf_addr = uctypes.addressof(buf) + off = 0 + while off < len(buf): + mem32[addr + off] = mem32[buf_addr + off] + off += 4 + if off % 8 == 0: + self.wait_not_busy() + if off % 8: + mem32[addr + off] = 0 + self.wait_not_busy() + mem32[stm.FLASH + stm.FLASH_CR] = 0 + + +def copy_file_to_flash(filename, addr): + flash = Flash() + flash.unlock() + try: + with open(filename, "rb") as f: + buf = bytearray(4096) + while 1: + sz = f.readinto(buf) + if sz == 0: + break + print("write", hex(addr), sz) + flash.erase_page((addr - 0x08000000) // 4096) + print("done e") + flash.write(addr, buf) + print("done") + addr += 4096 + finally: + flash.lock() + + +SRAM2A_BASE = const(0x2003_0000) + +# for vendor OGF +OGF_VENDOR = const(0x3F) +OCF_FUS_GET_STATE = const(0x52) +OCF_FUS_FW_UPGRADE = const(0x54) +OCF_FUS_FW_DELETE = const(0x55) +OCF_FUS_START_WS = const(0x5A) +OCF_BLE_INIT = const(0x66) + + +@micropython.asm_thumb +def asm_sev_wfe(): + data(2, 0xBF40) # sev + data(2, 0xBF20) # wfe + + +TABLE_DEVICE_INFO = const(0) +TABLE_BLE = const(1) +TABLE_SYS = const(3) +TABLE_MEM_MANAGER = const(4) + +CHANNEL_BLE = const(1) +CHANNEL_SYS = const(2) +CHANNEL_TRACES = const(4) +CHANNEL_ACL = const(6) + +INDICATOR_HCI_COMMAND = const(0x01) +INDICATOR_HCI_EVENT = const(0x04) +INDICATOR_FUS_COMMAND = const(0x10) +INDICATOR_FUS_RESPONSE = const(0x11) +INDICATOR_FUS_EVENT = const(0x12) + +MAGIC_FUS_ACTIVE = const(0xA94656B9) + + +def get_ipccdba(): + return mem32[stm.FLASH + stm.FLASH_IPCCBR] & 0x3FFF + + +def get_ipcc_table(table): + return mem32[SRAM2A_BASE + get_ipccdba() + table * 4] + + +def get_ipcc_table_word(table, offset): + return mem32[get_ipcc_table(table) + offset * 4] & 0xFFFFFFFF + + +def get_ipcc_table_byte(table, offset): + return mem8[get_ipcc_table(table) + offset] & 0xFF + + +def sram2a_dump(num_words=64, width=8): + print("SRAM2A @%08x" % SRAM2A_BASE) + for i in range((num_words + width - 1) // width): + print(" %04x " % (i * 4 * width), end="") + for j in range(width): + print(" %08x" % (mem32[SRAM2A_BASE + (i * width + j) * 4] & 0xFFFFFFFF), end="") + print() + + +SYS_CMD_BUF = 0 # next*,prev*,type8,...; 272 bytes +SYS_SYS_QUEUE = 0 # next*,prev* + +MM_BLE_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_SYS_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_BLE_POOL = 0 # ? +MM_BLE_POOL_SIZE = 0 # ? +MM_FREE_BUF_QUEUE = 0 # next*,prev* +MM_EV_POOL = 0 # ? +MM_EV_POOL_SIZE = 0 # ? + +BLE_CMD_BUF = 0 +BLE_CS_BUF = 0 +BLE_EVT_QUEUE = 0 +BLE_HCI_ACL_DATA_BUF = 0 + + +def ipcc_init(): + global SYS_CMD_BUF, SYS_SYS_QUEUE + SYS_CMD_BUF = get_ipcc_table_word(TABLE_SYS, 0) + SYS_SYS_QUEUE = get_ipcc_table_word(TABLE_SYS, 1) + + global MM_BLE_SPARE_EVT_BUF, MM_SYS_SPARE_EVT_BUF, MM_BLE_POOL, MM_BLE_POOL_SIZE, MM_FREE_BUF_QUEUE, MM_EV_POOL, MM_EV_POOL_SIZE + MM_BLE_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 0) + MM_SYS_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 1) + MM_BLE_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 2) + MM_BLE_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 3) + MM_FREE_BUF_QUEUE = get_ipcc_table_word(TABLE_MEM_MANAGER, 4) + MM_EV_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 5) + MM_EV_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 6) + + global BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE, BLE_HCI_ACL_DATA_BUF + BLE_CMD_BUF = get_ipcc_table_word(TABLE_BLE, 0) + BLE_CS_BUF = get_ipcc_table_word(TABLE_BLE, 1) + BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) + BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) + + print("IPCC initialised") + print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) + print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE)) + + +def tl_list_init(addr): + mem32[addr] = addr # next + mem32[addr + 4] = addr # prev + + +def tl_list_append(head, n): + sram2a_dump(1024) + print("Appending 0x%08x to 0x%08x" % (head, n)) + # item->next = head + mem32[n] = head + # item->prev = head->prev + mem32[n + 4] = mem32[head + 4] + # head->prev->next = item + mem32[mem32[head + 4]] = n + # head->prev = item + mem32[head + 4] = n + + +def tl_list_unlink(n): + # next = item->next + next = mem32[n] + # prev = item->prev + prev = mem32[n + 4] + # prev->next = item->next + mem32[prev] = next + # item->next->prev = prev + mem32[next + 4] = prev + + return next + + +def tl_list_dump(head): + print( + "list(%08x, %08x, %08x):" % (head, mem32[head] & 0xFFFFFFFF, mem32[head + 4] & 0xFFFFFFFF), + end="", + ) + cur = mem32[head] + while cur != head: + print(" %08x" % (cur & 0xFFFFFFFF), end="") + cur = mem32[cur] + print() + + +def fus_active(): + return get_ipcc_table_word(TABLE_DEVICE_INFO, 0) == MAGIC_FUS_ACTIVE + + +def info(): + sfr = mem32[stm.FLASH + stm.FLASH_SFR] + srrvr = mem32[stm.FLASH + stm.FLASH_SRRVR] + + print("IPCCDBA : 0x%08x" % (get_ipccdba() & 0x3FFF)) + print("DDS : %r" % bool(sfr & (1 << 12))) + print("FSD : %r" % bool(sfr & (1 << 8))) + print("SFSA : 0x%08x" % (sfr & 0xFF)) + print("C2OPT : %r" % bool(srrvr & (1 << 31))) + print("NBRSD : %r" % bool(srrvr & (1 << 30))) + print("SNBRSA : 0x%08x" % ((srrvr >> 25) & 0x1F)) + print("BRSD : %r" % bool(srrvr & (1 << 23))) + print("SBRSA : 0x%08x" % ((srrvr >> 18) & 0x1F)) + print("SBRV : 0x%08x" % (srrvr & 0x3FFFF)) + + +def dev_info(): + def dump_version(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (%u.%u.%u.%u.%u)" + % (x, x >> 24, x >> 16 & 0xFF, x >> 8 & 0xFF, x >> 4 & 0xF, x & 0xF) + ) + + def dump_memory_size(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (SRAM2b=%uk SRAM2a=%uk flash=%uk)" + % (x, x >> 24, x >> 16 & 0xFF, (x & 0xFF) * 4) + ) + + print("Device information table @%08x:" % get_ipcc_table(TABLE_DEVICE_INFO)) + if fus_active(): + # layout when running FUS + print("FUS is active") + print("state : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 0)) + print("last FUS active state : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 5)) + print("last wireless stack state: 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 6)) + print("cur wireless stack type : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 7)) + print("safe boot version : ", end="") + dump_version(2) + print("FUS version : ", end="") + dump_version(3) + print("FUS memory size : ", end="") + dump_memory_size(4) + print("wireless stack version : ", end="") + dump_version(5) + print("wireless stack mem size : ", end="") + dump_memory_size(6) + print("wireless FW-BLE info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless FW-thread info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 8)) + print( + "UID64 : 0x%08x 0x%08x" + % ( + get_ipcc_table_word(TABLE_DEVICE_INFO, 9), + get_ipcc_table_word(TABLE_DEVICE_INFO, 10), + ) + ) + print("device ID : 0x%04x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 11)) + else: + # layout when running WS + print("WS is active") + print("safe boot version : ", end="") + dump_version(0) + print("FUS version : ", end="") + dump_version(1) + print("FUS memory size : ", end="") + dump_memory_size(2) + print("FUS info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 3)) + print("wireless stack version : ", end="") + dump_version(4) + print("wireless stack mem size : ", end="") + dump_memory_size(5) + print("wireless stack info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless reserved : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + + +def ipcc_state(): + print("IPCC:") + print(" C1CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1CR] & 0xFFFFFFFF), end="") + print(" C2CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2CR] & 0xFFFFFFFF)) + print(" C1MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1MR] & 0xFFFFFFFF), end="") + print(" C2MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2MR] & 0xFFFFFFFF)) + # these always read 0 + # print(' C1SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C1SCR] & 0xffffffff), end='') + # print(' C2SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C2SCR] & 0xffffffff)) + print(" C1TOC2SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1TOC2SR] & 0xFFFFFFFF), end="") + print(" C2TOC1SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2TOC1SR] & 0xFFFFFFFF)) + + +sram2a_dump(264) +ipcc_init() +info() +dev_info() diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index 757dacf9a..f6e130eb5 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -188,6 +188,7 @@ extern struct _spi_bdev_t spi_bdev2; #define MBOOT_USB_AUTODETECT_PORT (1) #define MBOOT_FSLOAD (1) +#define MBOOT_VFS_FAT (1) #define MBOOT_I2C_PERIPH_ID 1 #define MBOOT_I2C_SCL (pin_B8) diff --git a/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk index 119ec8f82..abe3dcd86 100644 --- a/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk @@ -17,6 +17,7 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_VFS_LFS2 = 1 # PYBD-specific frozen modules FROZEN_MANIFEST = boards/PYBD_SF2/manifest.py diff --git a/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk index ddc176e9c..bac516ab7 100644 --- a/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk @@ -14,6 +14,7 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_VFS_LFS2 = 1 # PYBD-specific frozen modules FROZEN_MANIFEST = boards/PYBD_SF2/manifest.py diff --git a/ports/stm32/boards/STM32F769DISC/board_init.c b/ports/stm32/boards/STM32F769DISC/board_init.c index 67fad407a..6bb3dfb1f 100644 --- a/ports/stm32/boards/STM32F769DISC/board_init.c +++ b/ports/stm32/boards/STM32F769DISC/board_init.c @@ -9,7 +9,6 @@ const mp_spiflash_config_t spiflash_config = { .bus_kind = MP_SPIFLASH_BUS_QSPI, .bus.u_qspi.data = NULL, .bus.u_qspi.proto = &qspi_proto, - .cache = NULL, .cache = &spi_bdev_cache, }; diff --git a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h index 8dbac2d01..e1fe93bb0 100644 --- a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h @@ -41,7 +41,7 @@ extern struct _spi_bdev_t spi_bdev; #if !USE_QSPI_XIP #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ - (op) == BDEV_IOCTL_NUM_BLOCKS ? (64 * 1024 * 1024 / FLASH_BLOCK_SIZE) : \ + (op) == BDEV_IOCTL_NUM_BLOCKS ? ((1 << MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) / 8 / FLASH_BLOCK_SIZE) : \ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \ spi_bdev_ioctl(&spi_bdev, (op), (arg)) \ ) diff --git a/ports/stm32/boards/stm32wb55xg.ld b/ports/stm32/boards/stm32wb55xg.ld index 77596d775..dbeccc189 100644 --- a/ports/stm32/boards/stm32wb55xg.ld +++ b/ports/stm32/boards/stm32wb55xg.ld @@ -6,6 +6,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* sectors 0-127 */ + FLASH_APP (rx) : ORIGIN = 0x08004000, LENGTH = 496K /* sectors 4-127 */ FLASH_FS (r) : ORIGIN = 0x08080000, LENGTH = 256K /* sectors 128-191 */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K /* SRAM1 */ RAM2A (xrw) : ORIGIN = 0x20030020, LENGTH = 8K /* SRAM2A */ diff --git a/ports/stm32/factoryreset.c b/ports/stm32/factoryreset.c index 04591101e..725ecd12a 100644 --- a/ports/stm32/factoryreset.c +++ b/ports/stm32/factoryreset.c @@ -34,6 +34,8 @@ #if MICROPY_HW_ENABLE_STORAGE +#if MICROPY_VFS_FAT + static const char fresh_boot_py[] = "# boot.py -- run on boot-up\r\n" "# can run arbitrary Python, but best to keep it minimal\r\n" @@ -128,4 +130,13 @@ MP_WEAK int factory_reset_create_filesystem(void) { return 0; // success } +#else + +// If FAT is not enabled then it's up to the board to create a fresh filesystem. +MP_WEAK int factory_reset_create_filesystem(void) { + return 0; // success +} + +#endif + #endif // MICROPY_HW_ENABLE_STORAGE diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index b3b1da998..2892572f4 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -200,26 +200,38 @@ void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, uint8_t *data, uint32_t timeout_ms) { volatile uint32_t *rxf, *rxa; + uint32_t fl; + if (fifo == FDCAN_RX_FIFO0) { rxf = &can->Instance->RXF0S; rxa = &can->Instance->RXF0A; + fl = FDCAN_RXF0S_F0FL; } else { rxf = &can->Instance->RXF1S; rxa = &can->Instance->RXF1A; + fl = FDCAN_RXF1S_F1FL; } // Wait for a message to become available, with timeout uint32_t start = HAL_GetTick(); - while ((*rxf & 7) == 0) { - MICROPY_EVENT_POLL_HOOK - if (HAL_GetTick() - start >= timeout_ms) { - return -MP_ETIMEDOUT; + while ((*rxf & fl) == 0) { + if (timeout_ms != HAL_MAX_DELAY) { + if (HAL_GetTick() - start >= timeout_ms) { + return -MP_ETIMEDOUT; + } } + MICROPY_EVENT_POLL_HOOK } // Get pointer to incoming message - uint32_t index = (can->Instance->RXF0S & FDCAN_RXF0S_F0GI) >> 8; - uint32_t *address = (uint32_t *)(can->msgRam.RxFIFO0SA + (index * can->Init.RxFifo0ElmtSize * 4)); + uint32_t index, *address; + if (fifo == FDCAN_RX_FIFO0) { + index = (*rxf & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; + address = (uint32_t *)(can->msgRam.RxFIFO0SA + (index * can->Init.RxFifo0ElmtSize * 4)); + } else { + index = (*rxf & FDCAN_RXF1S_F1GI) >> FDCAN_RXF1S_F1GI_Pos; + address = (uint32_t *)(can->msgRam.RxFIFO1SA + (index * can->Init.RxFifo1ElmtSize * 4)); + } // Parse header of message hdr->IdType = *address & FDCAN_ELEMENT_MASK_XTD; diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c index 99c95f7d9..499129a6f 100644 --- a/ports/stm32/flash.c +++ b/ports/stm32/flash.c @@ -26,6 +26,7 @@ #include "py/mpconfig.h" #include "py/misc.h" +#include "py/mphal.h" #include "flash.h" typedef struct { @@ -69,10 +70,15 @@ static const flash_layout_t flash_layout[] = { { 0x08020000, 0x20000, 3 }, }; #else +// This is for dual-bank mode disabled static const flash_layout_t flash_layout[] = { { 0x08000000, 0x08000, 4 }, { 0x08020000, 0x20000, 1 }, + #if FLASH_SECTOR_TOTAL == 8 { 0x08040000, 0x40000, 3 }, + #else + { 0x08040000, 0x40000, 7 }, + #endif }; #endif @@ -138,7 +144,14 @@ static uint32_t get_page(uint32_t addr) { #endif -uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size) { +bool flash_is_valid_addr(uint32_t addr) { + uint8_t last = MP_ARRAY_SIZE(flash_layout) - 1; + uint32_t end_of_flash = flash_layout[last].base_address + + flash_layout[last].sector_count * flash_layout[last].sector_size; + return flash_layout[0].base_address <= addr && addr < end_of_flash; +} + +int32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size) { if (addr >= flash_layout[0].base_address) { uint32_t sector_index = 0; for (int i = 0; i < MP_ARRAY_SIZE(flash_layout); ++i) { @@ -159,20 +172,21 @@ uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *si } } } - return 0; + return -1; } -void flash_erase(uint32_t flash_dest, uint32_t num_word32) { +int flash_erase(uint32_t flash_dest, uint32_t num_word32) { // check there is something to write if (num_word32 == 0) { - return; + return 0; } - // unlock + // Unlock the flash for erase. HAL_FLASH_Unlock(); - FLASH_EraseInitTypeDef EraseInitStruct; + // Clear pending flags (if any) and set up EraseInitStruct. + FLASH_EraseInitTypeDef EraseInitStruct; #if defined(STM32F0) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR); EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; @@ -190,17 +204,14 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { EraseInitStruct.NbPages = (4 * num_word32 + FLASH_PAGE_SIZE - 4) / FLASH_PAGE_SIZE; #elif defined(STM32L4) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); - - // erase the sector(s) // The sector returned by flash_get_sector_info can not be used // as the flash has on each bank 0/1 pages 0..255 EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; EraseInitStruct.Banks = get_bank(flash_dest); EraseInitStruct.Page = get_page(flash_dest); EraseInitStruct.NbPages = get_page(flash_dest + 4 * num_word32 - 1) - EraseInitStruct.Page + 1; - ; #else - // Clear pending flags (if any) + #if defined(STM32H7) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS_BANK1 | FLASH_FLAG_ALL_ERRORS_BANK2); #else @@ -208,7 +219,6 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); #endif - // erase the sector(s) EraseInitStruct.TypeErase = TYPEERASE_SECTORS; EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3; // voltage range needs to be 2.7V to 3.6V #if defined(STM32H7) @@ -216,14 +226,17 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { #endif EraseInitStruct.Sector = flash_get_sector_info(flash_dest, NULL, NULL); EraseInitStruct.NbSectors = flash_get_sector_info(flash_dest + 4 * num_word32 - 1, NULL, NULL) - EraseInitStruct.Sector + 1; + #endif + // Erase the sectors. uint32_t SectorError = 0; - if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) { - // error occurred during sector erase - HAL_FLASH_Lock(); // lock the flash - return; - } + HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError); + + // Lock the flash after erase. + HAL_FLASH_Lock(); + + return mp_hal_status_to_neg_errno(status); } /* @@ -255,16 +268,21 @@ void flash_erase_it(uint32_t flash_dest, uint32_t num_word32) { } */ -void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { +int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { + // Unlock the flash for write. + HAL_FLASH_Unlock(); + + HAL_StatusTypeDef status = HAL_OK; + #if defined(STM32L4) || defined(STM32WB) // program the flash uint64 by uint64 for (int i = 0; i < num_word32 / 2; i++) { uint64_t val = *(uint64_t *)src; - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); + if (status != HAL_OK) { + num_word32 = 0; // don't write any odd word after this loop + break; } flash_dest += 8; src += 2; @@ -272,21 +290,16 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) if ((num_word32 & 0x01) == 1) { uint64_t val = *(uint64_t *)flash_dest; val = (val & 0xffffffff00000000uL) | (*src); - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; - } + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); } #elif defined(STM32H7) // program the flash 256 bits at a time for (int i = 0; i < num_word32 / 8; i++) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, flash_dest, (uint64_t)(uint32_t)src) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, flash_dest, (uint64_t)(uint32_t)src); + if (status != HAL_OK) { + break; } flash_dest += 32; src += 8; @@ -296,10 +309,9 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) // program the flash word by word for (int i = 0; i < num_word32; i++) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flash_dest, *src) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flash_dest, *src); + if (status != HAL_OK) { + break; } flash_dest += 4; src += 1; @@ -307,8 +319,10 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) #endif - // lock the flash + // Lock the flash after write. HAL_FLASH_Lock(); + + return mp_hal_status_to_neg_errno(status); } /* diff --git a/ports/stm32/flash.h b/ports/stm32/flash.h index b9edf6106..ecda923db 100644 --- a/ports/stm32/flash.h +++ b/ports/stm32/flash.h @@ -26,8 +26,9 @@ #ifndef MICROPY_INCLUDED_STM32_FLASH_H #define MICROPY_INCLUDED_STM32_FLASH_H -uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size); -void flash_erase(uint32_t flash_dest, uint32_t num_word32); -void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32); +bool flash_is_valid_addr(uint32_t addr); +int32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size); +int flash_erase(uint32_t flash_dest, uint32_t num_word32); +int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32); #endif // MICROPY_INCLUDED_STM32_FLASH_H diff --git a/ports/stm32/flashbdev.c b/ports/stm32/flashbdev.c index e105bd353..4153a713c 100644 --- a/ports/stm32/flashbdev.c +++ b/ports/stm32/flashbdev.c @@ -181,7 +181,7 @@ int32_t flash_bdev_ioctl(uint32_t op, uint32_t arg) { static uint8_t *flash_cache_get_addr_for_write(uint32_t flash_addr) { uint32_t flash_sector_start; uint32_t flash_sector_size; - uint32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); + int32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); if (flash_sector_size > FLASH_SECTOR_SIZE_MAX) { flash_sector_size = FLASH_SECTOR_SIZE_MAX; } @@ -201,7 +201,7 @@ static uint8_t *flash_cache_get_addr_for_write(uint32_t flash_addr) { static uint8_t *flash_cache_get_addr_for_read(uint32_t flash_addr) { uint32_t flash_sector_start; uint32_t flash_sector_size; - uint32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); + int32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); if (flash_cache_sector_id == flash_sector_id) { // in cache, copy from there return (uint8_t *)CACHE_MEM_START_ADDR + flash_addr - flash_sector_start; diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index cacc00e0c..a575c5308 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -42,25 +42,25 @@ void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { // Read of SR1, SR2 needed to clear ADDR bit sr1 = i2c->SR1; uint32_t sr2 = i2c->SR2; - i2c_slave_process_addr_match((sr2 >> I2C_SR2_TRA_Pos) & 1); + i2c_slave_process_addr_match(i2c, (sr2 >> I2C_SR2_TRA_Pos) & 1); } if (sr1 & I2C_SR1_TXE) { - i2c->DR = i2c_slave_process_tx_byte(); + i2c->DR = i2c_slave_process_tx_byte(i2c); } if (sr1 & I2C_SR1_RXNE) { - i2c_slave_process_rx_byte(i2c->DR); + i2c_slave_process_rx_byte(i2c, i2c->DR); } if (sr1 & I2C_SR1_STOPF) { // STOPF only set at end of RX mode (in TX mode AF is set on NACK) // Read of SR1, write CR1 needed to clear STOPF bit sr1 = i2c->SR1; i2c->CR1 &= ~I2C_CR1_ACK; - i2c_slave_process_rx_end(); + i2c_slave_process_rx_end(i2c); i2c->CR1 |= I2C_CR1_ACK; } } -#elif defined(STM32F7) || defined(STM32H7) +#elif defined(STM32F7) || defined(STM32H7) || defined(STM32WB) void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { i2c->CR1 = I2C_CR1_STOPIE | I2C_CR1_ADDRIE | I2C_CR1_RXIE | I2C_CR1_TXIE; @@ -77,22 +77,22 @@ void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { // Set TXE so that TXDR is flushed and ready for the first byte i2c->ISR = I2C_ISR_TXE; i2c->ICR = I2C_ICR_ADDRCF; - i2c_slave_process_addr_match(0); + i2c_slave_process_addr_match(i2c, (i2c->ISR >> I2C_ISR_DIR_Pos) & 1); } if (isr & I2C_ISR_TXIS) { - i2c->TXDR = i2c_slave_process_tx_byte(); + i2c->TXDR = i2c_slave_process_tx_byte(i2c); } if (isr & I2C_ISR_RXNE) { - i2c_slave_process_rx_byte(i2c->RXDR); + i2c_slave_process_rx_byte(i2c, i2c->RXDR); } if (isr & I2C_ISR_STOPF) { // STOPF only set for STOP condition, not a repeated START i2c->ICR = I2C_ICR_STOPCF; i2c->OAR1 &= ~I2C_OAR1_OA1EN; if (i2c->ISR & I2C_ISR_DIR) { - // i2c_slave_process_tx_end(); + i2c_slave_process_tx_end(i2c); } else { - i2c_slave_process_rx_end(); + i2c_slave_process_rx_end(i2c); } i2c->OAR1 |= I2C_OAR1_OA1EN; } diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index 55882acd8..4a51bf378 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -28,6 +28,11 @@ #include STM32_HAL_H +#if !defined(I2C2_BASE) +// This MCU doesn't have I2C2_BASE, define it so that the i2c_idx calculation works. +#define I2C2_BASE (I2C1_BASE + ((I2C3_BASE - I2C1_BASE) / 2)) +#endif + typedef I2C_TypeDef i2c_slave_t; void i2c_slave_init_helper(i2c_slave_t *i2c, int addr); @@ -42,6 +47,10 @@ static inline void i2c_slave_init(i2c_slave_t *i2c, int irqn, int irq_pri, int a RCC->APB1LENR |= 1 << (RCC_APB1LENR_I2C1EN_Pos + i2c_idx); volatile uint32_t tmp = RCC->APB1LENR; // Delay after enabling clock (void)tmp; + #elif defined(STM32WB) + RCC->APB1ENR1 |= 1 << (RCC_APB1ENR1_I2C1EN_Pos + i2c_idx); + volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock + (void)tmp; #endif i2c_slave_init_helper(i2c, addr); @@ -58,9 +67,10 @@ static inline void i2c_slave_shutdown(i2c_slave_t *i2c, int irqn) { void i2c_slave_ev_irq_handler(i2c_slave_t *i2c); // These should be provided externally -int i2c_slave_process_addr_match(int rw); -int i2c_slave_process_rx_byte(uint8_t val); -void i2c_slave_process_rx_end(void); -uint8_t i2c_slave_process_tx_byte(void); +int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw); +int i2c_slave_process_rx_byte(i2c_slave_t *i2c, uint8_t val); +void i2c_slave_process_rx_end(i2c_slave_t *i2c); +uint8_t i2c_slave_process_tx_byte(i2c_slave_t *i2c); +void i2c_slave_process_tx_end(i2c_slave_t *i2c); #endif // MICROPY_INCLUDED_STM32_I2CSLAVE_H diff --git a/ports/stm32/irq.c b/ports/stm32/irq.c index 010089973..fdaf2385c 100644 --- a/ports/stm32/irq.c +++ b/ports/stm32/irq.c @@ -27,44 +27,28 @@ #include "py/obj.h" #include "py/mphal.h" #include "irq.h" - -/// \moduleref pyb +#include "modmachine.h" #if IRQ_ENABLE_STATS uint32_t irq_stats[IRQ_STATS_MAX] = {0}; #endif -/// \function wfi() -/// Wait for an interrupt. -/// This executies a `wfi` instruction which reduces power consumption -/// of the MCU until an interrupt occurs, at which point execution continues. -STATIC mp_obj_t pyb_wfi(void) { - __WFI(); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(pyb_wfi_obj, pyb_wfi); - -/// \function disable_irq() -/// Disable interrupt requests. -/// Returns the previous IRQ state: `False`/`True` for disabled/enabled IRQs -/// respectively. This return value can be passed to enable_irq to restore -/// the IRQ to its original state. -STATIC mp_obj_t pyb_disable_irq(void) { +// disable_irq() +// Disable interrupt requests. +// Returns the previous IRQ state which can be passed to enable_irq. +STATIC mp_obj_t machine_disable_irq(void) { return mp_obj_new_bool(disable_irq() == IRQ_STATE_ENABLED); } -MP_DEFINE_CONST_FUN_OBJ_0(pyb_disable_irq_obj, pyb_disable_irq); +MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); -/// \function enable_irq(state=True) -/// Enable interrupt requests. -/// If `state` is `True` (the default value) then IRQs are enabled. -/// If `state` is `False` then IRQs are disabled. The most common use of -/// this function is to pass it the value returned by `disable_irq` to -/// exit a critical section. -STATIC mp_obj_t pyb_enable_irq(uint n_args, const mp_obj_t *arg) { +// enable_irq(state=True) +// Enable interrupt requests, based on the argument, which is usually the +// value returned by a previous call to disable_irq. +STATIC mp_obj_t machine_enable_irq(uint n_args, const mp_obj_t *arg) { enable_irq((n_args == 0 || mp_obj_is_true(arg[0])) ? IRQ_STATE_ENABLED : IRQ_STATE_DISABLED); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_enable_irq_obj, 0, 1, pyb_enable_irq); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj, 0, 1, machine_enable_irq); #if IRQ_ENABLE_STATS // return a memoryview of the irq statistics array diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index 4b1251666..6c10f5e1b 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -52,7 +52,7 @@ extern uint32_t irq_stats[IRQ_STATS_MAX]; #define IRQ_EXIT(irq) #endif -static inline mp_uint_t query_irq(void) { +static inline uint32_t query_irq(void) { return __get_PRIMASK(); } @@ -92,11 +92,6 @@ static inline void restore_irq_pri(uint32_t state) { #endif -MP_DECLARE_CONST_FUN_OBJ_0(pyb_wfi_obj); -MP_DECLARE_CONST_FUN_OBJ_0(pyb_disable_irq_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_enable_irq_obj); -MP_DECLARE_CONST_FUN_OBJ_0(pyb_irq_stats_obj); - // IRQ priority definitions. // // Lower number implies higher interrupt priority. diff --git a/ports/stm32/mbedtls/mbedtls_config.h b/ports/stm32/mbedtls/mbedtls_config.h index 338c8b354..56fbbf3aa 100644 --- a/ports/stm32/mbedtls/mbedtls_config.h +++ b/ports/stm32/mbedtls/mbedtls_config.h @@ -67,6 +67,7 @@ #define MBEDTLS_CTR_DRBG_C //#define MBEDTLS_ECP_C #define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C #define MBEDTLS_MD_C #define MBEDTLS_MD5_C #define MBEDTLS_OID_C diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 43aae2a67..c901dfb33 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -34,6 +34,7 @@ STFLASH ?= st-flash OPENOCD ?= openocd OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg STARTUP_FILE ?= lib/stm32lib/CMSIS/STM32$(MCU_SERIES_UPPER)xx/Source/Templates/gcc/startup_$(CMSIS_MCU_LOWER).o +SYSTEM_FILE ?= lib/stm32lib/CMSIS/STM32$(MCU_SERIES_UPPER)xx/Source/Templates/system_stm32$(MCU_SERIES)xx.o CROSS_COMPILE ?= arm-none-eabi- @@ -54,6 +55,11 @@ CFLAGS_MCU_f4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 CFLAGS_MCU_f7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7 CFLAGS_MCU_h7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7 CFLAGS_MCU_l4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 +CFLAGS_MCU_wb = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 + +# 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 CFLAGS = $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Werror -std=gnu99 -nostdlib $(CFLAGS_MOD) $(CFLAGS_EXTRA) CFLAGS += -D$(CMSIS_MCU) @@ -64,6 +70,8 @@ CFLAGS += -DSTM32_HAL_H='' CFLAGS += -DBOARD_$(BOARD) CFLAGS += -DAPPLICATION_ADDR=$(TEXT0_ADDR) CFLAGS += -DFFCONF_H=\"ports/stm32/mboot/ffconf.h\" +CFLAGS += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT +CFLAGS += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT CFLAGS += -DBUILDING_MBOOT=1 CFLAGS += -DBOOTLOADER_DFU_USB_VID=$(BOOTLOADER_DFU_USB_VID) -DBOOTLOADER_DFU_USB_PID=$(BOOTLOADER_DFU_USB_PID) @@ -82,8 +90,13 @@ else COPT += -Os -DNDEBUG endif +$(BUILD)/lib/libc/string0.o: CFLAGS += $(CFLAGS_BUILTIN) LIB_SRC_C = \ lib/libc/string0.c \ + lib/littlefs/lfs1.c \ + lib/littlefs/lfs1_util.c \ + lib/littlefs/lfs2.c \ + lib/littlefs/lfs2_util.c \ lib/oofatfs/ff.c \ lib/oofatfs/ffunicode.c \ extmod/uzlib/crc32.c \ @@ -95,19 +108,24 @@ SRC_C = \ main.c \ elem.c \ fsload.c \ - diskio.c \ + gzstream.c \ + vfs_fat.c \ + vfs_lfs.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ + ports/stm32/flash.c \ + ports/stm32/flashbdev.c \ ports/stm32/i2cslave.c \ + ports/stm32/powerctrlboot.c \ ports/stm32/qspi.c \ - ports/stm32/flashbdev.c \ ports/stm32/spibdev.c \ ports/stm32/usbd_conf.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O = \ $(STARTUP_FILE) \ + $(SYSTEM_FILE) \ ports/stm32/resethandler.o \ $(BUILD)/$(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_ll_usb.o: CFLAGS += -Wno-attributes diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 52bbf5567..d8aa6d456 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -2,11 +2,11 @@ Mboot - MicroPython boot loader =============================== Mboot is a custom bootloader for STM32 MCUs, and currently supports the -STM32F4xx and STM32F7xx families. It can provide a standard USB DFU interface -on either the FS or HS peripherals, as well as a sophisticated, custom I2C +STM32F4xx, STM32F7xx and STM32WBxx families. It can provide a standard USB DFU +interface on either the FS or HS peripherals, as well as a sophisticated, custom I2C interface. It can also load and program firmware in .dfu.gz format from a -filesystem. It can fit in 16k of flash space, but all features enabled requires -32k. +filesystem, either FAT, littlefs 1 or littlfs 2. +It can fit in 16k of flash space, but all features enabled requires 32k. How to use ---------- @@ -63,6 +63,15 @@ How to use #define MBOOT_FSLOAD (1) + and then enable one or more of the following depending on what filesystem + support is required in Mboot (note that the FAT driver is read-only and + quite compact, but littlefs supports both read and write so is rather + large): + + #define MBOOT_VFS_FAT (1) + #define MBOOT_VFS_LFS1 (1) + #define MBOOT_VFS_LFS2 (1) + 2. Build the board's main application firmware as usual. 3. Build mboot via: @@ -133,10 +142,18 @@ are located and what filename to program. The elements to use are: `u32` means unsigned 32-bit little-endian integer. The firmware to load must be a gzip'd DfuSe file (.dfu.gz) and stored within a -FAT formatted partition. +FAT or littlefs formatted partition. The provided fwupdate.py script contains helper functions to call into Mboot -with the correct data, and also to update Mboot itself. +with the correct data, and also to update Mboot itself. For example on PYBD +the following will update the main MicroPython firmware from the file +firmware.dfu.gz stored on the default FAT filesystem: + + import fwupdate + fwupdate.update_mpy('firmware.dfu.gz', 0x80000000, 2 * 1024 * 1024) + +The 0x80000000 value is the address understood by Mboot as the location of +the external SPI flash, configured via `MBOOT_SPIFLASH_ADDR`. Example: Mboot on PYBv1.x ------------------------- diff --git a/ports/stm32/mboot/dfu.h b/ports/stm32/mboot/dfu.h index e826e217f..a1d4d10d0 100644 --- a/ports/stm32/mboot/dfu.h +++ b/ports/stm32/mboot/dfu.h @@ -67,6 +67,12 @@ typedef enum { DFU_CMD_DNLOAD = 8, } dfu_cmd_t; +enum { + DFU_CMD_DNLOAD_SET_ADDRESS = 0x21, + DFU_CMD_DNLOAD_ERASE = 0x41, + DFU_CMD_DNLOAD_READ_UNPROTECT = 0x92, +}; + // Error status flags typedef enum { DFU_STATUS_OK = 0x00, // No error condition is present. diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index 64eb2a3a5..1e1ad7a04 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.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 @@ -27,81 +27,22 @@ #include #include "py/mphal.h" -#include "lib/oofatfs/ff.h" -#include "extmod/uzlib/uzlib.h" #include "mboot.h" +#include "vfs.h" #if MBOOT_FSLOAD -#define DICT_SIZE (1 << 15) - -typedef struct _gz_stream_t { - FIL fp; - TINF_DATA tinf; - uint8_t buf[512]; - uint8_t dict[DICT_SIZE]; -} gz_stream_t; - -static gz_stream_t gz_stream SECTION_NOZERO_BSS; - -static int gz_stream_read_src(TINF_DATA *tinf) { - UINT n; - FRESULT res = f_read(&gz_stream.fp, gz_stream.buf, sizeof(gz_stream.buf), &n); - if (res != FR_OK) { - return -1; - } - if (n == 0) { - return -1; - } - tinf->source = gz_stream.buf + 1; - tinf->source_limit = gz_stream.buf + n; - return gz_stream.buf[0]; -} - -static int gz_stream_open(FATFS *fatfs, const char *filename) { - FRESULT res = f_open(fatfs, &gz_stream.fp, filename, FA_READ); - if (res != FR_OK) { - return -1; - } - memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); - gz_stream.tinf.readSource = gz_stream_read_src; - - int st = uzlib_gzip_parse_header(&gz_stream.tinf); - if (st != TINF_OK) { - f_close(&gz_stream.fp); - return -1; - } - - uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE); - - return 0; -} - -static int gz_stream_read(size_t len, uint8_t *buf) { - gz_stream.tinf.dest = buf; - gz_stream.tinf.dest_limit = buf + len; - int st = uzlib_uncompress_chksum(&gz_stream.tinf); - if (st == TINF_DONE) { - return 0; - } - if (st < 0) { - return st; - } - return gz_stream.tinf.dest - buf; -} - -static int fsload_program_file(FATFS *fatfs, const char *filename, bool write_to_flash) { - int res = gz_stream_open(fatfs, filename); - if (res != 0) { - return res; - } +#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) +#error Must enable at least one VFS component +#endif +static int fsload_program_file(bool write_to_flash) { // Parse DFU uint8_t buf[512]; size_t file_offset; // Read file header, <5sBIB - res = gz_stream_read(11, buf); + int res = gz_stream_read(11, buf); if (res != 11) { return -1; } @@ -204,46 +145,23 @@ static int fsload_program_file(FATFS *fatfs, const char *filename, bool write_to return 0; } -static int fsload_process_fatfs(uint32_t base_addr, uint32_t byte_len, size_t fname_len, const char *fname) { - fsload_bdev_t bdev = {base_addr, byte_len}; - FATFS fatfs; - fatfs.drv = &bdev; - FRESULT res = f_mount(&fatfs); - if (res != FR_OK) { - return -1; - } - - FF_DIR dp; - res = f_opendir(&fatfs, &dp, "/"); - if (res != FR_OK) { - return -1; - } - - // Search for firmware file with correct name - int r; - for (;;) { - FILINFO fno; - res = f_readdir(&dp, &fno); - char *fn = fno.fname; - if (res != FR_OK || fn[0] == 0) { - // Finished listing dir, no firmware found - r = -1; - break; - } - if (memcmp(fn, fname, fname_len) == 0 && fn[fname_len] == '\0') { - // Found firmware - led_state_all(2); - r = fsload_program_file(&fatfs, fn, false); - if (r == 0) { - // Firmware is valid, program it - led_state_all(4); - r = fsload_program_file(&fatfs, fn, true); +static int fsload_validate_and_program_file(void *stream, const stream_methods_t *meth, const char *fname) { + // First pass verifies the file, second pass programs it + for (unsigned int pass = 0; pass <= 1; ++pass) { + led_state_all(pass == 0 ? 2 : 4); + int res = meth->open(stream, fname); + if (res == 0) { + res = gz_stream_init(stream, meth->read); + if (res == 0) { + res = fsload_program_file(pass == 0 ? false : true); } - break; + } + meth->close(stream); + if (res != 0) { + return res; } } - - return r; + return 0; } int fsload_process(void) { @@ -252,9 +170,12 @@ int fsload_process(void) { return -1; } + // Get mount point id and create null-terminated filename uint8_t mount_point = elem[0]; uint8_t fname_len = elem[-1] - 1; - const char *fname = (const char*)&elem[1]; + char fname[256]; + memcpy(fname, &elem[1], fname_len); + fname[fname_len] = '\0'; elem = ELEM_DATA_START; for (;;) { @@ -266,23 +187,58 @@ int fsload_process(void) { if (elem[0] == mount_point) { uint32_t base_addr = get_le32(&elem[2]); uint32_t byte_len = get_le32(&elem[6]); + int ret; + union { + #if MBOOT_VFS_FAT + vfs_fat_context_t fat; + #endif + #if MBOOT_VFS_LFS1 + vfs_lfs1_context_t lfs1; + #endif + #if MBOOT_VFS_LFS2 + vfs_lfs2_context_t lfs2; + #endif + } ctx; + const stream_methods_t *methods; + #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { - int ret = fsload_process_fatfs(base_addr, byte_len, fname_len, fname); - // Flash LEDs based on success/failure of update - for (int i = 0; i < 4; ++i) { - if (ret == 0) { - led_state_all(7); - } else { - led_state_all(1); - } - mp_hal_delay_ms(100); - led_state_all(0); - mp_hal_delay_ms(100); + ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); + methods = &vfs_fat_stream_methods; + } else + #endif + #if MBOOT_VFS_LFS1 + if (elem[1] == ELEM_MOUNT_LFS1) { + ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len); + methods = &vfs_lfs1_stream_methods; + } else + #endif + #if MBOOT_VFS_LFS2 + if (elem[1] == ELEM_MOUNT_LFS2) { + ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len); + methods = &vfs_lfs2_stream_methods; + } else + #endif + { + // Unknown filesystem type + return -1; + } + + if (ret == 0) { + ret = fsload_validate_and_program_file(&ctx, methods, fname); + } + + // Flash LEDs based on success/failure of update + for (int i = 0; i < 4; ++i) { + if (ret == 0) { + led_state_all(7); + } else { + led_state_all(1); } - return ret; + mp_hal_delay_ms(100); + led_state_all(0); + mp_hal_delay_ms(100); } - // Unknown filesystem type - return -1; + return ret; } elem += elem[-1]; } diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index b44ed772c..dab5fa663 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -1,9 +1,13 @@ # Update Mboot or MicroPython from a .dfu.gz file on the board's filesystem -# MIT license; Copyright (c) 2019 Damien P. George +# MIT license; Copyright (c) 2019-2020 Damien P. George import struct, time import uzlib, machine, stm +# Constants to be used with update_mpy +VFS_FAT = 1 +VFS_LFS1 = 2 +VFS_LFS2 = 3 FLASH_KEY1 = 0x45670123 FLASH_KEY2 = 0xCDEF89AB @@ -152,7 +156,7 @@ def update_mboot(filename): print("Programming finished, can now reset or turn off.") -def update_mpy(filename, fs_base, fs_len): +def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT): # Check firmware is of .dfu.gz type try: with open(filename, "rb") as f: @@ -166,11 +170,8 @@ def update_mpy(filename, fs_base, fs_len): ELEM_TYPE_END = 1 ELEM_TYPE_MOUNT = 2 ELEM_TYPE_FSLOAD = 3 - ELEM_MOUNT_FAT = 1 mount_point = 1 - mount = struct.pack( - " + +#include "py/mphal.h" +#include "extmod/uzlib/uzlib.h" +#include "gzstream.h" +#include "mboot.h" + +#if MBOOT_FSLOAD + +#define DICT_SIZE (1 << 15) + +typedef struct _gz_stream_t { + void *stream_data; + stream_read_t stream_read; + TINF_DATA tinf; + uint8_t buf[512]; + uint8_t dict[DICT_SIZE]; +} gz_stream_t; + +static gz_stream_t gz_stream SECTION_NOZERO_BSS; + +static int gz_stream_read_src(TINF_DATA *tinf) { + int n = gz_stream.stream_read(gz_stream.stream_data, gz_stream.buf, sizeof(gz_stream.buf)); + if (n < 0) { + // Stream error + return -1; + } + if (n == 0) { + // No data / EOF + return -1; + } + + tinf->source = gz_stream.buf + 1; + tinf->source_limit = gz_stream.buf + n; + return gz_stream.buf[0]; +} + +int gz_stream_init(void *stream_data, stream_read_t stream_read) { + gz_stream.stream_data = stream_data; + gz_stream.stream_read = stream_read; + + memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); + gz_stream.tinf.readSource = gz_stream_read_src; + + int st = uzlib_gzip_parse_header(&gz_stream.tinf); + if (st != TINF_OK) { + return -1; + } + + uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE); + + return 0; +} + +int gz_stream_read(size_t len, uint8_t *buf) { + gz_stream.tinf.dest = buf; + gz_stream.tinf.dest_limit = buf + len; + int st = uzlib_uncompress_chksum(&gz_stream.tinf); + if (st == TINF_DONE) { + return 0; + } + if (st < 0) { + return st; + } + return gz_stream.tinf.dest - buf; +} + +#endif // MBOOT_FSLOAD diff --git a/ports/stm32/mboot/gzstream.h b/ports/stm32/mboot/gzstream.h new file mode 100644 index 000000000..ec11ba79b --- /dev/null +++ b/ports/stm32/mboot/gzstream.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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 + * 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_STM32_MBOOT_GZSTREAM_H +#define MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H + +#include +#include + +typedef int (*stream_open_t)(void *stream, const char *fname); +typedef void (*stream_close_t)(void *stream); +typedef int (*stream_read_t)(void *stream, uint8_t *buf, size_t len); + +typedef struct _stream_methods_t { + stream_open_t open; + stream_close_t close; + stream_read_t read; +} stream_methods_t; + +int gz_stream_init(void *stream_data, stream_read_t stream_read); +int gz_stream_read(size_t len, uint8_t *buf); + +#endif // MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 4ae575f3d..99187e3ef 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -31,30 +31,36 @@ #include "extmod/crypto-algorithms/sha256.c" #include "usbd_core.h" #include "storage.h" +#include "flash.h" #include "i2cslave.h" +#include "irq.h" #include "mboot.h" +#include "powerctrl.h" #include "dfu.h" -// Using polling is about 10% faster than not using it (and using IRQ instead) -// This DFU code with polling runs in about 70% of the time of the ST bootloader -#define USE_USB_POLLING (1) +// This option selects whether to use explicit polling or IRQs for USB events. +// In some test cases polling mode can run slightly faster, but it uses more power. +// Polling mode will also cause failures with the mass-erase command because USB +// events will not be serviced for the duration of the mass erase. +// With STM32WB MCUs only non-polling/IRQ mode is supported. +#define USE_USB_POLLING (0) // Using cache probably won't make it faster because we run at a low frequency, and best // to keep the MCU config as minimal as possible. #define USE_CACHE (0) // IRQ priorities (encoded values suitable for NVIC_SetPriority) -#define IRQ_PRI_SYSTICK (NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 0, 0)) +// Most values are defined in irq.h. #define IRQ_PRI_I2C (NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0)) // Configure PLL to give the desired CPU freq #undef MICROPY_HW_FLASH_LATENCY -#if defined(STM32H7) -#define CORE_PLL_FREQ (96000000) -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 -#else +#if defined(STM32F4) || defined(STM32F7) #define CORE_PLL_FREQ (48000000) #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_1 +#elif defined(STM32H7) +#define CORE_PLL_FREQ (96000000) +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 #endif #undef MICROPY_HW_CLK_PLLM #undef MICROPY_HW_CLK_PLLN @@ -92,7 +98,11 @@ uint32_t get_le32(const uint8_t *b) { void mp_hal_delay_us(mp_uint_t usec) { // use a busy loop for the delay // sys freq is always a multiple of 2MHz, so division here won't lose precision + #if defined(CORE_PLL_FREQ) const uint32_t ucount = CORE_PLL_FREQ / 2000000 * usec / 2; + #else + const uint32_t ucount = SystemCoreClock / 2000000 * usec / 2; + #endif for (uint32_t count = 0; ++count <= ucount;) { } } @@ -139,72 +149,6 @@ static void __fatal_error(const char *msg) { /******************************************************************************/ // CLOCK -#if defined(STM32F4) || defined(STM32F7) - -#define CONFIG_RCC_CR_1ST (RCC_CR_HSION) -#define CONFIG_RCC_CR_2ND (RCC_CR_HSEON || RCC_CR_CSSON || RCC_CR_PLLON) -#define CONFIG_RCC_PLLCFGR (0x24003010) - -#elif defined(STM32H7) - -#define CONFIG_RCC_CR_1ST (RCC_CR_HSION) -#define CONFIG_RCC_CR_2ND (RCC_CR_PLL3ON | RCC_CR_PLL2ON | RCC_CR_PLL1ON | RCC_CR_CSSHSEON \ - | RCC_CR_HSEON | RCC_CR_HSI48ON | RCC_CR_CSIKERON | RCC_CR_CSION) -#define CONFIG_RCC_PLLCFGR (0x00000000) - -#else -#error Unknown processor -#endif - -void SystemInit(void) { - #if defined(STM32H7) - // Configure write-once power options, and wait for voltage levels to be ready - PWR->CR3 = PWR_CR3_LDOEN; - while (!(PWR->CSR1 & PWR_CSR1_ACTVOSRDY)) { - } - #endif - - // Set HSION bit - RCC->CR |= CONFIG_RCC_CR_1ST; - - // Reset CFGR register - RCC->CFGR = 0x00000000; - - // Reset HSEON, CSSON and PLLON bits - RCC->CR &= ~CONFIG_RCC_CR_2ND; - - // Reset PLLCFGR register - RCC->PLLCFGR = CONFIG_RCC_PLLCFGR; - - #if defined(STM32H7) - // Reset PLL and clock configuration registers - RCC->D1CFGR = 0x00000000; - RCC->D2CFGR = 0x00000000; - RCC->D3CFGR = 0x00000000; - RCC->PLLCKSELR = 0x00000000; - RCC->D1CCIPR = 0x00000000; - RCC->D2CCIP1R = 0x00000000; - RCC->D2CCIP2R = 0x00000000; - RCC->D3CCIPR = 0x00000000; - #endif - - // Reset HSEBYP bit - RCC->CR &= (uint32_t)0xFFFBFFFF; - - // Disable all interrupts - #if defined(STM32F4) || defined(STM32F7) - RCC->CIR = 0x00000000; - #elif defined(STM32H7) - RCC->CIER = 0x00000000; - #endif - - // Set location of vector table - SCB->VTOR = FLASH_BASE; - - // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI - SCB->CCR |= SCB_CCR_STKALIGN_Msk; -} - void systick_init(void) { // Configure SysTick as 1ms ticker SysTick_Config(SystemCoreClock / 1000); @@ -378,6 +322,9 @@ uint32_t HAL_RCC_GetHCLKFreq(void) { #elif defined(STM32H7) #define AHBxENR AHB4ENR #define AHBxENR_GPIOAEN_Pos RCC_AHB4ENR_GPIOAEN_Pos +#elif defined(STM32WB) +#define AHBxENR AHB2ENR +#define AHBxENR_GPIOAEN_Pos RCC_AHB2ENR_GPIOAEN_Pos #endif void mp_hal_pin_config(mp_hal_pin_obj_t port_pin, uint32_t mode, uint32_t pull, uint32_t alt) { @@ -492,6 +439,11 @@ static int usrbtn_state(void) { /******************************************************************************/ // FLASH +#if defined(STM32WB) +#define FLASH_END FLASH_END_ADDR +#endif +#define APPLICATION_FLASH_LENGTH (FLASH_END + 1 - APPLICATION_ADDR) + #ifndef MBOOT_SPIFLASH_LAYOUT #define MBOOT_SPIFLASH_LAYOUT "" #endif @@ -500,153 +452,49 @@ static int usrbtn_state(void) { #define MBOOT_SPIFLASH2_LAYOUT "" #endif -typedef struct { - uint32_t base_address; - uint32_t sector_size; - uint32_t sector_count; -} flash_layout_t; - -#if defined(STM32F7) -// FLASH_FLAG_PGSERR (Programming Sequence Error) was renamed to -// FLASH_FLAG_ERSERR (Erasing Sequence Error) in STM32F7 -#define FLASH_FLAG_PGSERR FLASH_FLAG_ERSERR -#endif - #if defined(STM32F4) \ || defined(STM32F722xx) \ || defined(STM32F723xx) \ || defined(STM32F732xx) \ || defined(STM32F733xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x04000, 4 }, - { 0x08010000, 0x10000, 1 }, - { 0x08020000, 0x20000, 3 }, - #if defined(FLASH_SECTOR_8) - { 0x08080000, 0x20000, 4 }, - #endif - #if defined(FLASH_SECTOR_12) - { 0x08100000, 0x04000, 4 }, - { 0x08110000, 0x10000, 1 }, - { 0x08120000, 0x20000, 7 }, - #endif -}; - #elif defined(STM32F765xx) || defined(STM32F767xx) || defined(STM32F769xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*032Kg,01*128Kg,07*256Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -// This is for dual-bank mode disabled -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x08000, 4 }, - { 0x08020000, 0x20000, 1 }, - { 0x08040000, 0x40000, 7 }, -}; - #elif defined(STM32H743xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/16*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x20000, 16 }, -}; - +#elif defined(STM32WB) +#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/256*04Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT #endif -static inline bool flash_is_valid_addr(uint32_t addr) { - uint8_t last = MP_ARRAY_SIZE(flash_layout) - 1; - uint32_t end_of_flash = flash_layout[last].base_address + - flash_layout[last].sector_count * flash_layout[last].sector_size; - return flash_layout[0].base_address <= addr && addr < end_of_flash; -} - -static uint32_t flash_get_sector_index(uint32_t addr, uint32_t *sector_size) { - if (addr >= flash_layout[0].base_address) { - uint32_t sector_index = 0; - for (int i = 0; i < MP_ARRAY_SIZE(flash_layout); ++i) { - for (int j = 0; j < flash_layout[i].sector_count; ++j) { - uint32_t sector_start_next = flash_layout[i].base_address - + (j + 1) * flash_layout[i].sector_size; - if (addr < sector_start_next) { - *sector_size = flash_layout[i].sector_size; - return sector_index; - } - ++sector_index; - } - } - } - return 0; -} - -#if defined(STM32H7) -// get the bank of a given flash address -static uint32_t get_bank(uint32_t addr) { - if (READ_BIT(FLASH->OPTCR, FLASH_OPTCR_SWAP_BANK) == 0) { - // no bank swap - if (addr < (FLASH_BASE + FLASH_BANK_SIZE)) { - return FLASH_BANK_1; - } else { - return FLASH_BANK_2; - } - } else { - // bank swap - if (addr < (FLASH_BASE + FLASH_BANK_SIZE)) { - return FLASH_BANK_2; - } else { - return FLASH_BANK_1; - } - } -} -#endif - -static int flash_mass_erase(void) { - // TODO - return -1; +static int mboot_flash_mass_erase(void) { + // Erase all flash pages after mboot. + int ret = flash_erase(APPLICATION_ADDR, APPLICATION_FLASH_LENGTH / sizeof(uint32_t)); + return ret; } -static int flash_page_erase(uint32_t addr, uint32_t *next_addr) { +static int mboot_flash_page_erase(uint32_t addr, uint32_t *next_addr) { uint32_t sector_size = 0; - uint32_t sector = flash_get_sector_index(addr, §or_size); - if (sector == 0) { - // Don't allow to erase the sector with this bootloader in it + uint32_t sector_start = 0; + int32_t sector = flash_get_sector_info(addr, §or_start, §or_size); + if (sector <= 0) { + // Don't allow to erase the sector with this bootloader in it, or invalid sectors dfu_context.status = DFU_STATUS_ERROR_ADDRESS; - dfu_context.error = MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX; + dfu_context.error = (sector == 0) ? MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX + : MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; return -1; } - *next_addr = addr + sector_size; - - HAL_FLASH_Unlock(); - - // Clear pending flags (if any) - #if defined(STM32H7) - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS_BANK1 | FLASH_FLAG_ALL_ERRORS_BANK2); - #else - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | - FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); - #endif - - // erase the sector(s) - FLASH_EraseInitTypeDef EraseInitStruct; - EraseInitStruct.TypeErase = TYPEERASE_SECTORS; - EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3; // voltage range needs to be 2.7V to 3.6V - #if defined(STM32H7) - EraseInitStruct.Banks = get_bank(addr); - #endif - EraseInitStruct.Sector = sector; - EraseInitStruct.NbSectors = 1; + *next_addr = sector_start + sector_size; - uint32_t SectorError = 0; - if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) { - // error occurred during sector erase - return -1; + // Erase the flash page. + int ret = flash_erase(sector_start, sector_size / sizeof(uint32_t)); + if (ret != 0) { + return ret; } // Check the erase set bits to 1, at least for the first 256 bytes for (int i = 0; i < 64; ++i) { - if (((volatile uint32_t*)addr)[i] != 0xffffffff) { + if (((volatile uint32_t*)sector_start)[i] != 0xffffffff) { return -2; } } @@ -654,42 +502,25 @@ static int flash_page_erase(uint32_t addr, uint32_t *next_addr) { return 0; } -static int flash_write(uint32_t addr, const uint8_t *src8, size_t len) { - if (addr >= flash_layout[0].base_address && addr < flash_layout[0].base_address + flash_layout[0].sector_size) { +static int mboot_flash_write(uint32_t addr, const uint8_t *src8, size_t len) { + int32_t sector = flash_get_sector_info(addr, NULL, NULL); + if (sector <= 0) { // Don't allow to write the sector with this bootloader in it dfu_context.status = DFU_STATUS_ERROR_ADDRESS; - dfu_context.error = MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX; + dfu_context.error = (sector == 0) ? MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX + : MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; return -1; } const uint32_t *src = (const uint32_t*)src8; size_t num_word32 = (len + 3) / 4; - HAL_FLASH_Unlock(); - #if defined(STM32H7) - - // program the flash 256 bits at a time - for (int i = 0; i < num_word32 / 8; ++i) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr, (uint64_t)(uint32_t)src) != HAL_OK) { - return - 1; - } - addr += 32; - src += 8; + // Write the data to flash. + int ret = flash_write(addr, src, num_word32); + if (ret != 0) { + return ret; } - #else - - // program the flash word by word - for (size_t i = 0; i < num_word32; i++) { - if (HAL_FLASH_Program(TYPEPROGRAM_WORD, addr, *src) != HAL_OK) { - return -1; - } - addr += 4; - src += 1; - } - - #endif - // TODO verify data return 0; @@ -699,8 +530,8 @@ static int flash_write(uint32_t addr, const uint8_t *src8, size_t len) { // Writable address space interface static int do_mass_erase(void) { - // TODO - return flash_mass_erase(); + // TODO spiflash erase ? + return mboot_flash_mass_erase(); } #if defined(MBOOT_SPIFLASH_ADDR) || defined(MBOOT_SPIFLASH2_ADDR) @@ -735,7 +566,7 @@ int do_page_erase(uint32_t addr, uint32_t *next_addr) { } else #endif { - ret = flash_page_erase(addr, next_addr); + ret = mboot_flash_page_erase(addr, next_addr); } led0_state((ret == 0) ? LED0_STATE_SLOW_FLASH : LED0_STATE_SLOW_INVERTED_FLASH); @@ -775,7 +606,7 @@ int do_write(uint32_t addr, const uint8_t *src8, size_t len) { } else #endif if (flash_is_valid_addr(addr)) { - ret = flash_write(addr, src8, len); + ret = mboot_flash_write(addr, src8, len); } else { dfu_context.status = DFU_STATUS_ERROR_ADDRESS; dfu_context.error = MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; @@ -840,7 +671,7 @@ void i2c_init(int addr) { i2c_slave_init(MBOOT_I2Cx, I2Cx_EV_IRQn, IRQ_PRI_I2C, addr); } -int i2c_slave_process_addr_match(int rw) { +int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw) { if (i2c_obj.cmd_arg_sent) { i2c_obj.cmd_send_arg = false; } @@ -848,14 +679,14 @@ int i2c_slave_process_addr_match(int rw) { return 0; // ACK } -int i2c_slave_process_rx_byte(uint8_t val) { +int i2c_slave_process_rx_byte(i2c_slave_t *i2c, uint8_t val) { if (i2c_obj.cmd_buf_pos < sizeof(i2c_obj.cmd_buf)) { i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++] = val; } return 0; // ACK } -void i2c_slave_process_rx_end(void) { +void i2c_slave_process_rx_end(i2c_slave_t *i2c) { if (i2c_obj.cmd_buf_pos == 0) { return; } @@ -940,7 +771,7 @@ void i2c_slave_process_rx_end(void) { i2c_obj.cmd_arg_sent = false; } -uint8_t i2c_slave_process_tx_byte(void) { +uint8_t i2c_slave_process_tx_byte(i2c_slave_t *i2c) { if (i2c_obj.cmd_send_arg) { i2c_obj.cmd_arg_sent = true; return i2c_obj.cmd_arg; @@ -951,6 +782,9 @@ uint8_t i2c_slave_process_tx_byte(void) { } } +void i2c_slave_process_tx_end(i2c_slave_t *i2c) { +} + #endif // defined(MBOOT_I2C_SCL) /******************************************************************************/ @@ -968,16 +802,19 @@ static int dfu_process_dnload(void) { int ret = -1; if (dfu_context.wBlockNum == 0) { // download control commands - if (dfu_context.wLength >= 1 && dfu_context.buf[0] == 0x41) { + if (dfu_context.wLength >= 1 && dfu_context.buf[0] == DFU_CMD_DNLOAD_ERASE) { if (dfu_context.wLength == 1) { // mass erase ret = do_mass_erase(); + if (ret != 0) { + dfu_context.cmd = DFU_CMD_NONE; + } } else if (dfu_context.wLength == 5) { // erase page uint32_t next_addr; ret = do_page_erase(get_le32(&dfu_context.buf[1]), &next_addr); } - } else if (dfu_context.wLength >= 1 && dfu_context.buf[0] == 0x21) { + } else if (dfu_context.wLength >= 1 && dfu_context.buf[0] == DFU_CMD_DNLOAD_SET_ADDRESS) { if (dfu_context.wLength == 5) { // set address dfu_context.addr = get_le32(&dfu_context.buf[1]); @@ -1061,13 +898,19 @@ static int dfu_handle_tx(int cmd, int arg, int len, uint8_t *buf, int max_len) { default: dfu_context.state = DFU_STATE_BUSY; } - buf[0] = dfu_context.status; // bStatus - buf[1] = 0; // bwPollTimeout (ms) - buf[2] = 0; // bwPollTimeout (ms) - buf[3] = 0; // bwPollTimeout (ms) - buf[4] = dfu_context.state; // bState - buf[5] = dfu_context.error; // iString + buf[0] = dfu_context.status; // bStatus + buf[1] = 0; // bwPollTimeout_lsb (ms) + buf[2] = 0; // bwPollTimeout (ms) + buf[3] = 0; // bwPollTimeout_msb (ms) + buf[4] = dfu_context.state; // bState + buf[5] = dfu_context.error; // iString + // Clear errors now they've been sent + dfu_context.status = DFU_STATUS_OK; + dfu_context.error = 0; return 6; + } else if (cmd == DFU_GETSTATE && len == 1) { + buf[0] = dfu_context.state; // bState + return 1; } return -1; } @@ -1116,12 +959,19 @@ typedef struct _pyb_usbdd_obj_t { #define MBOOT_USB_PID BOOTLOADER_DFU_USB_PID #endif +#if !MICROPY_HW_USB_IS_MULTI_OTG +STATIC const uint8_t usbd_fifo_size[USBD_PMA_NUM_FIFO] = { + 32, 32, // EP0(out), EP0(in) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x unused +}; +#else static const uint8_t usbd_fifo_size[] = { 32, 8, 16, 8, 16, 0, 0, // FS: RX, EP0(in), 5x IN endpoints #if MICROPY_HW_USB_HS 116, 8, 64, 4, 64, 0, 0, 0, 0, 0, // HS: RX, EP0(in), 8x IN endpoints #endif }; +#endif __ALIGN_BEGIN static const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END = { USB_LEN_LANGID_STR_DESC, @@ -1437,12 +1287,29 @@ static void do_reset(void) { NVIC_SystemReset(); } -uint32_t SystemCoreClock; - extern PCD_HandleTypeDef pcd_fs_handle; extern PCD_HandleTypeDef pcd_hs_handle; void stm32_main(int initial_r0) { + #if defined(STM32H7) + // Configure write-once power options, and wait for voltage levels to be ready + PWR->CR3 = PWR_CR3_LDOEN; + while (!(PWR->CSR1 & PWR_CSR1_ACTVOSRDY)) { + } + + // Reset the kernel clock configuration registers for all domains. + RCC->D1CCIPR = 0x00000000; + RCC->D2CCIP1R = 0x00000000; + RCC->D2CCIP2R = 0x00000000; + RCC->D3CCIPR = 0x00000000; + #endif + + // Make sure IRQ vector table points to flash where this bootloader lives. + SCB->VTOR = FLASH_BASE; + + // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI + SCB->CCR |= SCB_CCR_STKALIGN_Msk; + #if defined(STM32F4) #if INSTRUCTION_CACHE_ENABLE __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); @@ -1481,9 +1348,6 @@ void stm32_main(int initial_r0) { goto enter_bootloader; } - // MCU starts up with HSI - SystemCoreClock = HSI_VALUE; - int reset_mode = get_reset_mode(); uint32_t msp = *(volatile uint32_t*)APPLICATION_ADDR; if (reset_mode != 4 && (msp & APP_VALIDITY_BITS) == 0) { @@ -1636,6 +1500,15 @@ void I2Cx_EV_IRQHandler(void) { #endif #if !USE_USB_POLLING + +#if defined(STM32WB) + +void USB_LP_IRQHandler(void) { + HAL_PCD_IRQHandler(&pcd_fs_handle); +} + +#else + #if MBOOT_USB_AUTODETECT_PORT || MICROPY_HW_USB_MAIN_DEV == USB_PHY_FS_ID void OTG_FS_IRQHandler(void) { HAL_PCD_IRQHandler(&pcd_fs_handle); @@ -1648,3 +1521,5 @@ void OTG_HS_IRQHandler(void) { } #endif #endif + +#endif diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index 7dc1ada0c..37929665e 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -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 @@ -23,6 +23,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#ifndef MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H +#define MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H #include #include @@ -42,13 +44,10 @@ enum { enum { ELEM_MOUNT_FAT = 1, + ELEM_MOUNT_LFS1, + ELEM_MOUNT_LFS2, }; -typedef struct _fsload_bdev_t { - uint32_t base_addr; - uint32_t byte_len; -} fsload_bdev_t; - extern uint8_t _estack[ELEM_DATA_SIZE]; uint32_t get_le32(const uint8_t *b); @@ -60,3 +59,5 @@ int do_write(uint32_t addr, const uint8_t *src8, size_t len); const uint8_t *elem_search(const uint8_t *elem, uint8_t elem_id); int fsload_process(void); + +#endif // MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H diff --git a/ports/stm32/mboot/mphalport.h b/ports/stm32/mboot/mphalport.h index 0c8cb91a6..56d18a9b0 100644 --- a/ports/stm32/mboot/mphalport.h +++ b/ports/stm32/mboot/mphalport.h @@ -28,6 +28,11 @@ #include "genhdr/pins.h" +// For simplicity just convert all HAL errors to one errno. +static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { + return status == HAL_OK ? 0 : -1; +} + #define mp_hal_delay_us_fast(us) mp_hal_delay_us(us) #define MP_HAL_PIN_MODE_INPUT (0) diff --git a/ports/stm32/mboot/vfs.h b/ports/stm32/mboot/vfs.h new file mode 100644 index 000000000..6cf883a13 --- /dev/null +++ b/ports/stm32/mboot/vfs.h @@ -0,0 +1,96 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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 + * 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_STM32_MBOOT_VFS_H +#define MICROPY_INCLUDED_STM32_MBOOT_VFS_H + +#include "gzstream.h" +#include "mboot.h" + +#if MBOOT_VFS_FAT + +#include "lib/oofatfs/ff.h" + +typedef struct _vfs_fat_context_t { + uint32_t bdev_base_addr; + uint32_t bdev_byte_len; + FATFS fatfs; + FIL fp; +} vfs_fat_context_t; + +extern const stream_methods_t vfs_fat_stream_methods; + +int vfs_fat_mount(vfs_fat_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#if MBOOT_VFS_LFS1 + +#include "lib/littlefs/lfs1.h" + +#define LFS_READ_SIZE (32) +#define LFS_PROG_SIZE (32) +#define LFS_LOOKAHEAD_SIZE (32) + +typedef struct _vfs_lfs1_context_t { + uint32_t bdev_base_addr; + struct lfs1_config config; + lfs1_t lfs; + struct lfs1_file_config filecfg; + uint8_t filebuf[LFS_PROG_SIZE]; + lfs1_file_t file; +} vfs_lfs1_context_t; + +extern const stream_methods_t vfs_lfs1_stream_methods; + +int vfs_lfs1_mount(vfs_lfs1_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#if MBOOT_VFS_LFS2 + +#include "lib/littlefs/lfs2.h" + +#define LFS_READ_SIZE (32) +#define LFS_PROG_SIZE (32) +#define LFS_CACHE_SIZE (4 * LFS_READ_SIZE) +#define LFS_LOOKAHEAD_SIZE (32) + +typedef struct _vfs_lfs2_context_t { + uint32_t bdev_base_addr; + struct lfs2_config config; + lfs2_t lfs; + struct lfs2_file_config filecfg; + uint8_t filebuf[LFS_CACHE_SIZE]; // lfs2 specific + lfs2_file_t file; +} vfs_lfs2_context_t; + +extern const stream_methods_t vfs_lfs2_stream_methods; + +int vfs_lfs2_mount(vfs_lfs2_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#endif // MICROPY_INCLUDED_STM32_MBOOT_VFS_H diff --git a/ports/stm32/mboot/vfs_fat.c b/ports/stm32/mboot/vfs_fat.c new file mode 100644 index 000000000..20de074f0 --- /dev/null +++ b/ports/stm32/mboot/vfs_fat.c @@ -0,0 +1,122 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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 + * 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/mphal.h" +#include "lib/oofatfs/ff.h" +#include "lib/oofatfs/diskio.h" +#include "mboot.h" +#include "vfs.h" + +#if MBOOT_FSLOAD && MBOOT_VFS_FAT + +#if FF_MAX_SS == FF_MIN_SS +#define SECSIZE (FF_MIN_SS) +#else +#error Unsupported +#endif + +DRESULT disk_read(void *pdrv, BYTE *buf, DWORD sector, UINT count) { + vfs_fat_context_t *ctx = pdrv; + + if (0 <= sector && sector < ctx->bdev_byte_len / 512) { + do_read(ctx->bdev_base_addr + sector * SECSIZE, count * SECSIZE, buf); + return RES_OK; + } + + return RES_PARERR; +} + +DRESULT disk_ioctl(void *pdrv, BYTE cmd, void *buf) { + vfs_fat_context_t *ctx = pdrv; + + switch (cmd) { + case CTRL_SYNC: + return RES_OK; + + case GET_SECTOR_COUNT: + *((DWORD*)buf) = ctx->bdev_byte_len / SECSIZE; + return RES_OK; + + case GET_SECTOR_SIZE: + *((WORD*)buf) = SECSIZE; + return RES_OK; + + case GET_BLOCK_SIZE: + *((DWORD*)buf) = 1; // erase block size in units of sector size + return RES_OK; + + case IOCTL_INIT: + case IOCTL_STATUS: + *((DSTATUS*)buf) = STA_PROTECT; + return RES_OK; + + default: + return RES_PARERR; + } +} + +int vfs_fat_mount(vfs_fat_context_t *ctx, uint32_t base_addr, uint32_t byte_len) { + ctx->bdev_base_addr = base_addr; + ctx->bdev_byte_len = byte_len; + ctx->fatfs.drv = ctx; + FRESULT res = f_mount(&ctx->fatfs); + if (res != FR_OK) { + return -1; + } + return 0; +} + +static int vfs_fat_stream_open(void *stream_in, const char *fname) { + vfs_fat_context_t *stream = stream_in; + FRESULT res = f_open(&stream->fatfs, &stream->fp, fname, FA_READ); + if (res != FR_OK) { + return -1; + } + return 0; +} + +static void vfs_fat_stream_close(void *stream_in) { + vfs_fat_context_t *stream = stream_in; + f_close(&stream->fp); +} + +static int vfs_fat_stream_read(void *stream_in, uint8_t *buf, size_t len) { + vfs_fat_context_t *stream = stream_in; + UINT n; + FRESULT res = f_read(&stream->fp, buf, len, &n); + if (res != FR_OK) { + return -1; + } + return n; +} + +const stream_methods_t vfs_fat_stream_methods = { + vfs_fat_stream_open, + vfs_fat_stream_close, + vfs_fat_stream_read, +}; + +#endif // MBOOT_FSLOAD && MBOOT_VFS_FAT diff --git a/ports/stm32/mboot/vfs_lfs.c b/ports/stm32/mboot/vfs_lfs.c new file mode 100644 index 000000000..dec7c015f --- /dev/null +++ b/ports/stm32/mboot/vfs_lfs.c @@ -0,0 +1,177 @@ +/* + * 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 + +#include "py/mphal.h" +#include "mboot.h" +#include "vfs.h" + +#if MBOOT_FSLOAD && (MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) + +#if MBOOT_VFS_LFS1 +#if MBOOT_VFS_LFS2 +#error Unsupported +#endif + +#define LFSx_MACRO(s) LFS1##s +#define LFSx_API(x) lfs1_ ## x +#define VFS_LFSx_CONTEXT_T vfs_lfs1_context_t +#define VFS_LFSx_MOUNT vfs_lfs1_mount +#define VFS_LFSx_STREAM_METHODS vfs_lfs1_stream_methods + +#define SUPERBLOCK_MAGIC_OFFSET (40) +#define SUPERBLOCK_BLOCK_SIZE_OFFSET (28) +#define SUPERBLOCK_BLOCK_COUNT_OFFSET (32) + +static uint8_t lfs_read_buffer[LFS_READ_SIZE]; +static uint8_t lfs_prog_buffer[LFS_PROG_SIZE]; +static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE / 8]; + +#else + +#define LFSx_MACRO(s) LFS2##s +#define LFSx_API(x) lfs2_ ## x +#define VFS_LFSx_CONTEXT_T vfs_lfs2_context_t +#define VFS_LFSx_MOUNT vfs_lfs2_mount +#define VFS_LFSx_STREAM_METHODS vfs_lfs2_stream_methods + +#define SUPERBLOCK_MAGIC_OFFSET (8) +#define SUPERBLOCK_BLOCK_SIZE_OFFSET (24) +#define SUPERBLOCK_BLOCK_COUNT_OFFSET (28) + +static uint8_t lfs_read_buffer[LFS_CACHE_SIZE]; +static uint8_t lfs_prog_buffer[LFS_CACHE_SIZE]; +static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE]; + +#endif + +static int dev_read(const struct LFSx_API (config) * c, LFSx_API(block_t) block, LFSx_API(off_t) off, void *buffer, LFSx_API(size_t) size) { + VFS_LFSx_CONTEXT_T *ctx = c->context; + if (0 <= block && block < ctx->config.block_count) { + do_read(ctx->bdev_base_addr + block * ctx->config.block_size + off, size, buffer); + return LFSx_MACRO(_ERR_OK); + } + return LFSx_MACRO(_ERR_IO); +} + +static int dev_prog(const struct LFSx_API (config) * c, LFSx_API(block_t) block, LFSx_API(off_t) off, const void *buffer, LFSx_API(size_t) size) { + return LFSx_MACRO(_ERR_IO); +} + +static int dev_erase(const struct LFSx_API (config) * c, LFSx_API(block_t) block) { + return LFSx_MACRO(_ERR_IO); +} + +static int dev_sync(const struct LFSx_API (config) * c) { + return LFSx_MACRO(_ERR_OK); +} + +int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_len) { + // Read start of superblock. + uint8_t buf[48]; + do_read(base_addr, sizeof(buf), buf); + + // Verify littlefs and detect block size. + if (memcmp(&buf[SUPERBLOCK_MAGIC_OFFSET], "littlefs", 8) != 0) { + return -1; + } + uint32_t block_size = get_le32(&buf[SUPERBLOCK_BLOCK_SIZE_OFFSET]); + uint32_t block_count = get_le32(&buf[SUPERBLOCK_BLOCK_COUNT_OFFSET]); + + // Verify size of volume. + if (block_size * block_count != byte_len) { + return -1; + } + + ctx->bdev_base_addr = base_addr; + + struct LFSx_API (config) *config = &ctx->config; + memset(config, 0, sizeof(*config)); + + config->context = ctx; + + config->read = dev_read; + config->prog = dev_prog; + config->erase = dev_erase; + config->sync = dev_sync; + + config->read_size = LFS_READ_SIZE; + config->prog_size = LFS_PROG_SIZE; + config->block_size = block_size; + config->block_count = byte_len / block_size; + + #if MBOOT_VFS_LFS1 + config->lookahead = LFS_LOOKAHEAD_SIZE; + config->read_buffer = lfs_read_buffer; + config->prog_buffer = lfs_prog_buffer; + config->lookahead_buffer = lfs_lookahead_buffer; + #else + config->block_cycles = 100; + config->cache_size = LFS_CACHE_SIZE; + config->lookahead_size = LFS_LOOKAHEAD_SIZE; + config->read_buffer = lfs_read_buffer; + config->prog_buffer = lfs_prog_buffer; + config->lookahead_buffer = lfs_lookahead_buffer; + #endif + + int ret = LFSx_API(mount)(&ctx->lfs, &ctx->config); + if (ret < 0) { + return -1; + } + return 0; +} + +static int vfs_lfs_stream_open(void *stream_in, const char *fname) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + memset(&ctx->file, 0, sizeof(ctx->file)); + memset(&ctx->filecfg, 0, sizeof(ctx->filecfg)); + ctx->filecfg.buffer = &ctx->filebuf[0]; + LFSx_API(file_opencfg)(&ctx->lfs, &ctx->file, fname, LFSx_MACRO(_O_RDONLY), &ctx->filecfg); + return 0; +} + +static void vfs_lfs_stream_close(void *stream_in) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + LFSx_API(file_close)(&ctx->lfs, &ctx->file); +} + +static int vfs_lfs_stream_read(void *stream_in, uint8_t *buf, size_t len) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + LFSx_API(ssize_t) sz = LFSx_API(file_read)(&ctx->lfs, &ctx->file, buf, len); + if (sz < 0) { + return -1; + } + return sz; +} + +const stream_methods_t VFS_LFSx_STREAM_METHODS = { + vfs_lfs_stream_open, + vfs_lfs_stream_close, + vfs_lfs_stream_read, +}; + +#endif // MBOOT_FSLOAD && (MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 6fd2488a5..ac4d87123 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -346,6 +346,15 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 4, machine_freq); +// idle() +// This executies a wfi machine instruction which reduces power consumption +// of the MCU until an interrupt occurs, at which point execution continues. +STATIC mp_obj_t machine_idle(void) { + __WFI(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); + STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_obj_t args2[2] = {MP_OBJ_NULL, args[0]}; @@ -382,7 +391,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_HW_ENABLE_RNG { MP_ROM_QSTR(MP_QSTR_rng), MP_ROM_PTR(&pyb_rng_get_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&pyb_wfi_obj) }, + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, @@ -391,8 +400,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index f04bfb486..e4ccf6388 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -39,7 +39,14 @@ MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj); + +MP_DECLARE_CONST_FUN_OBJ_0(machine_idle_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj); +MP_DECLARE_CONST_FUN_OBJ_0(machine_disable_irq_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj); + +MP_DECLARE_CONST_FUN_OBJ_0(pyb_irq_stats_obj); + #endif // MICROPY_INCLUDED_STM32_MODMACHINE_H diff --git a/ports/stm32/modpyb.c b/ports/stm32/modpyb.c index 321fb62e9..c92090699 100644 --- a/ports/stm32/modpyb.c +++ b/ports/stm32/modpyb.c @@ -146,9 +146,9 @@ STATIC const mp_rom_map_elem_t pyb_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_repl_info), MP_ROM_PTR(&pyb_set_repl_info_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&pyb_wfi_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&machine_idle_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, #if IRQ_ENABLE_STATS { MP_ROM_QSTR(MP_QSTR_irq_stats), MP_ROM_PTR(&pyb_irq_stats_obj) }, #endif diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c new file mode 100644 index 000000000..a5977ff12 --- /dev/null +++ b/ports/stm32/mpbthciport.c @@ -0,0 +1,240 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-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/mphal.h" +#include "extmod/mpbthci.h" +#include "systick.h" +#include "pendsv.h" +#include "lib/utils/mpirq.h" + +#include "py/obj.h" + +#if MICROPY_PY_BLUETOOTH + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). +extern void mp_bluetooth_hci_poll(void); + +// Hook for pendsv poller to run this periodically every 128ms +#define BLUETOOTH_HCI_TICK(tick) (((tick) & ~(SYSTICK_DISPATCH_NUM_SLOTS - 1) & 0x7f) == 0) + +// Called periodically (systick) or directly (e.g. uart irq). +void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms) { + if (ticks_ms == 0 || BLUETOOTH_HCI_TICK(ticks_ms)) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_BLUETOOTH_HCI, mp_bluetooth_hci_poll); + } +} + +#if defined(STM32WB) + +/******************************************************************************/ +// HCI over IPCC + +#include +#include "rfcore.h" + +STATIC uint16_t hci_uart_rx_buf_cur; +STATIC uint16_t hci_uart_rx_buf_len; +STATIC uint8_t hci_uart_rx_buf_data[256]; + +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + (void)port; + (void)baudrate; + rfcore_ble_init(); + hci_uart_rx_buf_cur = 0; + hci_uart_rx_buf_len = 0; + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + (void)baudrate; + return 0; +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + MICROPY_PY_BLUETOOTH_ENTER + rfcore_ble_hci_cmd(len, (const uint8_t *)buf); + MICROPY_PY_BLUETOOTH_EXIT + return 0; +} + +// Callback to copy data into local hci_uart_rx_buf_data buffer for subsequent use. +STATIC int mp_bluetooth_hci_uart_msg_cb(void *env, const uint8_t *buf, size_t len) { + (void)env; + if (hci_uart_rx_buf_len + len > MP_ARRAY_SIZE(hci_uart_rx_buf_data)) { + len = MP_ARRAY_SIZE(hci_uart_rx_buf_data) - hci_uart_rx_buf_len; + } + memcpy(hci_uart_rx_buf_data + hci_uart_rx_buf_len, buf, len); + hci_uart_rx_buf_len += len; + return 0; +} + +int mp_bluetooth_hci_uart_readchar(void) { + if (hci_uart_rx_buf_cur >= hci_uart_rx_buf_len) { + hci_uart_rx_buf_cur = 0; + hci_uart_rx_buf_len = 0; + rfcore_ble_check_msg(mp_bluetooth_hci_uart_msg_cb, NULL); + } + + if (hci_uart_rx_buf_cur < hci_uart_rx_buf_len) { + return hci_uart_rx_buf_data[hci_uart_rx_buf_cur++]; + } else { + return -1; + } +} + +#else + +/******************************************************************************/ +// HCI over UART + +#include "pendsv.h" +#include "uart.h" + +pyb_uart_obj_t mp_bluetooth_hci_uart_obj; +mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; + +static uint8_t hci_uart_rxbuf[512]; + +mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { + // DEBUG_printf("mp_uart_interrupt\n"); + // New HCI data, schedule mp_bluetooth_hci_poll via PENDSV to make the stack handle it. + mp_bluetooth_hci_poll_wrapper(0); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); + +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_init (stm32)\n"); + + // bits (8), stop (1), parity (none) and flow (rts/cts) are assumed to match MYNEWT_VAL_BLE_HCI_UART_ constants in syscfg.h. + mp_bluetooth_hci_uart_obj.base.type = &pyb_uart_type; + mp_bluetooth_hci_uart_obj.uart_id = port; + mp_bluetooth_hci_uart_obj.is_static = true; + // We don't want to block indefinitely, but expect flow control is doing its job. + mp_bluetooth_hci_uart_obj.timeout = 200; + mp_bluetooth_hci_uart_obj.timeout_char = 200; + MP_STATE_PORT(pyb_uart_obj_all)[mp_bluetooth_hci_uart_obj.uart_id - 1] = &mp_bluetooth_hci_uart_obj; + + // This also initialises the UART. + mp_bluetooth_hci_uart_set_baudrate(baudrate); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit (stm32)\n"); + // TODO: deinit mp_bluetooth_hci_uart_obj + + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate(%lu) (stm32)\n", baudrate); + if (!baudrate) { + return -1; + } + + uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); + uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); + + // Add IRQ handler for IDLE (i.e. packet finished). + uart_irq_config(&mp_bluetooth_hci_uart_obj, false); + mp_irq_init(&mp_bluetooth_hci_uart_irq_obj, &uart_irq_methods, MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj)); + mp_bluetooth_hci_uart_obj.mp_irq_obj = &mp_bluetooth_hci_uart_irq_obj; + mp_bluetooth_hci_uart_obj.mp_irq_trigger = UART_FLAG_IDLE; + mp_bluetooth_hci_uart_irq_obj.handler = MP_OBJ_FROM_PTR(&mp_uart_interrupt_obj); + mp_bluetooth_hci_uart_irq_obj.ishard = true; + uart_irq_config(&mp_bluetooth_hci_uart_obj, true); + + return 0; +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write (stm32)\n"); + + mp_bluetooth_hci_controller_wakeup(); + int errcode; + uart_tx_data(&mp_bluetooth_hci_uart_obj, (void *)buf, len, &errcode); + if (errcode != 0) { + printf("\nmp_bluetooth_hci_uart_write: failed to write to UART %d\n", errcode); + } + return 0; +} + +// This function expects the controller to be in the wake state via a previous call +// to mp_bluetooth_hci_controller_woken. +int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar (stm32)\n"); + + if (uart_rx_any(&mp_bluetooth_hci_uart_obj)) { + // DEBUG_printf("... available\n"); + return uart_rx_char(&mp_bluetooth_hci_uart_obj); + } else { + return -1; + } +} + +#endif // defined(STM32WB) + +// Default (weak) implementation of the HCI controller interface. +// A driver (e.g. cywbt43.c) can override these for controller-specific +// functionality (i.e. power management). + +MP_WEAK int mp_bluetooth_hci_controller_init(void) { + DEBUG_printf("mp_bluetooth_hci_controller_init (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_controller_deinit (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_sleep_maybe(void) { + DEBUG_printf("mp_bluetooth_hci_controller_sleep_maybe (default)\n"); + return 0; +} + +MP_WEAK bool mp_bluetooth_hci_controller_woken(void) { + DEBUG_printf("mp_bluetooth_hci_controller_woken (default)\n"); + return true; +} + +MP_WEAK int mp_bluetooth_hci_controller_wakeup(void) { + DEBUG_printf("mp_bluetooth_hci_controller_wakeup (default)\n"); + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c new file mode 100644 index 000000000..a031a6a15 --- /dev/null +++ b/ports/stm32/mpbtstackport.c @@ -0,0 +1,107 @@ +/* + * 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 "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/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +// 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(); +} + +STATIC const hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + MICROPY_HW_BLE_UART_BAUDRATE, + 3000000, + 0, + NULL, +}; + +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { + return; + } + + // Process uart data. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + mp_bluetooth_btstack_hci_uart_process(); + } + + // Call the BTstack run loop. + btstack_run_loop_embedded_execute_once(); +} + +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(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + // TODO: Probably not necessary for BCM (we have our own firmware loader in the cyw43 driver), + // but might be worth investigating for other controllers in the future. + // hci_set_chipset(btstack_chipset_bcm_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/stm32/mpbtstackport.h b/ports/stm32/mpbtstackport.h new file mode 100644 index 000000000..110f921d9 --- /dev/null +++ b/ports/stm32/mpbtstackport.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_STM32_BTSTACK_PORT_H +#define MICROPY_INCLUDED_STM32_BTSTACK_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_BTSTACK_PORT_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 13dce6e96..6f38d90e8 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -120,7 +120,7 @@ #define MICROPY_PY_CMATH (1) #define MICROPY_PY_IO (1) #define MICROPY_PY_IO_IOBASE (1) -#define MICROPY_PY_IO_FILEIO (MICROPY_VFS_FAT) // because mp_type_fileio/textio point to fatfs impl +#define MICROPY_PY_IO_FILEIO (MICROPY_VFS_FAT || MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) #define MICROPY_PY_SYS_MAXSIZE (1) #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_STDFILES (1) @@ -210,9 +210,17 @@ #define MICROPY_FATFS_RPATH (2) #define MICROPY_FATFS_MULTI_PARTITION (1) -// TODO these should be generic, not bound to fatfs +// TODO these should be generic, not bound to a particular FS implementation +#if MICROPY_VFS_FAT #define mp_type_fileio mp_type_vfs_fat_fileio #define mp_type_textio mp_type_vfs_fat_textio +#elif MICROPY_VFS_LFS1 +#define mp_type_fileio mp_type_vfs_lfs1_fileio +#define mp_type_textio mp_type_vfs_lfs1_textio +#elif MICROPY_VFS_LFS2 +#define mp_type_fileio mp_type_vfs_lfs2_fileio +#define mp_type_textio mp_type_vfs_lfs2_textio +#endif // use vfs's functions for import stat and builtin open #define mp_import_stat mp_vfs_import_stat diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index fe9378a1c..a76945db5 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -4,6 +4,10 @@ extern const unsigned char mp_hal_status_to_errno_table[4]; +static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { + return -mp_hal_status_to_errno_table[status]; +} + NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable diff --git a/ports/stm32/mpnimbleport.c b/ports/stm32/mpnimbleport.c new file mode 100644 index 000000000..1d7c09513 --- /dev/null +++ b/ports/stm32/mpnimbleport.c @@ -0,0 +1,71 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jim Mussared + * 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_NIMBLE + +#include "host/ble_hs.h" +#include "nimble/nimble_npl.h" + +#include "extmod/mpbthci.h" +#include "extmod/nimble/modbluetooth_nimble.h" +#include "extmod/nimble/hal/hal_uart.h" + +// This implements the Nimble "background task". It's called at PENDSV +// priority, either every 128ms or whenever there's UART data available. +// Because it's called via PENDSV, you can implicitly consider that it +// is surrounded by MICROPY_PY_BLUETOOTH_ENTER / MICROPY_PY_BLUETOOTH_EXIT. +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Ask NimBLE to process UART data. + mp_bluetooth_nimble_hci_uart_process(); + + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); + } +} + +// --- Port-specific helpers for the generic NimBLE bindings. ----------------- + +void mp_bluetooth_nimble_hci_uart_wfi(void) { + __WFI(); +} + +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; +} + +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/mpnimbleport.h b/ports/stm32/mpnimbleport.h new file mode 100644 index 000000000..f3ee59718 --- /dev/null +++ b/ports/stm32/mpnimbleport.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_PORT_H +#define MICROPY_INCLUDED_STM32_NIMBLE_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_PORT_H diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 749cb5505..206b19b75 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -25,6 +25,7 @@ */ #include "py/mphal.h" +#include "irq.h" #include "powerctrl.h" static inline void powerctrl_config_systick(void) { diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index ad2efbef4..224b8f28b 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -432,6 +432,20 @@ STATIC mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * HAL_StatusTypeDef status; #if MICROPY_HW_ENABLE_FDCAN + uint32_t timeout_ms = args[ARG_timeout].u_int; + uint32_t start = HAL_GetTick(); + while (HAL_FDCAN_GetTxFifoFreeLevel(&self->can) == 0) { + if (timeout_ms == 0) { + mp_raise_OSError(MP_ETIMEDOUT); + } + // Check for the Timeout + if (timeout_ms != HAL_MAX_DELAY) { + if (HAL_GetTick() - start >= timeout_ms) { + mp_raise_OSError(MP_ETIMEDOUT); + } + } + MICROPY_EVENT_POLL_HOOK + } status = HAL_FDCAN_AddMessageToTxFifoQ(&self->can, &tx_msg, tx_data); #else self->can.pTxMsg = &tx_msg; diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 602ef974b..54b339343 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -416,8 +416,13 @@ void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env) SWAP_UINT8(buf[3], buf[6]); SWAP_UINT8(buf[4], buf[5]); tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), 8, buf); // set BDADDR - tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, (const uint8_t *)"\x00\x06"); // 0 dBm } } +// "level" is 0x00-0x1f, ranging from -40 dBm to +6 dBm (not linear). +void rfcore_ble_set_txpower(uint8_t level) { + uint8_t buf[2] = { 0x00, level }; + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, buf); +} + #endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index 138c438f1..fbe111e1e 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -33,5 +33,6 @@ void rfcore_init(void); void rfcore_ble_init(void); void rfcore_ble_hci_cmd(size_t len, const uint8_t *src); void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env); +void rfcore_ble_set_txpower(uint8_t level); #endif // MICROPY_INCLUDED_STM32_RFCORE_H diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index b84f4adfa..8e96da177 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -602,7 +602,7 @@ void TIM1_UP_TIM10_IRQHandler(void) { IRQ_EXIT(TIM1_UP_TIM10_IRQn); } -#if defined(STM32L4) +#if defined(STM32L4) || defined(STM32WB) void TIM1_UP_TIM16_IRQHandler(void) { IRQ_ENTER(TIM1_UP_TIM16_IRQn); timer_irq_handler(1); diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 491221ec2..9b8c14c0d 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -477,7 +477,7 @@ STATIC void config_deadtime(pyb_timer_obj_t *self, mp_int_t ticks, mp_int_t brk) deadTimeConfig.DeadTime = compute_dtg_from_ticks(ticks); deadTimeConfig.BreakState = brk == BRK_OFF ? TIM_BREAK_DISABLE : TIM_BREAK_ENABLE; deadTimeConfig.BreakPolarity = brk == BRK_LOW ? TIM_BREAKPOLARITY_LOW : TIM_BREAKPOLARITY_HIGH; - #if defined(STM32F7) || defined(STM32H7) | defined(STM32L4) + #if defined(STM32F7) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) deadTimeConfig.BreakFilter = 0; deadTimeConfig.Break2State = TIM_BREAK_DISABLE; deadTimeConfig.Break2Polarity = TIM_BREAKPOLARITY_LOW; @@ -769,14 +769,14 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons HAL_TIM_Base_Init(&self->tim); #if !defined(STM32L0) #if defined(IS_TIM_ADVANCED_INSTANCE) - if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) { + if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) #elif defined(IS_TIM_BREAK_INSTANCE) - if (IS_TIM_BREAK_INSTANCE(self->tim.Instance)) { + if (IS_TIM_BREAK_INSTANCE(self->tim.Instance)) #else - if (0) { - #endif + if (0) + #endif + { config_deadtime(self, args[ARG_deadtime].u_int, args[ARG_brk].u_int); - } #endif @@ -805,7 +805,7 @@ STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(1, TIM1_UP_TIM10_IRQn), #elif defined(STM32H7) TIM_ENTRY(1, TIM1_UP_IRQn), - #elif defined(STM32L4) + #elif defined(STM32L4) || defined(STM32WB) TIM_ENTRY(1, TIM1_UP_TIM16_IRQn), #endif #endif diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index cd5238172..657d4cb6e 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -93,8 +93,29 @@ pyb_usb_storage_medium_t pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_NONE; // Units of FIFO size arrays below are 4x 16-bit words = 8 bytes // There are 512x 16-bit words it total to use here (when using PCD_SNG_BUF) -// EP0(out), EP0(in), MSC/HID(out), MSC/HID(in), unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) -STATIC const uint8_t usbd_fifo_size_cdc1[] = {16, 16, 16, 16, 0, 16, 16, 16}; +STATIC const uint8_t usbd_fifo_size_cdc1[USBD_PMA_NUM_FIFO] = { + 16, 16, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 16, 16, 16, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 0, 0, 0, 0, 0, 0, 0, // 8x unused +}; + +#if MICROPY_HW_USB_CDC_NUM >= 2 +STATIC const uint8_t usbd_fifo_size_cdc2[USBD_PMA_NUM_FIFO] = { + 8, 8, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 8, 12, 12, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 8, 12, 12, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) + 0, 0, 0, 0, // 4x unused +}; + +// RX; EP0(in), MSC/HID, CDC_CMD, CDC_DATA, CDC2_CMD/HID, CDC2_DATA, HID +STATIC const uint8_t usbd_fifo_size_cdc2_msc_hid[USBD_PMA_NUM_FIFO] = { + 8, 8, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 8, 8, 8, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 8, 8, 8, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) + 8, 8, // HID(out), HID(in) + 0, 0, // 2x unused +}; +#endif #else diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index a0e19de97..7a0912852 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -171,32 +171,56 @@ int8_t usbd_cdc_control(usbd_cdc_state_t *cdc_in, uint8_t cmd, uint8_t *pbuf, ui return USBD_OK; } +static inline uint16_t usbd_cdc_tx_buffer_mask(uint16_t val) { + return val & (USBD_CDC_TX_DATA_SIZE - 1); +} + +static inline uint16_t usbd_cdc_tx_buffer_size(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_in - cdc->tx_buf_ptr_out; +} + +static inline bool usbd_cdc_tx_buffer_empty(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_out == cdc->tx_buf_ptr_in; +} + +static inline bool usbd_cdc_tx_buffer_will_be_empty(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_out_next == cdc->tx_buf_ptr_in; +} + +static inline bool usbd_cdc_tx_buffer_full(usbd_cdc_itf_t *cdc) { + return usbd_cdc_tx_buffer_size(cdc) == USBD_CDC_TX_DATA_SIZE; +} + +static uint16_t usbd_cdc_tx_send_length(usbd_cdc_itf_t *cdc) { + uint16_t to_end = USBD_CDC_TX_DATA_SIZE - usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_out); + return MIN(usbd_cdc_tx_buffer_size(cdc), to_end); +} + +static void usbd_cdc_tx_buffer_put(usbd_cdc_itf_t *cdc, uint8_t data) { + cdc->tx_buf[usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_in)] = data; + cdc->tx_buf_ptr_in++; +} + +static uint8_t *usbd_cdc_tx_buffer_getp(usbd_cdc_itf_t *cdc, uint16_t len) { + cdc->tx_buf_ptr_out_next += len; + return &cdc->tx_buf[usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_out)]; +} + // Called when the USB IN endpoint is ready to receive more data // (cdc.base.tx_in_progress must be 0) void usbd_cdc_tx_ready(usbd_cdc_state_t *cdc_in) { usbd_cdc_itf_t *cdc = (usbd_cdc_itf_t *)cdc_in; - cdc->tx_buf_ptr_out = cdc->tx_buf_ptr_out_shadow; + cdc->tx_buf_ptr_out = cdc->tx_buf_ptr_out_next; - if (cdc->tx_buf_ptr_out == cdc->tx_buf_ptr_in && !cdc->tx_need_empty_packet) { + if (usbd_cdc_tx_buffer_empty(cdc) && !cdc->tx_need_empty_packet) { // No outstanding data to send return; } - - uint32_t len; - if (cdc->tx_buf_ptr_out > cdc->tx_buf_ptr_in) { // rollback - len = USBD_CDC_TX_DATA_SIZE - cdc->tx_buf_ptr_out; - } else { - len = cdc->tx_buf_ptr_in - cdc->tx_buf_ptr_out; - } - + uint16_t len = usbd_cdc_tx_send_length(cdc); // Should always succeed because cdc.base.tx_in_progress==0 - USBD_CDC_TransmitPacket(&cdc->base, len, &cdc->tx_buf[cdc->tx_buf_ptr_out]); + USBD_CDC_TransmitPacket(&cdc->base, len, usbd_cdc_tx_buffer_getp(cdc, len)); - cdc->tx_buf_ptr_out_shadow += len; - if (cdc->tx_buf_ptr_out_shadow == USBD_CDC_TX_DATA_SIZE) { - cdc->tx_buf_ptr_out_shadow = 0; - } // According to the USB specification, a packet size of 64 bytes (CDC_DATA_FS_MAX_PACKET_SIZE) // gets held at the USB host until the next packet is sent. This is because a @@ -204,7 +228,7 @@ void usbd_cdc_tx_ready(usbd_cdc_state_t *cdc_in) { // the host waits for all data to arrive (ie, waits for a packet < max packet size). // To flush a packet of exactly max packet size, we need to send a zero-size packet. // See eg http://www.cypress.com/?id=4&rID=92719 - cdc->tx_need_empty_packet = (len > 0 && len % usbd_cdc_max_packet(cdc->base.usbd->pdev) == 0 && cdc->tx_buf_ptr_out_shadow == cdc->tx_buf_ptr_in); + cdc->tx_need_empty_packet = (len > 0 && len % usbd_cdc_max_packet(cdc->base.usbd->pdev) == 0 && usbd_cdc_tx_buffer_will_be_empty(cdc)); } // Attempt to queue data on the USB IN endpoint @@ -291,10 +315,7 @@ int8_t usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) { } int usbd_cdc_tx_half_empty(usbd_cdc_itf_t *cdc) { - int32_t tx_waiting = (int32_t)cdc->tx_buf_ptr_in - (int32_t)cdc->tx_buf_ptr_out; - if (tx_waiting < 0) { - tx_waiting += USBD_CDC_TX_DATA_SIZE; - } + int32_t tx_waiting = usbd_cdc_tx_buffer_size(cdc); return tx_waiting <= USBD_CDC_TX_DATA_SIZE / 2; } @@ -317,8 +338,7 @@ int usbd_cdc_tx(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len, uint32_t for (uint32_t i = 0; i < len; i++) { // Wait until the device is connected and the buffer has space, with a given timeout uint32_t start = HAL_GetTick(); - while (cdc->connect_state == USBD_CDC_CONNECT_STATE_DISCONNECTED - || ((cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1)) == cdc->tx_buf_ptr_out) { + while (cdc->connect_state == USBD_CDC_CONNECT_STATE_DISCONNECTED || usbd_cdc_tx_buffer_full(cdc)) { usbd_cdc_try_tx(cdc); // Wraparound of tick is taken care of by 2's complement arithmetic. if (HAL_GetTick() - start >= timeout) { @@ -333,8 +353,7 @@ int usbd_cdc_tx(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len, uint32_t } // Write data to device buffer - cdc->tx_buf[cdc->tx_buf_ptr_in] = buf[i]; - cdc->tx_buf_ptr_in = (cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1); + usbd_cdc_tx_buffer_put(cdc, buf[i]); } usbd_cdc_try_tx(cdc); @@ -357,7 +376,7 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { // If the buffer is full, wait until it gets drained, with a timeout of 500ms // (wraparound of tick is taken care of by 2's complement arithmetic). uint32_t start = HAL_GetTick(); - while (((cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1)) == cdc->tx_buf_ptr_out && HAL_GetTick() - start <= 500) { + while (usbd_cdc_tx_buffer_full(cdc) && HAL_GetTick() - start <= 500) { usbd_cdc_try_tx(cdc); if (query_irq() == IRQ_STATE_DISABLED) { // IRQs disabled so buffer will never be drained; exit loop @@ -367,8 +386,7 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { } } - cdc->tx_buf[cdc->tx_buf_ptr_in] = buf[i]; - cdc->tx_buf_ptr_in = (cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1); + usbd_cdc_tx_buffer_put(cdc, buf[i]); } usbd_cdc_try_tx(cdc); } diff --git a/ports/stm32/usbd_cdc_interface.h b/ports/stm32/usbd_cdc_interface.h index 1847b3a6b..d0509b09f 100644 --- a/ports/stm32/usbd_cdc_interface.h +++ b/ports/stm32/usbd_cdc_interface.h @@ -35,7 +35,7 @@ #define USBD_CDC_RX_DATA_SIZE (1024) // this must be 2 or greater, and a power of 2 #endif #ifndef USBD_CDC_TX_DATA_SIZE -#define USBD_CDC_TX_DATA_SIZE (1024) // I think this can be any value (was 2048) +#define USBD_CDC_TX_DATA_SIZE (1024) // This must be a power of 2 and no greater than 16384 #endif // Values for connect_state @@ -60,7 +60,7 @@ typedef struct _usbd_cdc_itf_t { uint8_t tx_buf[USBD_CDC_TX_DATA_SIZE]; // data for USB IN endpoind is stored in this buffer uint16_t tx_buf_ptr_in; // increment this pointer modulo USBD_CDC_TX_DATA_SIZE when new data is available volatile uint16_t tx_buf_ptr_out; // increment this pointer modulo USBD_CDC_TX_DATA_SIZE when data is drained - uint16_t tx_buf_ptr_out_shadow; // shadow of above + uint16_t tx_buf_ptr_out_next; // next position of above once transmission finished uint8_t tx_need_empty_packet; // used to flush the USB IN endpoint if the last packet was exactly the endpoint packet size volatile uint8_t connect_state; // indicates if we are connected diff --git a/ports/stm32/usbd_conf.h b/ports/stm32/usbd_conf.h index 5237ba3a9..83805a6e4 100644 --- a/ports/stm32/usbd_conf.h +++ b/ports/stm32/usbd_conf.h @@ -51,7 +51,7 @@ // For MCUs with a device-only USB peripheral #define USBD_PMA_RESERVE (64) -#define USBD_PMA_NUM_FIFO (8) +#define USBD_PMA_NUM_FIFO (16) // Maximum 8 endpoints, 2 FIFOs each // For MCUs with multiple OTG USB peripherals #define USBD_FS_NUM_TX_FIFO (6) diff --git a/ports/stm32/usbd_msc_interface.h b/ports/stm32/usbd_msc_interface.h index 411c707ca..9d25a72a3 100644 --- a/ports/stm32/usbd_msc_interface.h +++ b/ports/stm32/usbd_msc_interface.h @@ -26,8 +26,6 @@ #ifndef MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H #define MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H -#define USBD_MSC_MAX_LUN (2) - extern const USBD_StorageTypeDef usbd_msc_fops; void usbd_msc_init_lu(size_t lu_n, const void *lu_data); diff --git a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h index d934f4676..d6c38bbd0 100644 --- a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h +++ b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h @@ -34,6 +34,9 @@ #define MSC_MEDIA_PACKET (2048) // was 8192; how low can it go whilst still working? #define HID_DATA_FS_MAX_PACKET_SIZE (64) // endpoint IN & OUT packet size +// Maximum number of LUN that can be exposed on the MSC interface +#define USBD_MSC_MAX_LUN (2) + // Need to define here for BOT and SCSI layers #define MSC_IN_EP (0x81) #define MSC_OUT_EP (0x01) @@ -78,8 +81,8 @@ typedef struct { uint8_t scsi_sense_head; uint8_t scsi_sense_tail; - uint16_t scsi_blk_size; - uint32_t scsi_blk_nbr; + uint16_t scsi_blk_size[USBD_MSC_MAX_LUN]; + uint32_t scsi_blk_nbr[USBD_MSC_MAX_LUN]; uint32_t scsi_blk_addr_in_blks; uint32_t scsi_blk_len; diff --git a/ports/stm32/usbdev/class/src/usbd_msc_scsi.c b/ports/stm32/usbdev/class/src/usbd_msc_scsi.c index d0413b758..2eb716ccd 100644 --- a/ports/stm32/usbdev/class/src/usbd_msc_scsi.c +++ b/ports/stm32/usbdev/class/src/usbd_msc_scsi.c @@ -247,7 +247,7 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_ { USBD_MSC_BOT_HandleTypeDef *hmsc = &((usbd_cdc_msc_hid_state_t*)pdev->pClassData)->MSC_BOT_ClassData; - if(hmsc->bdev_ops->GetCapacity(lun, &hmsc->scsi_blk_nbr, &hmsc->scsi_blk_size) != 0) + if(hmsc->bdev_ops->GetCapacity(lun, &hmsc->scsi_blk_nbr[lun], &hmsc->scsi_blk_size[lun]) != 0) { SCSI_SenseCode(pdev, lun, @@ -258,15 +258,17 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_ else { - hmsc->bot_data[0] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 24); - hmsc->bot_data[1] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 16); - hmsc->bot_data[2] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 8); - hmsc->bot_data[3] = (uint8_t)(hmsc->scsi_blk_nbr - 1); + uint32_t blk_nbr = hmsc->scsi_blk_nbr[lun]; + hmsc->bot_data[0] = (uint8_t)((blk_nbr - 1) >> 24); + hmsc->bot_data[1] = (uint8_t)((blk_nbr - 1) >> 16); + hmsc->bot_data[2] = (uint8_t)((blk_nbr - 1) >> 8); + hmsc->bot_data[3] = (uint8_t)(blk_nbr - 1); - hmsc->bot_data[4] = (uint8_t)(hmsc->scsi_blk_size >> 24); - hmsc->bot_data[5] = (uint8_t)(hmsc->scsi_blk_size >> 16); - hmsc->bot_data[6] = (uint8_t)(hmsc->scsi_blk_size >> 8); - hmsc->bot_data[7] = (uint8_t)(hmsc->scsi_blk_size); + uint32_t blk_size = hmsc->scsi_blk_size[lun]; + hmsc->bot_data[4] = (uint8_t)(blk_size >> 24); + hmsc->bot_data[5] = (uint8_t)(blk_size >> 16); + hmsc->bot_data[6] = (uint8_t)(blk_size >> 8); + hmsc->bot_data[7] = (uint8_t)(blk_size); hmsc->bot_data_length = 8; return 0; @@ -516,7 +518,7 @@ static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun , uint8_t *para } hmsc->bot_state = USBD_BOT_DATA_IN; - hmsc->scsi_blk_len *= hmsc->scsi_blk_size; + hmsc->scsi_blk_len *= hmsc->scsi_blk_size[lun]; /* cases 4,5 : Hi <> Dn */ if (hmsc->cbw.dDataLength != hmsc->scsi_blk_len) @@ -596,7 +598,7 @@ static int8_t SCSI_Write10 (USBD_HandleTypeDef *pdev, uint8_t lun , uint8_t *pa return -1; /* error */ } - hmsc->scsi_blk_len *= hmsc->scsi_blk_size; + hmsc->scsi_blk_len *= hmsc->scsi_blk_size[lun]; /* cases 3,11,13 : Hn,Ho <> D0 */ if (hmsc->cbw.dDataLength != hmsc->scsi_blk_len) @@ -670,7 +672,7 @@ static int8_t SCSI_CheckAddressRange (USBD_HandleTypeDef *pdev, uint8_t lun , u { USBD_MSC_BOT_HandleTypeDef *hmsc = &((usbd_cdc_msc_hid_state_t*)pdev->pClassData)->MSC_BOT_ClassData; - if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr ) + if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr[lun]) { SCSI_SenseCode(pdev, lun, @@ -697,7 +699,7 @@ static int8_t SCSI_ProcessRead (USBD_HandleTypeDef *pdev, uint8_t lun) if( hmsc->bdev_ops->Read(lun , hmsc->bot_data, hmsc->scsi_blk_addr_in_blks, - len / hmsc->scsi_blk_size) < 0) + len / hmsc->scsi_blk_size[lun]) < 0) { SCSI_SenseCode(pdev, @@ -714,7 +716,7 @@ static int8_t SCSI_ProcessRead (USBD_HandleTypeDef *pdev, uint8_t lun) len); - hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size; + hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size[lun]; hmsc->scsi_blk_len -= len; /* case 6 : Hi = Di */ @@ -744,7 +746,7 @@ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) if(hmsc->bdev_ops->Write(lun , hmsc->bot_data, hmsc->scsi_blk_addr_in_blks, - len / hmsc->scsi_blk_size) < 0) + len / hmsc->scsi_blk_size[lun]) < 0) { SCSI_SenseCode(pdev, lun, @@ -754,7 +756,7 @@ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) } - hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size; + hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size[lun]; hmsc->scsi_blk_len -= len; /* case 12 : Ho = Do */ diff --git a/ports/teensy/modpyb.c b/ports/teensy/modpyb.c index 26e3f4c43..f4384a885 100644 --- a/ports/teensy/modpyb.c +++ b/ports/teensy/modpyb.c @@ -36,7 +36,6 @@ #include "lib/utils/pyexec.h" #include "gccollect.h" -#include "irq.h" #include "systick.h" #include "led.h" #include "pin.h" @@ -53,6 +52,7 @@ #include "dac.h" #include "usb.h" #include "portmodules.h" +#include "modmachine.h" /// \module pyb - functions related to the pyboard /// @@ -230,6 +230,12 @@ STATIC mp_obj_t pyb_udelay(mp_obj_t usec_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_udelay_obj, pyb_udelay); +STATIC mp_obj_t pyb_wfi(void) { + __WFI(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(pyb_wfi_obj, pyb_wfi); + STATIC mp_obj_t pyb_stop(void) { printf("stop not currently implemented\n"); return mp_const_none; @@ -285,8 +291,8 @@ STATIC const mp_rom_map_elem_t pyb_module_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&pyb_wfi_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&pyb_stop_obj) }, { MP_ROM_QSTR(MP_QSTR_standby), MP_ROM_PTR(&pyb_standby_obj) }, diff --git a/ports/unix/Makefile b/ports/unix/Makefile index ec1416614..3e2fa63a1 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -14,7 +14,7 @@ include ../../py/mkenv.mk include $(VARIANT_DIR)/mpconfigvariant.mk # use FROZEN_MANIFEST for new projects, others are legacy -FROZEN_MANIFEST ?= manifest.py +FROZEN_MANIFEST ?= variants/manifest.py FROZEN_DIR = FROZEN_MPY_DIR = diff --git a/ports/unix/btstack_usb.c b/ports/unix/btstack_usb.c index da9d72fe1..ab6a49f39 100644 --- a/ports/unix/btstack_usb.c +++ b/ports/unix/btstack_usb.c @@ -110,7 +110,23 @@ void mp_bluetooth_btstack_port_init(void) { btstack_run_loop_embedded_get_instance()->init(); } - // TODO: allow setting USB device path via cmdline/env var. + // 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); @@ -140,7 +156,10 @@ STATIC void *btstack_thread(void *arg) { // 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. diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c new file mode 100644 index 000000000..316a8831f --- /dev/null +++ b/ports/unix/mpbthciport.c @@ -0,0 +1,235 @@ +/* + * 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 "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) + +#if !MICROPY_PY_THREAD +#error Unix HCI UART requires MICROPY_PY_THREAD +#endif + +#include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" + +#include +#include + +#include +#include +#include +#include + +#define DEBUG_printf(...) // printf(__VA_ARGS__) +#define DEBUG_HCI_DUMP (0) + +uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). +extern bool mp_bluetooth_hci_poll(void); + +STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; + +STATIC int uart_fd = -1; +STATIC pthread_t hci_poll_thread_id; + +STATIC void *hci_poll_thread(void *arg) { + (void)arg; + + // This will return false when the stack is shutdown. + while (mp_bluetooth_hci_poll()) { + usleep(UART_POLL_INTERVAL_US); + } + + return NULL; +} + +STATIC int configure_uart(void) { + struct termios toptions; + + // Get existing config. + if (tcgetattr(uart_fd, &toptions) < 0) { + DEBUG_printf("Couldn't get term attributes"); + return -1; + } + + // Raw mode (disable all processing). + cfmakeraw(&toptions); + + // 8N1, no parity. + toptions.c_cflag &= ~CSTOPB; + toptions.c_cflag |= CS8; + toptions.c_cflag &= ~PARENB; + + // Enable receiver, ignore modem control lines + toptions.c_cflag |= CREAD | CLOCAL; + + // Blocking, single-byte reads. + toptions.c_cc[VMIN] = 1; + toptions.c_cc[VTIME] = 0; + + // Enable HW RTS/CTS flow control. + toptions.c_iflag &= ~(IXON | IXOFF | IXANY); + toptions.c_cflag |= CRTSCTS; + + // 1Mbit (TODO: make this configurable). + speed_t brate = B1000000; + cfsetospeed(&toptions, brate); + cfsetispeed(&toptions, brate); + + // Apply immediately. + if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) { + DEBUG_printf("Couldn't set term attributes"); + + close(uart_fd); + uart_fd = -1; + + return -1; + } + + return 0; +} + +// HCI UART bindings. +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + (void)port; + (void)baudrate; + + DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n"); + + char uart_device_name[256] = "/dev/ttyUSB0"; + + char *path = getenv("MICROPYBTUART"); + if (path != NULL) { + strcpy(uart_device_name, path); + } + DEBUG_printf("mp_bluetooth_hci_uart_init: Using HCI UART: %s\n", uart_device_name); + + int flags = O_RDWR | O_NOCTTY | O_NONBLOCK; + uart_fd = open(uart_device_name, flags); + if (uart_fd == -1) { + printf("mp_bluetooth_hci_uart_init: Unable to open port %s\n", uart_device_name); + return -1; + } + + if (configure_uart()) { + return -1; + } + + // Create a thread to run the polling loop. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit\n"); + + if (uart_fd == -1) { + return 0; + } + + // Wait for the poll loop to terminate when the state is set to OFF. + pthread_join(hci_poll_thread_id, NULL); + + // Close the UART. + close(uart_fd); + uart_fd = -1; + + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + (void)baudrate; + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate\n"); + return 0; +} + +int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n"); + + if (uart_fd == -1) { + return -1; + } + + uint8_t c; + ssize_t bytes_read = read(uart_fd, &c, 1); + + if (bytes_read == 1) { + #if DEBUG_HCI_DUMP + printf("[% 8ld] RX: %02x\n", mp_hal_ticks_ms(), c); + #endif + return c; + } else { + return -1; + } +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write\n"); + + if (uart_fd == -1) { + return 0; + } + + #if DEBUG_HCI_DUMP + printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); + for (size_t i = 1; i < len; ++i) { + printf(":%02x", buf[i]); + } + printf("\n"); + #endif + + return write(uart_fd, buf, len); +} + +// No-op implementations of HCI controller interface. +int mp_bluetooth_hci_controller_init(void) { + return 0; +} + +int mp_bluetooth_hci_controller_deinit(void) { + return 0; +} + +int mp_bluetooth_hci_controller_sleep_maybe(void) { + return 0; +} + +bool mp_bluetooth_hci_controller_woken(void) { + return true; +} + +int mp_bluetooth_hci_controller_wakeup(void) { + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) diff --git a/ports/unix/mpbtstackport.h b/ports/unix/mpbtstackport.h new file mode 100644 index 000000000..c82e8bd81 --- /dev/null +++ b/ports/unix/mpbtstackport.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H +#define MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +bool mp_bluetooth_hci_poll(void); + +#if MICROPY_BLUETOOTH_BTSTACK_H4 +void mp_bluetooth_hci_poll_h4(void); +void mp_bluetooth_btstack_port_init_h4(void); +#endif + +#if MICROPY_BLUETOOTH_BTSTACK_USB +void mp_bluetooth_btstack_port_init_usb(void); +#endif + +#endif // MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c new file mode 100644 index 000000000..621e661f9 --- /dev/null +++ b/ports/unix/mpbtstackport_common.c @@ -0,0 +1,96 @@ +/* + * 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 "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" + +#include "mpbtstackport.h" + +// Called by the UART polling thread in mpbthciport.c, or by the USB polling thread in mpbtstackport_usb.c. +bool mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + // 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(); + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_hci_poll_h4(); + #endif + btstack_run_loop_embedded_execute_once(); + MICROPY_END_ATOMIC_SECTION(atomic_state); + + return true; + } + + return false; +} + +// 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(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_btstack_port_init_h4(); + #endif + + #if MICROPY_BLUETOOTH_BTSTACK_USB + mp_bluetooth_btstack_port_init_usb(); + #endif +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/unix/mpbtstackport_h4.c b/ports/unix/mpbtstackport_h4.c new file mode 100644 index 000000000..4fdc20c22 --- /dev/null +++ b/ports/unix/mpbtstackport_h4.c @@ -0,0 +1,80 @@ +/* + * 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 "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 + +#include "lib/btstack/chipset/zephyr/btstack_chipset_zephyr.h" + +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +STATIC hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + 1000000, // initial baudrate + 0, // main baudrate + 1, // flow control + NULL, // device name +}; + +void mp_bluetooth_hci_poll_h4(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + mp_bluetooth_btstack_hci_uart_process(); + } +} + +void mp_bluetooth_btstack_port_init_h4(void) { + DEBUG_printf("mp_bluetooth_btstack_port_init_h4\n"); + + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + hci_set_chipset(btstack_chipset_zephyr_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + DEBUG_printf("mp_bluetooth_btstack_port_deinit\n"); + + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + DEBUG_printf("mp_bluetooth_btstack_port_start\n"); + + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 diff --git a/ports/unix/mpbtstackport_usb.c b/ports/unix/mpbtstackport_usb.c new file mode 100644 index 000000000..28d2c8c54 --- /dev/null +++ b/ports/unix/mpbtstackport_usb.c @@ -0,0 +1,116 @@ +/* + * 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 && MICROPY_BLUETOOTH_BTSTACK_USB + +#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" + +#include "mpbtstackport.h" + +#if !MICROPY_PY_THREAD +#error Unix btstack requires MICROPY_PY_THREAD +#endif + +STATIC const useconds_t USB_POLL_INTERVAL_US = 1000; + +void mp_bluetooth_btstack_port_init_usb(void) { + // 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_init(hci_transport_usb_instance(), NULL); +} + +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); +} + + +// Provided by mpbstackport_common.c. +extern bool mp_bluetooth_hci_poll(void); + +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 (true) { + if (!mp_bluetooth_hci_poll()) { + break; + } + + // 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); + } + 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); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB diff --git a/ports/unix/mpnimbleport.c b/ports/unix/mpnimbleport.c new file mode 100644 index 000000000..896191009 --- /dev/null +++ b/ports/unix/mpnimbleport.c @@ -0,0 +1,84 @@ +/* + * 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 "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#include "nimble/nimble_npl.h" + +#include "extmod/nimble/modbluetooth_nimble.h" +#include "extmod/nimble/hal/hal_uart.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +// Called by the UART polling thread in mpbthciport.c. +bool mp_bluetooth_hci_poll(void) { + // DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) %d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) -- shutdown\n"); + return false; + } + + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + + // 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(); + + // Ask NimBLE to process UART data. + mp_bluetooth_nimble_hci_uart_process(); + + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); + + MICROPY_END_ATOMIC_SECTION(atomic_state); + } + + return true; +} + +// Extra port-specific helpers. +void mp_bluetooth_nimble_hci_uart_wfi(void) { + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_wfi\n"); + // TODO: this should do a select() on the uart_fd. +} + +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_enter_critical\n"); + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; // Always 0xffffffff +} + +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_exit_critical\n"); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/unix/mpnimbleport.h b/ports/unix/mpnimbleport.h new file mode 100644 index 000000000..a2935e6fd --- /dev/null +++ b/ports/unix/mpnimbleport.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H +#define MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +#endif // MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 711cf2a6b..de0f5923b 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -65,7 +65,7 @@ STATIC pthread_key_t tls_key; // 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 = PTHREAD_MUTEX_INITIALIZER; +STATIC pthread_mutex_t thread_mutex; STATIC thread_t *thread; // this is used to synchronise the signal handler of the thread @@ -111,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(); diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index ddb5027a9..66e694e0a 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -11,7 +11,7 @@ CFLAGS += \ LDFLAGS += -fprofile-arcs -ftest-coverage -FROZEN_MANIFEST = manifest_coverage.py +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 diff --git a/ports/unix/variants/dev/mpconfigvariant.h b/ports/unix/variants/dev/mpconfigvariant.h index eb6513471..7c3e84cc4 100644 --- a/ports/unix/variants/dev/mpconfigvariant.h +++ b/ports/unix/variants/dev/mpconfigvariant.h @@ -24,9 +24,22 @@ * THE SOFTWARE. */ -#define MICROPY_REPL_EMACS_WORDS_MOVE (1) -#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (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_ENABLE_SCHEDULER (1) +#define MICROPY_VFS (1) +#define MICROPY_VFS_POSIX (1) -#define MICROPY_PY_SYS_SETTRACE (1) -#define MICROPY_PY_URANDOM_EXTRA_FUNCS (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 index bbd30b623..1f8611b6f 100644 --- a/ports/unix/variants/dev/mpconfigvariant.mk +++ b/ports/unix/variants/dev/mpconfigvariant.mk @@ -1,5 +1,9 @@ PROG ?= micropython-dev -MICROPY_ROM_TEXT_COMPRESSION = 1 +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/variants/manifest.py b/ports/unix/variants/manifest.py new file mode 100644 index 000000000..666b4c0ab --- /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/wapy-unix/Makefile b/ports/wapy-unix/Makefile new file mode 100644 index 000000000..b74f0467d --- /dev/null +++ b/ports/wapy-unix/Makefile @@ -0,0 +1,303 @@ +# 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 + +# use FROZEN_MANIFEST for new projects, others are legacy +FROZEN_MANIFEST ?= manifest.py +FROZEN_DIR = +FROZEN_MPY_DIR = + +# This should be configured by the mpconfigvariant.mk +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) + +# include py core make definitions +include $(TOP)/py/py.mk + +GIT_SUBMODULES += lib/axtls lib/berkeley-db-1.xx lib/libffi + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) + +# compiler settings +CWARN = -Wall -Werror +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 +COPT ?= -O0 +else +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. +# +# gcc already optimizes some printf calls to call puts and/or putchar. When +# _FORTIFY_SOURCE is enabled and compiling with -O1 or greater, then some +# printf calls will also be optimized to call __printf_chk (in glibc). Any +# printfs which get redirected to __printf_chk are then no longer synchronized +# with printfs that go through mp_printf. +# +# In MicroPython, we don't want to use the runtime library's printf but rather +# go through mp_printf, so that stdout is properly tied into streams, etc. +# This means that we either need to turn off _FORTIFY_SOURCE or provide our +# own implementation of __printf_chk. We've chosen to turn off _FORTIFY_SOURCE. +# It should also be noted that the use of printf in MicroPython is typically +# quite limited anyways (primarily for debug and some error reporting, etc +# in the unix version). +# +# Information about _FORTIFY_SOURCE seems to be rather scarce. The best I could +# find was this: https://securityblog.redhat.com/2014/03/26/fortify-and-you/ +# Original patchset was introduced by +# https://gcc.gnu.org/ml/gcc-patches/2004-09/msg02055.html . +# +# Turning off _FORTIFY_SOURCE is only required when compiling with -O1 or greater +CFLAGS += -U _FORTIFY_SOURCE +endif + +# On OSX, 'gcc' is a symlink to clang unless a real gcc is installed. +# The unix port of MicroPython on OSX must be compiled with clang, +# while cross-compile ports require gcc, so we test here for OSX and +# if necessary override the value of 'CC' set in py/mkenv.mk +ifeq ($(UNAME_S),Darwin) +ifeq ($(MICROPY_FORCE_32BIT),1) +CC = clang -m32 +else +CC = clang +endif +# Use clang syntax for map file +LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip +else +# Use gcc syntax for map file +LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections +endif +LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) + +# Flags to link with pthread library +LIBPTHREAD = -lpthread + +ifeq ($(MICROPY_FORCE_32BIT),1) +# Note: you may need to install i386 versions of dependency packages, +# starting with linux-libc-dev:i386 +ifeq ($(MICROPY_PY_FFI),1) +ifeq ($(UNAME_S),Linux) +CFLAGS_MOD += -I/usr/include/i686-linux-gnu +endif +endif +endif + +ifeq ($(MICROPY_USE_READLINE),1) +INC += -I$(TOP)/lib/mp-readline +CFLAGS_MOD += -DMICROPY_USE_READLINE=1 +LIB_SRC_C_EXTRA += mp-readline/readline.c +endif +ifeq ($(MICROPY_PY_TERMIOS),1) +CFLAGS_MOD += -DMICROPY_PY_TERMIOS=1 +SRC_MOD += modtermios.c +endif +ifeq ($(MICROPY_PY_SOCKET),1) +CFLAGS_MOD += -DMICROPY_PY_SOCKET=1 +SRC_MOD += modusocket.c +endif +ifeq ($(MICROPY_PY_THREAD),1) +CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 +LDFLAGS_MOD += $(LIBPTHREAD) +endif + +# If the variant enables it and we have libusb, enable BTStack support for USB adaptors. +ifeq ($(MICROPY_PY_BLUETOOTH),1) + +HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1') +ifeq ($(HAVE_LIBUSB),1) + +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=1 + +MICROPY_BLUETOOTH_BTSTACK ?= 1 +MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 + +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +GIT_SUBMODULES += lib/btstack + +include $(TOP)/extmod/btstack/btstack.mk +endif + +endif +endif + +ifeq ($(MICROPY_PY_FFI),1) + +ifeq ($(MICROPY_STANDALONE),1) +LIBFFI_CFLAGS_MOD := -I$(shell ls -1d $(BUILD)/lib/libffi/out/lib/libffi-*/include) + ifeq ($(MICROPY_FORCE_32BIT),1) + LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib32/libffi.a + else + LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib/libffi.a + endif +else +LIBFFI_CFLAGS_MOD := $(shell pkg-config --cflags libffi) +LIBFFI_LDFLAGS_MOD := $(shell pkg-config --libs libffi) +endif + +ifeq ($(UNAME_S),Linux) +LIBFFI_LDFLAGS_MOD += -ldl +endif + +CFLAGS_MOD += $(LIBFFI_CFLAGS_MOD) -DMICROPY_PY_FFI=1 +LDFLAGS_MOD += $(LIBFFI_LDFLAGS_MOD) +SRC_MOD += modffi.c +endif + +ifeq ($(MICROPY_PY_JNI),1) +# Path for 64-bit OpenJDK, should be adjusted for other JDKs +CFLAGS_MOD += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 +SRC_MOD += modjni.c +endif + +include ../wapy/wapy.mk + +# source files + + +SRC_C = \ + main.c \ + file.c \ + gccollect.c \ + unix_mphal.c \ + mpthreadport.c \ + input.c \ + modmachine.c \ + modos.c \ + moduos_vfs.c \ + modtime.c \ + moduselect.c \ + alloc.c \ + coverage.c \ + fatfs_port.c \ + btstack_usb.c \ + $(SRC_MOD) \ + $(wildcard $(VARIANT_DIR)/*.c) + +LIB_SRC_C += $(addprefix lib/,\ + $(LIB_SRC_C_EXTRA) \ + timeutils/timeutils.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) $(EXTMOD_SRC_C) +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += + +ifneq ($(FROZEN_MANIFEST)$(FROZEN_MPY_DIR),) +# To use frozen code create a manifest.py file with a description of files to +# freeze, then invoke make with FROZEN_MANIFEST=manifest.py (be sure to build from scratch). +CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CFLAGS += -DMICROPY_MODULE_FROZEN_MPY +CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs +MPY_CROSS_FLAGS += -mcache-lookup-bc +endif + +ifneq ($(FROZEN_MANIFEST)$(FROZEN_DIR),) +CFLAGS += -DMICROPY_MODULE_FROZEN_STR +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 test_full + +test: $(PROG) $(TOP)/tests/run-tests + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests + +test_full: $(PROG) $(TOP)/tests/run-tests + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + 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 + +# Value of configure's --host= option (required for cross-compilation). +# Deduce it from CROSS_COMPILE by default, but can be overridden. +ifneq ($(CROSS_COMPILE),) +CROSS_COMPILE_HOST = --host=$(patsubst %-,%,$(CROSS_COMPILE)) +else +CROSS_COMPILE_HOST = +endif + +deplibs: libffi axtls + +libffi: $(BUILD)/lib/libffi/include/ffi.h + +$(TOP)/lib/libffi/configure: $(TOP)/lib/libffi/autogen.sh + cd $(TOP)/lib/libffi; ./autogen.sh + +# install-exec-recursive & install-data-am targets are used to avoid building +# docs and depending on makeinfo +$(BUILD)/lib/libffi/include/ffi.h: $(TOP)/lib/libffi/configure + mkdir -p $(BUILD)/lib/libffi; cd $(BUILD)/lib/libffi; \ + $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ + $(MAKE) install-exec-recursive; $(MAKE) -C include install-data-am + +axtls: $(TOP)/lib/axtls/README + +$(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) diff --git a/ports/wapy-unix/alloc.c b/ports/wapy-unix/alloc.c new file mode 100644 index 000000000..7fe7b4dba --- /dev/null +++ b/ports/wapy-unix/alloc.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Fabian Vogt + * + * 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 "py/mpstate.h" +#include "py/gc.h" + +#if MICROPY_EMIT_NATIVE || (MICROPY_PY_FFI && MICROPY_FORCE_PLAT_ALLOC_EXEC) + +#if defined(__OpenBSD__) || defined(__MACH__) +#define MAP_ANONYMOUS MAP_ANON +#endif + +// The memory allocated here is not on the GC heap (and it may contain pointers +// that need to be GC'd) so we must somehow trace this memory. We do it by +// keeping a linked list of all mmap'd regions, and tracing them explicitly. + +typedef struct _mmap_region_t { + void *ptr; + size_t len; + struct _mmap_region_t *next; +} mmap_region_t; + +void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size) { + // size needs to be a multiple of the page size + *size = (min_size + 0xfff) & (~0xfff); + *ptr = mmap(NULL, *size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (*ptr == MAP_FAILED) { + *ptr = NULL; + } + + // add new link to the list of mmap'd regions + mmap_region_t *rg = m_new_obj(mmap_region_t); + rg->ptr = *ptr; + rg->len = min_size; + rg->next = MP_STATE_VM(mmap_region_head); + MP_STATE_VM(mmap_region_head) = rg; +} + +void mp_unix_free_exec(void *ptr, size_t size) { + munmap(ptr, size); + + // unlink the mmap'd region from the list + for (mmap_region_t **rg = (mmap_region_t **)&MP_STATE_VM(mmap_region_head); *rg != NULL; *rg = (*rg)->next) { + if ((*rg)->ptr == ptr) { + mmap_region_t *next = (*rg)->next; + m_del_obj(mmap_region_t, *rg); + *rg = next; + return; + } + } +} + +void mp_unix_mark_exec(void) { + for (mmap_region_t *rg = MP_STATE_VM(mmap_region_head); rg != NULL; rg = rg->next) { + gc_collect_root(rg->ptr, rg->len / sizeof(mp_uint_t)); + } +} + +#if MICROPY_FORCE_PLAT_ALLOC_EXEC +// Provide implementation of libffi ffi_closure_* functions in terms +// of the functions above. On a normal Linux system, this save a lot +// of code size. +void *ffi_closure_alloc(size_t size, void **code); +void ffi_closure_free(void *ptr); + +void *ffi_closure_alloc(size_t size, void **code) { + size_t dummy; + mp_unix_alloc_exec(size, code, &dummy); + return *code; +} + +void ffi_closure_free(void *ptr) { + (void)ptr; + // TODO +} +#endif + +#endif // MICROPY_EMIT_NATIVE || (MICROPY_PY_FFI && MICROPY_FORCE_PLAT_ALLOC_EXEC) diff --git a/ports/wapy-unix/aosp_mphal.c b/ports/wapy-unix/aosp_mphal.c new file mode 100644 index 000000000..ae23bb6f1 --- /dev/null +++ b/ports/wapy-unix/aosp_mphal.c @@ -0,0 +1,148 @@ +#include +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif + +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i +#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(); + } + + // TODO: allow setting USB device path via cmdline/env var. + + // 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) { + btstack_run_loop_embedded_execute_once(); + + // 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/wapy-unix/coverage-frzmpy/frzmpy1.py b/ports/wapy-unix/coverage-frzmpy/frzmpy1.py new file mode 100644 index 000000000..8ad0f1573 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy1.py @@ -0,0 +1 @@ +print('frzmpy1') diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy2.py b/ports/wapy-unix/coverage-frzmpy/frzmpy2.py new file mode 100644 index 000000000..1ad930db2 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy2.py @@ -0,0 +1 @@ +raise ZeroDivisionError diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py new file mode 100644 index 000000000..8c023afeb --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzmpy_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py new file mode 100644 index 000000000..a66b505bf --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzmpy_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/coverage-frzmpy/frzqstr.py b/ports/wapy-unix/coverage-frzmpy/frzqstr.py new file mode 100644 index 000000000..051f2a9c1 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzqstr.py @@ -0,0 +1,3 @@ +# Checks for regression on MP_QSTR_NULL +def returns_NULL(): + return "NULL" diff --git a/ports/wapy-unix/coverage-frzstr/frzstr1.py b/ports/wapy-unix/coverage-frzstr/frzstr1.py new file mode 100644 index 000000000..6e88ac38d --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr1.py @@ -0,0 +1 @@ +print('frzstr1') diff --git a/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py b/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py new file mode 100644 index 000000000..1d1df9417 --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzstr_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py b/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py new file mode 100644 index 000000000..bafb5978b --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzstr_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/coverage.c b/ports/wapy-unix/coverage.c new file mode 100644 index 000000000..e239abe94 --- /dev/null +++ b/ports/wapy-unix/coverage.c @@ -0,0 +1,641 @@ +#include +#include + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/repl.h" +#include "py/mpz.h" +#include "py/builtin.h" +#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 +typedef struct _mp_obj_streamtest_t { + mp_obj_base_t base; + uint8_t *buf; + size_t len; + size_t pos; + int error_code; +} mp_obj_streamtest_t; + +STATIC mp_obj_t stest_set_buf(mp_obj_t o_in, mp_obj_t buf_in) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + o->buf = m_new(uint8_t, bufinfo.len); + memcpy(o->buf, bufinfo.buf, bufinfo.len); + o->len = bufinfo.len; + o->pos = 0; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(stest_set_buf_obj, stest_set_buf); + +STATIC mp_obj_t stest_set_error(mp_obj_t o_in, mp_obj_t err_in) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + o->error_code = mp_obj_get_int(err_in); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(stest_set_error_obj, stest_set_error); + +STATIC mp_uint_t stest_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + if (o->pos < o->len) { + if (size > o->len - o->pos) { + size = o->len - o->pos; + } + memcpy(buf, o->buf + o->pos, size); + o->pos += size; + return size; + } else if (o->error_code == 0) { + return 0; + } else { + *errcode = o->error_code; + return MP_STREAM_ERROR; + } +} + +STATIC mp_uint_t stest_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + (void)buf; + (void)size; + *errcode = o->error_code; + return MP_STREAM_ERROR; +} + +STATIC mp_uint_t stest_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + (void)arg; + (void)request; + (void)errcode; + if (o->error_code != 0) { + *errcode = o->error_code; + return MP_STREAM_ERROR; + } + return 0; +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_set_buf), MP_ROM_PTR(&stest_set_buf_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_error), MP_ROM_PTR(&stest_set_error_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read1), MP_ROM_PTR(&mp_stream_read1_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write1), MP_ROM_PTR(&mp_stream_write1_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_ioctl), MP_ROM_PTR(&mp_stream_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); + +STATIC const mp_stream_p_t fileio_stream_p = { + .read = stest_read, + .write = stest_write, + .ioctl = stest_ioctl, +}; + +STATIC const mp_obj_type_t mp_type_stest_fileio = { + { &mp_type_type }, + .protocol = &fileio_stream_p, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, +}; + +// stream read returns non-blocking error +STATIC mp_uint_t stest_read2(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + (void)o_in; + (void)buf; + (void)size; + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table2[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict2, rawfile_locals_dict_table2); + +STATIC const mp_stream_p_t textio_stream_p2 = { + .read = stest_read2, + .write = NULL, + .is_text = true, +}; + +STATIC const mp_obj_type_t mp_type_stest_textio2 = { + { &mp_type_type }, + .protocol = &textio_stream_p2, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict2, +}; + +// str/bytes objects without a valid hash +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) + { + mp_printf(&mp_plat_print, "# mp_printf\n"); + mp_printf(&mp_plat_print, "%d %+d % d\n", -123, 123, 123); // sign + mp_printf(&mp_plat_print, "%05d\n", -123); // negative number with zero padding + mp_printf(&mp_plat_print, "%ld\n", 123); // long + mp_printf(&mp_plat_print, "%lx\n", 0x123); // long hex + mp_printf(&mp_plat_print, "%X\n", 0x1abcdef); // capital hex + mp_printf(&mp_plat_print, "%.2s %.3s\n", "abc", "abc"); // fixed string precision + mp_printf(&mp_plat_print, "%.*s\n", -1, "abc"); // negative string precision + mp_printf(&mp_plat_print, "%b %b\n", 0, 1); // bools + #ifndef NDEBUG + mp_printf(&mp_plat_print, "%s\n", NULL); // null string + #else + mp_printf(&mp_plat_print, "(null)\n"); // without debugging mp_printf won't check for null + #endif + mp_printf(&mp_plat_print, "%d\n", 0x80000000); // should print signed + mp_printf(&mp_plat_print, "%u\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "%x\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "%X\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "abc\n%"); // string ends in middle of format specifier + mp_printf(&mp_plat_print, "%%\n"); // literal % character + } + + // GC + { + mp_printf(&mp_plat_print, "# GC\n"); + + // calling gc_free while GC is locked + gc_lock(); + gc_free(NULL); + gc_unlock(); + + // using gc_realloc to resize to 0, which means free the memory + void *p = gc_alloc(4, false); + mp_printf(&mp_plat_print, "%p\n", gc_realloc(p, 0, false)); + + // calling gc_nbytes with a non-heap pointer + mp_printf(&mp_plat_print, "%p\n", gc_nbytes(NULL)); + } + + // vstr + { + mp_printf(&mp_plat_print, "# vstr\n"); + vstr_t *vstr = vstr_new(16); + vstr_hint_size(vstr, 32); + vstr_add_str(vstr, "ts"); + vstr_ins_byte(vstr, 1, 'e'); + vstr_ins_char(vstr, 3, 't'); + vstr_ins_char(vstr, 10, 's'); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_head_bytes(vstr, 2); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_tail_bytes(vstr, 10); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_printf(vstr, "t%cst", 'e'); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_out_bytes(vstr, 3, 10); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + VSTR_FIXED(fix, 4); + if (vstr_add_str(&fix, "large")) { + mp_obj_print_exception(&mp_plat_print, MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + } + + fix.len = fix.alloc; + if (vstr_null_terminated_str(&fix) == NULL) { + mp_obj_print_exception(&mp_plat_print, MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + } + } + + // repl autocomplete + { + mp_printf(&mp_plat_print, "# repl\n"); + + const char *str; + size_t len = mp_repl_autocomplete("__n", 3, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + + mp_store_global(MP_QSTR_sys, mp_import_name(MP_QSTR_sys, mp_const_none, MP_OBJ_NEW_SMALL_INT(0))); + mp_repl_autocomplete("sys.", 4, &mp_plat_print, &str); + len = mp_repl_autocomplete("sys.impl", 8, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + } + + // attrtuple + { + mp_printf(&mp_plat_print, "# attrtuple\n"); + + static const qstr fields[] = {MP_QSTR_start, MP_QSTR_stop, MP_QSTR_step}; + static const mp_obj_t items[] = {MP_OBJ_NEW_SMALL_INT(1), MP_OBJ_NEW_SMALL_INT(2), MP_OBJ_NEW_SMALL_INT(3)}; + mp_obj_print_helper(&mp_plat_print, mp_obj_new_attrtuple(fields, 3, items), PRINT_REPR); + mp_printf(&mp_plat_print, "\n"); + } + + // str + { + mp_printf(&mp_plat_print, "# str\n"); + + // intern string + mp_printf(&mp_plat_print, "%d\n", mp_obj_is_qstr(mp_obj_str_intern(mp_obj_new_str("intern me", 9)))); + } + + // bytearray + { + mp_printf(&mp_plat_print, "# bytearray\n"); + + // create a bytearray via mp_obj_new_bytearray + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(mp_obj_new_bytearray(4, "data"), &bufinfo, MP_BUFFER_RW); + mp_printf(&mp_plat_print, "%.*s\n", bufinfo.len, bufinfo.buf); + } + + // mpz + { + mp_printf(&mp_plat_print, "# mpz\n"); + + mp_uint_t value; + mpz_t mpz; + mpz_init_zero(&mpz); + + // mpz_as_uint_checked, with success + mpz_set_from_int(&mpz, 12345678); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_as_uint_checked, with negative arg + mpz_set_from_int(&mpz, -1); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + + // mpz_as_uint_checked, with overflowing arg + mpz_set_from_int(&mpz, 1); + mpz_shl_inpl(&mpz, &mpz, 70); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + + // mpz_set_from_float with inf as argument + mpz_set_from_float(&mpz, 1.0 / 0.0); + mpz_as_uint_checked(&mpz, &value); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_set_from_float with 0 as argument + mpz_set_from_float(&mpz, 0); + mpz_as_uint_checked(&mpz, &value); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_set_from_float with 0fun_bc = &fun_bc; + code_state->ip = (const byte *)"\x00"; // just needed for an invalid opcode + code_state->sp = &code_state->state[0]; + code_state->exc_sp_idx = 0; + code_state->old_globals = NULL; + mp_vm_return_kind_t ret = mp_execute_bytecode(code_state, MP_OBJ_NULL); + mp_printf(&mp_plat_print, "%d %d\n", ret, mp_obj_get_type(code_state->state[0]) == &mp_type_NotImplementedError); + } + + // scheduler + { + mp_printf(&mp_plat_print, "# scheduler\n"); + + // lock scheduler + mp_sched_lock(); + + // schedule multiple callbacks; last one should fail + for (int i = 0; i < 5; ++i) { + mp_printf(&mp_plat_print, "sched(%d)=%d\n", i, mp_sched_schedule(MP_OBJ_FROM_PTR(&mp_builtin_print_obj), MP_OBJ_NEW_SMALL_INT(i))); + } + + // test nested locking/unlocking + mp_sched_lock(); + mp_sched_unlock(); + + // shouldn't do anything while scheduler is locked + mp_handle_pending(true); + + // unlock scheduler + mp_sched_unlock(); + mp_printf(&mp_plat_print, "unlocked\n"); + + // drain pending callbacks + while (mp_sched_num_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 + { + byte buf[100]; + ringbuf_t ringbuf = {buf, sizeof(buf), 0, 0}; + + mp_printf(&mp_plat_print, "# ringbuf\n"); + + // Single-byte put/get with empty ringbuf. + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + ringbuf_put(&ringbuf, 22); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_get(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put/get with empty ringbuf. + ringbuf_put16(&ringbuf, 0xaa55); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put with full ringbuf. + for (int i = 0; i < 99; ++i) { + ringbuf_put(&ringbuf, i); + } + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x11bb)); + // Two-byte put with one byte free. + ringbuf_get(&ringbuf); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x3377)); + ringbuf_get(&ringbuf); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0xcc99)); + for (int i = 0; i < 97; ++i) { + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put with wrap around on first byte: + ringbuf.iput = 0; + ringbuf.iget = 0; + for (int i = 0; i < 99; ++i) { + ringbuf_put(&ringbuf, i); + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x11bb)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + + // Two-byte put with wrap around on second byte: + ringbuf.iput = 0; + ringbuf.iget = 0; + for (int i = 0; i < 98; ++i) { + ringbuf_put(&ringbuf, i); + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x22ff)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + + // Two-byte get from empty ringbuf. + ringbuf.iput = 0; + ringbuf.iget = 0; + mp_printf(&mp_plat_print, "%d\n", ringbuf_get16(&ringbuf)); + + // Two-byte get from ringbuf with one byte available. + ringbuf.iput = 0; + ringbuf.iget = 0; + ringbuf_put(&ringbuf, 0xaa); + 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; + s->len = 0; + s->pos = 0; + s->error_code = 0; + mp_obj_streamtest_t *s2 = m_new_obj(mp_obj_streamtest_t); + s2->base.type = &mp_type_stest_textio2; + + // return a tuple of data for testing on the Python side + mp_obj_t items[] = {(mp_obj_t)&str_no_hash_obj, (mp_obj_t)&bytes_no_hash_obj, MP_OBJ_FROM_PTR(s), MP_OBJ_FROM_PTR(s2)}; + return mp_obj_new_tuple(MP_ARRAY_SIZE(items), items); +} +MP_DEFINE_CONST_FUN_OBJ_0(extra_coverage_obj, extra_coverage); + +#endif diff --git a/ports/wapy-unix/fatfs_port.c b/ports/wapy-unix/fatfs_port.c new file mode 100644 index 000000000..30f1959f5 --- /dev/null +++ b/ports/wapy-unix/fatfs_port.c @@ -0,0 +1,5 @@ +#include "lib/oofatfs/ff.h" + +DWORD get_fattime(void) { + return 0; +} diff --git a/ports/wapy-unix/fdfile.h b/ports/wapy-unix/fdfile.h new file mode 100644 index 000000000..69a9b6be4 --- /dev/null +++ b/ports/wapy-unix/fdfile.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * 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_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/wapy-unix/file.c b/ports/wapy-unix/file.c new file mode 100644 index 000000000..2ead15947 --- /dev/null +++ b/ports/wapy-unix/file.c @@ -0,0 +1,321 @@ +/* + * 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 +#include +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#if MICROPY_PY_IO && !MICROPY_VFS + +#if MICROPY_VFS_POSIX || MICROPY_VFS_POSIX_FILE + #error "need !MICROPY_VFS_POSIX and !MICROPY_VFS_POSIX_FILE" +#endif + +#ifdef _WIN32 +#define fsync _commit +#endif + +#ifdef MICROPY_CPYTHON_COMPAT +STATIC int check_fd_is_open(const mp_obj_fdfile_t *o) { + if (o->fd < 0) { + mp_raise_ValueError_o("I/O operation on closed file"); + return 1; + } + return 0; +} +#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_str(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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + mp_int_t r = read(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + 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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + #if MICROPY_PY_OS_DUPTERM + if (o->fd <= STDERR_FILENO) { + mp_hal_stdout_tx_strn(buf, size); + return size; + } + #endif + MP_THREAD_GIL_EXIT(); + mp_int_t r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + 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; + mp_raise_o(obj); + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + } + 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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + switch (request) { + 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; + } + s->offset = off; + return 0; + } + case MP_STREAM_FLUSH: + MP_THREAD_GIL_EXIT(); + int ret = fsync(o->fd); + MP_THREAD_GIL_ENTER(); + if (ret == -1) { + if (errno == 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 = errno; + return MP_STREAM_ERROR; + } + 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; + } +} + +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); + if (check_fd_is_open(self)) { + return MP_OBJ_NULL; + } + 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_NONE} }, + { 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_NONE} }, + { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, +}; +#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); + MP_THREAD_GIL_EXIT(); + int fd = open(fname, mode_x | mode_rw, 0644); + MP_THREAD_GIL_ENTER(); + if (fd == -1) { + return mp_raise_OSError_o(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, size_t n_kw, const mp_obj_t *args) { + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all_kw_array(n_args, n_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 = { + .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 = { + .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 }; +#else +#error "error, need MICROPY_PY_IO && !MICROPY_VFS" +#endif // MICROPY_PY_IO && !MICROPY_VFS diff --git a/ports/wapy-unix/gccollect.c b/ports/wapy-unix/gccollect.c new file mode 100644 index 000000000..f0441e4ea --- /dev/null +++ b/ports/wapy-unix/gccollect.c @@ -0,0 +1,53 @@ +/* + * 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 + +void gc_collect(void) { + // gc_dump_info(); + + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif + #if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); + #endif + gc_collect_end(); + + // printf("-----\n"); + // gc_dump_info(); +} + +#endif // MICROPY_ENABLE_GC diff --git a/ports/wapy-unix/input.c b/ports/wapy-unix/input.c new file mode 100644 index 000000000..4a77d1b27 --- /dev/null +++ b/ports/wapy-unix/input.c @@ -0,0 +1,125 @@ +/* + * 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 +#include +#include +#include + +#include "py/mpstate.h" +#include "py/mphal.h" +#include "input.h" + +#if MICROPY_USE_READLINE == 1 +#include "lib/mp-readline/readline.h" +#endif + +#if MICROPY_USE_READLINE == 0 +char *prompt(char *p) { + // simple read string + static char buf[256]; + fputs(p, stdout); + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + return NULL; + } + int l = strlen(buf); + if (buf[l - 1] == '\n') { + buf[l - 1] = 0; + } else { + l++; + } + char *line = malloc(l); + memcpy(line, buf, l); + return line; +} +#endif + +void prompt_read_history(void) { + #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + readline_init0(); // will clear history pointers + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + int fd = open(vstr_null_terminated_str(&vstr), O_RDONLY); + if (fd != -1) { + vstr_reset(&vstr); + for (;;) { + char c; + int sz = read(fd, &c, 1); + if (sz < 0) { + if (errno == EINTR) { + continue; + } + break; + } + if (sz == 0 || c == '\n') { + readline_push_history(vstr_null_terminated_str(&vstr)); + if (sz == 0) { + break; + } + vstr_reset(&vstr); + } else { + vstr_add_byte(&vstr, c); + } + } + close(fd); + } + vstr_clear(&vstr); + } + #endif + #endif +} + +void prompt_write_history(void) { + #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + int fd = open(vstr_null_terminated_str(&vstr), O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd != -1) { + 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) { + while (write(fd, line, strlen(line)) == -1 && errno == EINTR) { + } + while (write(fd, "\n", 1) == -1 && errno == EINTR) { + } + } + } + close(fd); + } + } + #endif + #endif +} diff --git a/ports/wapy-unix/input.h b/ports/wapy-unix/input.h new file mode 100644 index 000000000..a76b87e64 --- /dev/null +++ b/ports/wapy-unix/input.h @@ -0,0 +1,8 @@ +#ifndef MICROPY_INCLUDED_UNIX_INPUT_H +#define MICROPY_INCLUDED_UNIX_INPUT_H + +char *prompt(char *p); +void prompt_read_history(void); +void prompt_write_history(void); + +#endif // MICROPY_INCLUDED_UNIX_INPUT_H diff --git a/ports/wapy-unix/main.c b/ports/wapy-unix/main.c new file mode 100644 index 000000000..3e8d141bc --- /dev/null +++ b/ports/wapy-unix/main.c @@ -0,0 +1,780 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2017 Paul Sokolovsky + * + * 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 "py/compile.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/stackctrl.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "extmod/misc.h" +#include "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "genhdr/mpversion.h" +#include "input.h" + +// Command line options, with their defaults +STATIC bool compile_only = false; +STATIC uint emit_opt = MP_EMIT_OPT_NONE; + +#if MICROPY_ENABLE_GC +// Heap size of GC heap (if enabled) +// Make it larger on a 64 bit machine, because pointers are larger. +long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4); +#endif + +STATIC void stderr_print_strn(void *env, const char *str, size_t len) { + (void)env; + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); + mp_uos_dupterm_tx_strn(str, len); +} + +const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; + +#define FORCED_EXIT (0x100) +// If exc is SystemExit, return value where FORCED_EXIT bit set, +// and lower 8 bits are SystemExit value. For all other exceptions, +// return 1. +STATIC int handle_uncaught_exception(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + } + + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(exc)); + return 1; +} + +#define LEX_SRC_STR (1) +#define LEX_SRC_VSTR (2) +#define LEX_SRC_FILENAME (3) +#define LEX_SRC_STDIN (4) + +// Returns standard error codes: 0 for success, 1 for all other errors, +// except if FORCED_EXIT bit is set then script raised SystemExit and the +// value of the exit is in the lower 8 bits of the return value +STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { + mp_hal_set_interrupt_char(CHAR_CTRL_C); + + { + // create lexer based on source kind + mp_lexer_t *lex; + if (source_kind == LEX_SRC_STR) { + const char *line = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, line, strlen(line), false); + } else if (source_kind == LEX_SRC_VSTR) { + const vstr_t *vstr = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, false); + } else if (source_kind == LEX_SRC_FILENAME) { + lex = mp_lexer_new_from_file((const char *)source); + } else { // LEX_SRC_STDIN + lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false); + } + + if (lex == NULL) { + return handle_uncaught_exception(); + } + + qstr source_name = lex->source_name; + + #if MICROPY_PY___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + + #if defined(MICROPY_UNIX_COVERAGE) + // allow to print the parse tree in the coverage build + if (mp_verbose_flag >= 3) { + printf("----------------\n"); + mp_parse_node_print(parse_tree.root, 0); + printf("----------------\n"); + } + #endif + + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, is_repl); + + if (!compile_only && module_fun != MP_OBJ_NULL) { + // execute it + mp_obj_t ret = mp_call_function_0(module_fun); + // check for pending exception + if (ret != MP_OBJ_NULL && 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; + mp_raise_o(obj); + } + } + + if (MP_STATE_THREAD(active_exception) != NULL) { + // uncaught exception + mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + return handle_uncaught_exception(); + } + + mp_hal_set_interrupt_char(-1); + mp_handle_pending(true); + return 0; + } +} + +#if MICROPY_USE_READLINE == 1 +#include "lib/mp-readline/readline.h" +#else +STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { + int l1 = strlen(s1); + int l2 = strlen(s2); + char *s = malloc(l1 + l2 + 2); + memcpy(s, s1, l1); + if (sep_char != 0) { + s[l1] = sep_char; + l1 += 1; + } + memcpy(s + l1, s2, l2); + s[l1 + l2] = 0; + return s; +} +#endif + +STATIC int do_repl(void) { + mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " + MICROPY_PY_SYS_PLATFORM " version\nUse Ctrl-D to exit, Ctrl-E for paste mode\n"); + + #if MICROPY_USE_READLINE == 1 + + // use MicroPython supplied readline + + vstr_t line; + vstr_init(&line, 16); + for (;;) { + mp_hal_stdio_mode_raw(); + + input_restart: + vstr_reset(&line); +#if MICROPY_UNIXSCHEDULE + const char sched[] = "__module__ = __import__('sys').modules.get('micropython',None);__module__ and __module__.scheduler()"; + const char *sched_ptr = &sched[0]; + mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; + // req prompt first pass + int ret=-3; + + for (;;) { + ret = readline_select( &line, "~~> ", ret); + //fail + if (ret==-2) { + mp_hal_stdout_tx_str("stdin select error\n"); + //maybe resume with normal readline + ret = readline(&line, ">>> "); + } + if (ret==-1) { + //mp_hal_stdio_mode_orig(); + execute_from_lexer( LEX_SRC_STR, sched_ptr, parse_input_kind, false); + //mp_hal_stdio_mode_raw(); + } + + //got data + if (ret>=0) { + break; + } + } +#else + int ret = readline(&line, ">>> "); + mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; +#endif + + if (ret == CHAR_CTRL_C) { + // cancel input + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // EOF + printf("\n"); + mp_hal_stdio_mode_orig(); + vstr_clear(&line); + return 0; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\n=== "); + vstr_reset(&line); + for (;;) { + char c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\n"); + break; + } else { + // add char to buffer and echo + vstr_add_byte(&line, c); + if (c == '\r') { + mp_hal_stdout_tx_str("\n=== "); + } else { + mp_hal_stdout_tx_strn(&c, 1); + } + } + } + parse_input_kind = MP_PARSE_FILE_INPUT; + } else if (line.len == 0) { + if (ret != 0) { + printf("\n"); + } + goto input_restart; + } else { + // got a line with non-zero length, see if it needs continuing + while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { + vstr_add_byte(&line, '\n'); + ret = readline(&line, "... "); + if (ret == CHAR_CTRL_C) { + // cancel everything + printf("\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + break; + } + } + } + + mp_hal_stdio_mode_orig(); + + ret = execute_from_lexer(LEX_SRC_VSTR, &line, parse_input_kind, true); + if (ret & FORCED_EXIT) { + return ret; + } + } + + #else + + // use simple readline + + for (;;) { + char *line = prompt(">>> "); + if (line == NULL) { + // EOF + return 0; + } + while (mp_repl_continue_with_input(line)) { + char *line2 = prompt("... "); + if (line2 == NULL) { + break; + } + char *line3 = strjoin(line, '\n', line2); + free(line); + free(line2); + line = line3; + } + + int ret = execute_from_lexer(LEX_SRC_STR, line, MP_PARSE_SINGLE_INPUT, true); + if (ret & FORCED_EXIT) { + return ret; + } + free(line); + } + + #endif +} + +STATIC int do_file(const char *file) { + return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false); +} + +STATIC int do_str(const char *str) { + return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); +} + +STATIC void print_help(char **argv) { + printf( + "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] + ); + int impl_opts_cnt = 0; + printf( + " compile-only -- parse and compile only\n" + #if MICROPY_EMIT_NATIVE + " emit={bytecode,native,viper} -- set the default code emitter\n" + #else + " emit=bytecode -- set the default code emitter\n" + #endif + ); + impl_opts_cnt++; + #if MICROPY_ENABLE_GC + printf( + " heapsize=[w][K|M] -- set the heap size for the GC (default %ld)\n" + , heap_size); + impl_opts_cnt++; + #endif + + 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; +} + +// Process options which set interpreter init options +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(invalid_args()); + } + if (0) { + } else if (strcmp(argv[a + 1], "compile-only") == 0) { + compile_only = true; + } else if (strcmp(argv[a + 1], "emit=bytecode") == 0) { + emit_opt = MP_EMIT_OPT_BYTECODE; + #if MICROPY_EMIT_NATIVE + } else if (strcmp(argv[a + 1], "emit=native") == 0) { + emit_opt = MP_EMIT_OPT_NATIVE_PYTHON; + } else if (strcmp(argv[a + 1], "emit=viper") == 0) { + emit_opt = MP_EMIT_OPT_VIPER; + #endif + #if MICROPY_ENABLE_GC + } else if (strncmp(argv[a + 1], "heapsize=", sizeof("heapsize=") - 1) == 0) { + char *end; + heap_size = strtol(argv[a + 1] + sizeof("heapsize=") - 1, &end, 0); + // Don't bring unneeded libc dependencies like tolower() + // If there's 'w' immediately after number, adjust it for + // target word size. Note that it should be *before* size + // suffix like K or M, to avoid confusion with kilowords, + // etc. the size is still in bytes, just can be adjusted + // for word size (taking 32bit as baseline). + bool word_adjust = false; + if ((*end | 0x20) == 'w') { + word_adjust = true; + end++; + } + if ((*end | 0x20) == 'k') { + heap_size *= 1024; + } else if ((*end | 0x20) == 'm') { + heap_size *= 1024 * 1024; + } else { + // Compensate for ++ below + --end; + } + if (*++end != 0) { + goto invalid_arg; + } + if (word_adjust) { + heap_size = heap_size * BYTES_PER_WORD / 4; + } + // If requested size too small, we'll crash anyway + if (heap_size < 700) { + goto invalid_arg; + } +/* + } else if (strncmp(argv[a + 1], "alloc-max=", sizeof("alloc-max=") - 1) == 0) { + char *end; + extern unsigned gc_alloc_count_max; + gc_alloc_count_max = strtol(argv[a + 1] + sizeof("alloc-max=") - 1, &end, 0); + if (*end != 0) { + goto invalid_arg; + } +*/ + #endif + } else { + invalid_arg: + exit(invalid_args()); + } + a++; + } + } + } +} + +STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { + for (int i = start_arg; i < argc; i++) { + mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i]))); + } +} + +#ifdef _WIN32 +#define PATHLIST_SEP_CHAR ';' +#else +#define PATHLIST_SEP_CHAR ':' +#endif + +MP_NOINLINE int main_(int argc, char **argv); + +int main(int argc, char **argv) { + #if MICROPY_PY_THREAD + mp_thread_init(); + #endif + // We should capture stack top ASAP after start, and it should be + // captured guaranteedly before any other stack variables are allocated. + // For this, actual main (renamed main_) should not be inlined into + // this function. main_() itself may have other functions inlined (with + // their own stack variables), that's why we need this main/main_ split. + mp_stack_ctrl_init(); + return main_(argc, argv); +} + +MP_NOINLINE int main_(int argc, char **argv) { + #ifdef SIGPIPE + // Do not raise SIGPIPE, instead return EPIPE. Otherwise, e.g. writing + // to peer-closed socket will lead to sudden termination of MicroPython + // process. SIGPIPE is particularly nasty, because unix shell doesn't + // print anything for it, so the above looks like completely sudden and + // silent termination for unknown reason. Ignoring SIGPIPE is also what + // CPython does. Note that this may lead to problems using MicroPython + // scripts as pipe filters, but again, that's what CPython does. So, + // scripts which want to follow unix shell pipe semantics (where SIGPIPE + // means "pipe was requested to terminate, it's not an error"), should + // catch EPIPE themselves. + signal(SIGPIPE, SIG_IGN); + #endif + + mp_stack_set_limit(40000 * (BYTES_PER_WORD / 4)); + + pre_process_options(argc, argv); + + #if MICROPY_ENABLE_GC + char *heap = malloc(heap_size); + gc_init(heap, heap + heap_size); + #endif + + #if MICROPY_ENABLE_PYSTACK + static mp_obj_t pystack[1024]; + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); + #endif + + mp_init(); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + + #if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; + #else + (void)emit_opt; + #endif + + #if MICROPY_VFS_POSIX + { + // Mount the host FS at the root of our internal VFS + mp_obj_t args[2] = { + mp_type_vfs_posix.make_new(&mp_type_vfs_posix, 0, 0, NULL), + MP_OBJ_NEW_QSTR(MP_QSTR__slash_), + }; + mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map); + MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); + } + #endif + + char *home = getenv("HOME"); + char *path = getenv("MICROPYPATH"); + if (path == NULL) { + #ifdef MICROPY_PY_SYS_PATH_DEFAULT + path = MICROPY_PY_SYS_PATH_DEFAULT; + #else + path = "~/.micropython/lib:/usr/lib/micropython"; + #endif + } + size_t path_num = 1; // [0] is for current dir (or base dir of the script) + if (*path == PATHLIST_SEP_CHAR) { + path_num++; + } + for (char *p = path; p != NULL; p = strchr(p, PATHLIST_SEP_CHAR)) { + path_num++; + if (p != NULL) { + p++; + } + } + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), path_num); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + mp_obj_t *path_items; + mp_obj_list_get(mp_sys_path, &path_num, &path_items); + path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_); + { + char *p = path; + for (mp_uint_t i = 1; i < path_num; i++) { + char *p1 = strchr(p, PATHLIST_SEP_CHAR); + if (p1 == NULL) { + p1 = p + strlen(p); + } + if (p[0] == '~' && p[1] == '/' && home != NULL) { + // Expand standalone ~ to $HOME + int home_l = strlen(home); + vstr_t vstr; + vstr_init(&vstr, home_l + (p1 - p - 1) + 1); + vstr_add_strn(&vstr, home, home_l); + vstr_add_strn(&vstr, p + 1, p1 - p - 1); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + path_items[i] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr); + } else { + path_items[i] = mp_obj_new_str_via_qstr(p, p1 - p); + } + p = p1 + 1; + } + } + + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + + #if defined(MICROPY_UNIX_COVERAGE) + { + MP_DECLARE_CONST_FUN_OBJ_0(extra_coverage_obj); + mp_store_global(QSTR_FROM_STR_STATIC("extra_coverage"), MP_OBJ_FROM_PTR(&extra_coverage_obj)); + } + #endif + + // Here is some example code to create a class and instance of that class. + // First is the Python, then the C code. + // + // class TestClass: + // pass + // test_obj = TestClass() + // test_obj.attr = 42 + // + // mp_obj_t test_class_type, test_class_instance; + // test_class_type = mp_obj_new_type(QSTR_FROM_STR_STATIC("TestClass"), mp_const_empty_tuple, mp_obj_new_dict(0)); + // mp_store_name(QSTR_FROM_STR_STATIC("test_obj"), test_class_instance = mp_call_function_0(test_class_type)); + // mp_store_attr(test_class_instance, QSTR_FROM_STR_STATIC("attr"), mp_obj_new_int(42)); + + /* + printf("bytes:\n"); + printf(" total %d\n", m_get_total_bytes_allocated()); + printf(" cur %d\n", m_get_current_bytes_allocated()); + printf(" peak %d\n", m_get_peak_bytes_allocated()); + */ + + const int NOTHING_EXECUTED = -2; + int ret = NOTHING_EXECUTED; + bool inspect = false; + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strcmp(argv[a], "-i") == 0) { + inspect = true; + } else if (strcmp(argv[a], "-c") == 0) { + if (a + 1 >= argc) { + return invalid_args(); + } + ret = do_str(argv[a + 1]); + if (ret & FORCED_EXIT) { + break; + } + a += 1; + } else if (strcmp(argv[a], "-m") == 0) { + if (a + 1 >= argc) { + return invalid_args(); + } + mp_obj_t import_args[4]; + import_args[0] = mp_obj_new_str(argv[a + 1], strlen(argv[a + 1])); + import_args[1] = import_args[2] = mp_const_none; + // Ask __import__ to handle imported module specially - set its __name__ + // to __main__, and also return this leaf module, not top-level package + // containing it. + import_args[3] = mp_const_false; + // TODO: https://docs.python.org/3/using/cmdline.html#cmdoption-m : + // "the first element of sys.argv will be the full path to + // the module file (while the module file is being located, + // the first element will be set to "-m")." + set_sys_argv(argv, argc, a + 1); + + mp_obj_t mod; + bool subpkg_tried = false; + + reimport: + mod = mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args); + if (mod == MP_OBJ_NULL) { + // uncaught exception + return handle_uncaught_exception() & 0xff; + } + + if (mp_obj_is_package(mod) && !subpkg_tried) { + subpkg_tried = true; + vstr_t vstr; + int len = strlen(argv[a + 1]); + vstr_init(&vstr, len + sizeof(".__main__")); + vstr_add_strn(&vstr, argv[a + 1], len); + vstr_add_strn(&vstr, ".__main__", sizeof(".__main__") - 1); + import_args[0] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr); + goto reimport; + } + + ret = 0; + break; + } else if (strcmp(argv[a], "-X") == 0) { + a += 1; + #if MICROPY_DEBUG_PRINTERS + } else if (strcmp(argv[a], "-v") == 0) { + mp_verbose_flag++; + #endif + } else if (strncmp(argv[a], "-O", 2) == 0) { + if (unichar_isdigit(argv[a][2])) { + MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; + } else { + MP_STATE_VM(mp_optimise_value) = 0; + for (char *p = argv[a] + 1; *p && *p == 'O'; p++, MP_STATE_VM(mp_optimise_value)++) {; + } + } + } else { + return invalid_args(); + } + } else { + char *pathbuf = malloc(PATH_MAX); + char *basedir = realpath(argv[a], pathbuf); + if (basedir == NULL) { + mp_printf(&mp_stderr_print, "%s: can't open file '%s': [Errno %d] %s\n", argv[0], argv[a], errno, strerror(errno)); + // CPython exits with 2 in such case + ret = 2; + break; + } + + // Set base dir of the script as first entry in sys.path + char *p = strrchr(basedir, '/'); + path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir); + free(pathbuf); + + set_sys_argv(argv, argc, a); + ret = do_file(argv[a]); + break; + } + } + + const char *inspect_env = getenv("MICROPYINSPECT"); + if (inspect_env && inspect_env[0] != '\0') { + inspect = true; + } + if (ret == NOTHING_EXECUTED || inspect) { + if (isatty(0)) { + prompt_read_history(); + ret = do_repl(); + prompt_write_history(); + } else { + ret = execute_from_lexer(LEX_SRC_STDIN, NULL, MP_PARSE_FILE_INPUT, false); + } + } + + #if MICROPY_PY_SYS_SETTRACE + MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; + #endif + + #if MICROPY_PY_SYS_ATEXIT + // Beware, the sys.settrace callback should be disabled before running sys.atexit. + if (mp_obj_is_callable(MP_STATE_VM(sys_exitfunc))) { + mp_call_function_0(MP_STATE_VM(sys_exitfunc)); + } + #endif + + #if MICROPY_PY_MICROPYTHON_MEM_INFO + if (mp_verbose_flag) { + mp_micropython_mem_info(0, NULL); + } + #endif + + #if MICROPY_PY_THREAD + mp_thread_deinit(); + #endif + + #if defined(MICROPY_UNIX_COVERAGE) + gc_sweep_all(); + #endif + + mp_deinit(); + + #if MICROPY_ENABLE_GC && !defined(NDEBUG) + // We don't really need to free memory since we are about to exit the + // process, but doing so helps to find memory leaks. + free(heap); + #endif + + //printf("total bytes = %d\n", m_get_total_bytes_allocated()); + return ret & 0xff; +} + +#if !MICROPY_VFS +uint mp_import_stat(const char *path) { + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return MP_IMPORT_STAT_DIR; + } else if (S_ISREG(st.st_mode)) { + return MP_IMPORT_STAT_FILE; + } + } + return MP_IMPORT_STAT_NO_EXIST; +} +#endif + +void nlr_jump_fail(void *val) { + fprintf(stderr, "FATAL: uncaught NLR %p\n", val); + exit(2); +} diff --git a/ports/wapy-unix/manifest.py b/ports/wapy-unix/manifest.py new file mode 100644 index 000000000..666b4c0ab --- /dev/null +++ b/ports/wapy-unix/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/wapy-unix/manifest_coverage.py b/ports/wapy-unix/manifest_coverage.py new file mode 100644 index 000000000..0c32d0857 --- /dev/null +++ b/ports/wapy-unix/manifest_coverage.py @@ -0,0 +1,2 @@ +freeze_as_str('coverage-frzstr') +freeze_as_mpy('coverage-frzmpy') diff --git a/ports/wapy-unix/modffi.c b/ports/wapy-unix/modffi.c new file mode 100644 index 000000000..5edd5b124 --- /dev/null +++ b/ports/wapy-unix/modffi.c @@ -0,0 +1,528 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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/binary.h" +#include "py/mperrno.h" + +/* + * modffi uses character codes to encode a value type, based on "struct" + * module type codes, with some extensions and overridings. + * + * Extra/overridden typecodes: + * v - void, can be used only as return type + * P - const void*, pointer to read-only memory + * p - void*, meaning pointer to a writable memory (note that this + * clashes with struct's "p" as "Pascal string"). + * s - as argument, the same as "p", as return value, causes string + * to be allocated and returned, instead of pointer value. + * O - mp_obj_t, passed as is (mostly useful as a callback param) + * + * TODO: + * C - callback function + * + * Note: all constraint specified by typecode can be not enforced at this time, + * but may be later. + */ + +typedef struct _mp_obj_opaque_t { + mp_obj_base_t base; + void *val; +} mp_obj_opaque_t; + +typedef struct _mp_obj_ffimod_t { + mp_obj_base_t base; + void *handle; +} mp_obj_ffimod_t; + +typedef struct _mp_obj_ffivar_t { + mp_obj_base_t base; + void *var; + char type; +// ffi_type *type; +} mp_obj_ffivar_t; + +typedef struct _mp_obj_ffifunc_t { + mp_obj_base_t base; + void *func; + char rettype; + const char *argtypes; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_ffifunc_t; + +typedef struct _mp_obj_fficallback_t { + mp_obj_base_t base; + void *func; + ffi_closure *clo; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_fficallback_t; + +//STATIC const mp_obj_type_t opaque_type; +STATIC const mp_obj_type_t ffimod_type; +STATIC const mp_obj_type_t ffifunc_type; +STATIC const mp_obj_type_t fficallback_type; +STATIC const mp_obj_type_t ffivar_type; + +STATIC ffi_type *char2ffi_type(char c) { + switch (c) { + case 'b': + return &ffi_type_schar; + case 'B': + return &ffi_type_uchar; + case 'h': + return &ffi_type_sshort; + case 'H': + return &ffi_type_ushort; + case 'i': + return &ffi_type_sint; + case 'I': + return &ffi_type_uint; + case 'l': + return &ffi_type_slong; + case 'L': + return &ffi_type_ulong; + case 'q': + return &ffi_type_sint64; + case 'Q': + return &ffi_type_uint64; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': + return &ffi_type_float; + case 'd': + return &ffi_type_double; + #endif + case 'O': // mp_obj_t + case 'C': // (*)() + case 'P': // const void* + case 'p': // void* + case 's': + return &ffi_type_pointer; + case 'v': + return &ffi_type_void; + default: + return NULL; + } +} + +STATIC ffi_type *get_ffi_type(mp_obj_t o_in) { + if (mp_obj_is_str(o_in)) { + const char *s = mp_obj_str_get_str(o_in); + ffi_type *t = char2ffi_type(*s); + if (t != NULL) { + return t; + } + } + // TODO: Support actual libffi type objects + + mp_raise_TypeError_o("Unknown type"); + return NULL; +} + +STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { + switch (type) { + case 's': { + const char *s = (const char *)(intptr_t)val; + if (!s) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); + } + case 'v': + return mp_const_none; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': { + union { ffi_arg ffi; + mp_float_t flt; + } val_union = { .ffi = val }; + return mp_obj_new_float(val_union.flt); + } + case 'd': { + double *p = (double *)&val; + return mp_obj_new_float(*p); + } + #endif + case 'O': + return (mp_obj_t)(intptr_t)val; + default: + return mp_obj_new_int(val); + } +} + +// FFI module + +STATIC void ffimod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->handle); +} + +STATIC mp_obj_t ffimod_close(mp_obj_t self_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + dlclose(self->handle); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ffimod_close_obj, ffimod_close); + +STATIC mp_obj_t make_func(mp_obj_t rettype_in, void *func, mp_obj_t argtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + const char *argtypes = mp_obj_str_get_str(argtypes_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(argtypes_in)); + mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type *, nparams); + o->base.type = &ffifunc_type; + + o->func = func; + o->rettype = *rettype; + o->argtypes = argtypes; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(argtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + if (o->params[i - 1] == NULL) { + return MP_OBJ_NULL; + } + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + return mp_raise_ValueError_o("Error in ffi_prep_cif"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t ffimod_func(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(args[0]); + const char *symname = mp_obj_str_get_str(args[2]); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + return make_func(args[1], sym, args[3]); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ffimod_func_obj, 4, 4, ffimod_func); + +STATIC mp_obj_t mod_ffi_func(mp_obj_t rettype, mp_obj_t addr_in, mp_obj_t argtypes) { + void *addr = (void *)MP_OBJ_TO_PTR(mp_obj_int_get_truncated(addr_in)); + return make_func(rettype, addr, argtypes); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_func_obj, mod_ffi_func); + +STATIC void call_py_func(ffi_cif *cif, void *ret, void **args, void *func) { + mp_obj_t pyargs[cif->nargs]; + for (uint i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(mp_int_t *)args[i]); + } + mp_obj_t res = mp_call_function_n_kw(MP_OBJ_FROM_PTR(func), cif->nargs, 0, pyargs); + + if (res != mp_const_none) { + *(ffi_arg *)ret = mp_obj_int_get_truncated(res); + } +} + +STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); + mp_obj_fficallback_t *o = m_new_obj_var(mp_obj_fficallback_t, ffi_type *, nparams); + o->base.type = &fficallback_type; + + o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); + + o->rettype = *rettype; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(paramtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + if (o->params[i - 1] == NULL) { + return MP_OBJ_NULL; + } + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + return mp_raise_ValueError_o("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) { + return mp_raise_ValueError_o("ffi_prep_closure_loc"); + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, mod_ffi_callback); + +STATIC mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *rettype = mp_obj_str_get_str(vartype_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t); + o->base.type = &ffivar_type; + + o->var = sym; + o->type = *rettype; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(ffimod_var_obj, ffimod_var); + +STATIC mp_obj_t ffimod_addr(mp_obj_t self_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + return mp_obj_new_int((uintptr_t)sym); +} +MP_DEFINE_CONST_FUN_OBJ_2(ffimod_addr_obj, ffimod_addr); + +STATIC mp_obj_t ffimod_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_args; + (void)n_kw; + + const char *fname = NULL; + if (args[0] != mp_const_none) { + fname = mp_obj_str_get_str(args[0]); + } + void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL); + + if (mod == NULL) { + return mp_raise_OSError_o(errno); + } + mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t); + o->base.type = type; + o->handle = mod; + return MP_OBJ_FROM_PTR(o); +} + +STATIC const mp_rom_map_elem_t ffimod_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&ffimod_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_var), MP_ROM_PTR(&ffimod_var_obj) }, + { MP_ROM_QSTR(MP_QSTR_addr), MP_ROM_PTR(&ffimod_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&ffimod_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffimod_locals_dict, ffimod_locals_dict_table); + +STATIC const mp_obj_type_t ffimod_type = { + { &mp_type_type }, + .name = MP_QSTR_ffimod, + .print = ffimod_print, + .make_new = ffimod_make_new, + .locals_dict = (mp_obj_dict_t *)&ffimod_locals_dict, +}; + +// FFI function + +STATIC void ffifunc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_kw; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + assert(n_kw == 0); + assert(n_args == self->cif.nargs); + + ffi_arg values[n_args]; + void *valueptrs[n_args]; + const char *argtype = self->argtypes; + for (uint i = 0; i < n_args; i++, argtype++) { + mp_obj_t a = args[i]; + if (*argtype == 'O') { + values[i] = (ffi_arg)(intptr_t)a; + #if MICROPY_PY_BUILTINS_FLOAT + } else if (*argtype == 'f') { + float *p = (float *)&values[i]; + *p = (float)mp_obj_get_float(a); + } else if (*argtype == 'd') { + double *p = (double *)&values[i]; + *p = mp_obj_get_float(a); + #endif + } else if (a == mp_const_none) { + values[i] = 0; + } else if (mp_obj_is_int(a)) { + values[i] = mp_obj_int_get_truncated(a); + } else if (mp_obj_is_str(a)) { + const char *s = mp_obj_str_get_str(a); + values[i] = (ffi_arg)(intptr_t)s; + } else if (((mp_obj_base_t *)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { + mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(a); + mp_buffer_info_t bufinfo; + int ret = o->type->buffer_p.get_buffer(MP_OBJ_FROM_PTR(o), &bufinfo, MP_BUFFER_READ); // TODO: MP_BUFFER_READ? + if (ret != 0) { + goto error; + } + values[i] = (ffi_arg)(intptr_t)bufinfo.buf; + } else if (mp_obj_is_type(a, &fficallback_type)) { + mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); + values[i] = (ffi_arg)(intptr_t)p->func; + } else { + goto error; + } + valueptrs[i] = &values[i]; + } + + // If ffi_arg is not big enough to hold a double, then we must pass along a + // pointer to a memory location of the correct size. + // TODO check if this needs to be done for other types which don't fit into + // ffi_arg. + #if MICROPY_PY_BUILTINS_FLOAT + if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { + double retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return mp_obj_new_float(retval); + } else + #endif + { + ffi_arg retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(retval, self->rettype); + } + +error: + return mp_raise_TypeError_o("Don't know how to pass object to native function"); +} + +STATIC const mp_obj_type_t ffifunc_type = { + { &mp_type_type }, + .name = MP_QSTR_ffifunc, + .print = ffifunc_print, + .call = ffifunc_call, +}; + +// FFI callback for Python function + +STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fficallback_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC const mp_obj_type_t fficallback_type = { + { &mp_type_type }, + .name = MP_QSTR_fficallback, + .print = fficallback_print, +}; + +// FFI variable + +STATIC void ffivar_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", self->var, *(int *)self->var); +} + +STATIC mp_obj_t ffivar_get(mp_obj_t self_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + return mp_binary_get_val_array(self->type, self->var, 0); +} +MP_DEFINE_CONST_FUN_OBJ_1(ffivar_get_obj, ffivar_get); + +STATIC mp_obj_t ffivar_set(mp_obj_t self_in, mp_obj_t val_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + mp_binary_set_val_array(self->type, self->var, 0, val_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(ffivar_set_obj, ffivar_set); + +STATIC const mp_rom_map_elem_t ffivar_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffivar_locals_dict, ffivar_locals_dict_table); + +STATIC const mp_obj_type_t ffivar_type = { + { &mp_type_type }, + .name = MP_QSTR_ffivar, + .print = ffivar_print, + .locals_dict = (mp_obj_dict_t *)&ffivar_locals_dict, +}; + +// Generic opaque storage object (unused) + +/* +STATIC const mp_obj_type_t opaque_type = { + { &mp_type_type }, + .name = MP_QSTR_opaqueval, +// .print = opaque_print, +}; +*/ + +STATIC mp_obj_t mod_ffi_open(size_t n_args, const mp_obj_t *args) { + return ffimod_make_new(&ffimod_type, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ffi_open_obj, 1, 2, mod_ffi_open); + +STATIC mp_obj_t mod_ffi_as_bytearray(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get_truncated(size), (void *)(uintptr_t)mp_obj_int_get_truncated(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_ffi_as_bytearray_obj, mod_ffi_as_bytearray); + +STATIC const mp_rom_map_elem_t mp_module_ffi_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ffi) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_ffi_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&mod_ffi_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&mod_ffi_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_as_bytearray), MP_ROM_PTR(&mod_ffi_as_bytearray_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ffi_globals, mp_module_ffi_globals_table); + +const mp_obj_module_t mp_module_ffi = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_ffi_globals, +}; diff --git a/ports/wapy-unix/modjni.c b/ports/wapy-unix/modjni.c new file mode 100644 index 000000000..74e7221de --- /dev/null +++ b/ports/wapy-unix/modjni.c @@ -0,0 +1,719 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Paul Sokolovsky + * + * 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 "py/runtime.h" +#include "py/binary.h" + +#include + +#define JJ(call, ...) (*env)->call(env, __VA_ARGS__) +#define JJ1(call) (*env)->call(env) +#define MATCH(s, static) (!strncmp(s, static, sizeof(static) - 1)) + +static JavaVM *jvm; +static JNIEnv *env; +static jclass Class_class; +static jclass String_class; +static jmethodID Class_getName_mid; +static jmethodID Class_getField_mid; +static jmethodID Class_getMethods_mid; +static jmethodID Class_getConstructors_mid; +static jmethodID Method_getName_mid; +static jmethodID Object_toString_mid; + +static jclass List_class; +static jmethodID List_get_mid; +static jmethodID List_set_mid; +static jmethodID List_size_mid; + +static jclass IndexException_class; + +STATIC const mp_obj_type_t jobject_type; +STATIC const mp_obj_type_t jmethod_type; + +STATIC mp_obj_t new_jobject(jobject jo); +STATIC mp_obj_t new_jclass(jclass jc); +STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool is_constr, size_t n_args, const mp_obj_t *args); +STATIC bool py2jvalue(const char **jtypesig, mp_obj_t arg, jvalue *out); + +typedef struct _mp_obj_jclass_t { + mp_obj_base_t base; + jclass cls; +} mp_obj_jclass_t; + +typedef struct _mp_obj_jobject_t { + mp_obj_base_t base; + jobject obj; +} mp_obj_jobject_t; + +typedef struct _mp_obj_jmethod_t { + mp_obj_base_t base; + jobject obj; + jmethodID meth; + qstr name; + bool is_static; +} mp_obj_jmethod_t; + +// Utility functions + +STATIC bool is_object_type(const char *jtypesig) { + while (*jtypesig != ' ' && *jtypesig) { + if (*jtypesig == '.') { + return true; + } + jtypesig++; + } + return false; +} + +STATIC void check_exception(void) { + jobject exc = JJ1(ExceptionOccurred); + if (exc) { + // JJ1(ExceptionDescribe); + mp_obj_t py_e = new_jobject(exc); + JJ1(ExceptionClear); + if (JJ(IsInstanceOf, exc, IndexException_class)) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, py_e)); + } + nlr_raise(mp_obj_new_exception_arg1(&mp_type_Exception, py_e)); + } +} + +STATIC void print_jobject(const mp_print_t *print, jobject obj) { + jobject str_o = JJ(CallObjectMethod, obj, Object_toString_mid); + const char *str = JJ(GetStringUTFChars, str_o, NULL); + mp_printf(print, str); + JJ(ReleaseStringUTFChars, str_o, str); +} + +// jclass + +STATIC void jclass_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + if (kind == PRINT_REPR) { + mp_printf(print, "cls); + } + print_jobject(print, self->cls); + if (kind == PRINT_REPR) { + mp_printf(print, "\">"); + } +} + +STATIC void jclass_attr(mp_obj_t self_in, qstr attr_in, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + // load attribute + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + const char *attr = qstr_str(attr_in); + + jstring field_name = JJ(NewStringUTF, attr); + jobject field = JJ(CallObjectMethod, self->cls, Class_getField_mid, field_name); + if (!JJ1(ExceptionCheck)) { + jfieldID field_id = JJ(FromReflectedField, field); + jobject obj = JJ(GetStaticObjectField, self->cls, field_id); + dest[0] = new_jobject(obj); + return; + } + // JJ1(ExceptionDescribe); + JJ1(ExceptionClear); + + mp_obj_jmethod_t *o = m_new_obj(mp_obj_jmethod_t); + o->base.type = &jmethod_type; + o->name = attr_in; + o->meth = NULL; + o->obj = self->cls; + o->is_static = true; + dest[0] = MP_OBJ_FROM_PTR(o); + } +} + +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(MP_ERROR_TEXT("kwargs not supported")); + } + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + + jarray methods = JJ(CallObjectMethod, self->cls, Class_getConstructors_mid); + + return call_method(self->cls, NULL, methods, true, n_args, args); +} + +STATIC const mp_rom_map_elem_t jclass_locals_dict_table[] = { +// { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, +// { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(jclass_locals_dict, jclass_locals_dict_table); + +STATIC const mp_obj_type_t jclass_type = { + { &mp_type_type }, + .name = MP_QSTR_jclass, + .print = jclass_print, + .attr = jclass_attr, + .call = jclass_call, + .locals_dict = (mp_obj_dict_t *)&jclass_locals_dict, +}; + +STATIC mp_obj_t new_jclass(jclass jc) { + mp_obj_jclass_t *o = m_new_obj(mp_obj_jclass_t); + o->base.type = &jclass_type; + o->cls = jc; + return MP_OBJ_FROM_PTR(o); +} + +// jobject + +STATIC void jobject_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + if (kind == PRINT_REPR) { + mp_printf(print, "obj); + } + print_jobject(print, self->obj); + if (kind == PRINT_REPR) { + mp_printf(print, "\">"); + } +} + +STATIC void jobject_attr(mp_obj_t self_in, qstr attr_in, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + // load attribute + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + + const char *attr = qstr_str(attr_in); + jclass obj_class = JJ(GetObjectClass, self->obj); + jstring field_name = JJ(NewStringUTF, attr); + jobject field = JJ(CallObjectMethod, obj_class, Class_getField_mid, field_name); + JJ(DeleteLocalRef, field_name); + JJ(DeleteLocalRef, obj_class); + if (!JJ1(ExceptionCheck)) { + jfieldID field_id = JJ(FromReflectedField, field); + JJ(DeleteLocalRef, field); + jobject obj = JJ(GetObjectField, self->obj, field_id); + dest[0] = new_jobject(obj); + return; + } + // JJ1(ExceptionDescribe); + JJ1(ExceptionClear); + + mp_obj_jmethod_t *o = m_new_obj(mp_obj_jmethod_t); + o->base.type = &jmethod_type; + o->name = attr_in; + o->meth = NULL; + o->obj = self->obj; + o->is_static = false; + dest[0] = MP_OBJ_FROM_PTR(o); + } +} + +STATIC void get_jclass_name(jobject obj, char *buf) { + jclass obj_class = JJ(GetObjectClass, obj); + jstring name = JJ(CallObjectMethod, obj_class, Class_getName_mid); + jint len = JJ(GetStringLength, name); + JJ(GetStringUTFRegion, name, 0, len, buf); + check_exception(); +} + +STATIC mp_obj_t jobject_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t idx = mp_obj_get_int(index); + char class_name[64]; + get_jclass_name(self->obj, class_name); + // printf("class: %s\n", class_name); + + if (class_name[0] == '[') { + if (class_name[1] == 'L' || class_name[1] == '[') { + if (value == MP_OBJ_NULL) { + // delete + assert(0); + } else if (value == MP_OBJ_SENTINEL) { + // load + jobject el = JJ(GetObjectArrayElement, self->obj, idx); + return new_jobject(el); + } else { + // store + jvalue jval; + const char *t = class_name + 1; + py2jvalue(&t, value, &jval); + JJ(SetObjectArrayElement, self->obj, idx, jval.l); + return mp_const_none; + } + } + mp_raise_NotImplementedError(NULL); + } + + if (!JJ(IsInstanceOf, self->obj, List_class)) { + return MP_OBJ_NULL; + } + + + if (value == MP_OBJ_NULL) { + // delete + assert(0); + } else if (value == MP_OBJ_SENTINEL) { + // load + jobject el = JJ(CallObjectMethod, self->obj, List_get_mid, idx); + check_exception(); + return new_jobject(el); + } else { + // store + assert(0); + } + + + return MP_OBJ_NULL; +} + +STATIC mp_obj_t jobject_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + case MP_UNARY_OP_LEN: { + jint len = JJ(CallIntMethod, self->obj, List_size_mid); + if (op == MP_UNARY_OP_BOOL) { + return mp_obj_new_bool(len != 0); + } + return MP_OBJ_NEW_SMALL_INT(len); + } + default: + return MP_OBJ_NULL; // op not supported + } +} + +// TODO: subscr_load_adaptor & subscr_getiter convenience functions +// should be moved to common location for reuse. +STATIC mp_obj_t subscr_load_adaptor(mp_obj_t self_in, mp_obj_t index_in) { + return mp_obj_subscr(self_in, index_in, MP_OBJ_SENTINEL); +} +MP_DEFINE_CONST_FUN_OBJ_2(subscr_load_adaptor_obj, subscr_load_adaptor); + +// .getiter special method which returns iterator which works in terms +// of object subscription. +STATIC mp_obj_t subscr_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + mp_obj_t dest[2] = {MP_OBJ_FROM_PTR(&subscr_load_adaptor_obj), self_in}; + return mp_obj_new_getitem_iter(dest, iter_buf); +} + +STATIC const mp_obj_type_t jobject_type = { + { &mp_type_type }, + .name = MP_QSTR_jobject, + .print = jobject_print, + .unary_op = jobject_unary_op, + .attr = jobject_attr, + .subscr = jobject_subscr, + .getiter = subscr_getiter, +// .locals_dict = (mp_obj_dict_t*)&jobject_locals_dict, +}; + +STATIC mp_obj_t new_jobject(jobject jo) { + if (jo == NULL) { + return mp_const_none; + } else if (JJ(IsInstanceOf, jo, String_class)) { + const char *s = JJ(GetStringUTFChars, jo, NULL); + mp_obj_t ret = mp_obj_new_str(s, strlen(s)); + JJ(ReleaseStringUTFChars, jo, s); + return ret; + } else if (JJ(IsInstanceOf, jo, Class_class)) { + return new_jclass(jo); + } else { + mp_obj_jobject_t *o = m_new_obj(mp_obj_jobject_t); + o->base.type = &jobject_type; + o->obj = jo; + return MP_OBJ_FROM_PTR(o); + } + +} + + +// jmethod + +STATIC void jmethod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", qstr_str(self->name)); +} + +#define IMATCH(s, static) ((!strncmp(s, static, sizeof(static) - 1)) && (s += sizeof(static) - 1)) + +#define CHECK_TYPE(java_type_name) \ + if (strncmp(arg_type, java_type_name, sizeof(java_type_name) - 1) != 0) { \ + return false; \ + } \ + arg_type += sizeof(java_type_name) - 1; + +STATIC const char *strprev(const char *s, char c) { + while (*s != c) { + s--; + } + return s; +} + +STATIC bool py2jvalue(const char **jtypesig, mp_obj_t arg, jvalue *out) { + const char *arg_type = *jtypesig; + mp_obj_type_t *type = mp_obj_get_type(arg); + + if (type == &mp_type_str) { + if (IMATCH(arg_type, "java.lang.String") || IMATCH(arg_type, "java.lang.Object")) { + out->l = JJ(NewStringUTF, mp_obj_str_get_str(arg)); + } else { + return false; + } + } else if (type == &mp_type_int) { + if (IMATCH(arg_type, "int") || IMATCH(arg_type, "long")) { + // TODO: Java long is 64-bit actually + out->j = mp_obj_get_int(arg); + } else { + return false; + } + } else if (type == &jobject_type) { + bool is_object = false; + const char *expected_type = arg_type; + while (1) { + if (isalpha(*arg_type)) { + } else if (*arg_type == '.') { + is_object = true; + } else { + break; + } + arg_type++; + } + if (!is_object) { + return false; + } + mp_obj_jobject_t *jo = MP_OBJ_TO_PTR(arg); + if (!MATCH(expected_type, "java.lang.Object")) { + char class_name[64]; + get_jclass_name(jo->obj, class_name); + // printf("Arg class: %s\n", class_name); + if (strcmp(class_name, expected_type) != 0) { + return false; + } + } + out->l = jo->obj; + } else if (type == &mp_type_bool) { + if (IMATCH(arg_type, "boolean")) { + out->z = arg == mp_const_true; + } else { + return false; + } + } else if (arg == mp_const_none) { + // printf("TODO: Check java arg type!!\n"); + while (isalpha(*arg_type) || *arg_type == '.') { + arg_type++; + } + out->l = NULL; + } else { + mp_raise_TypeError(MP_ERROR_TEXT("arg type not supported")); + } + + *jtypesig = arg_type; + return true; +} + +#if 0 +// jvalue is known to be union of jobject and friends. And yet from C's +// perspective, it's aggregate object which may require passing via stack +// instead of registers. Work that around by passing jobject and typecasting +// it. +STATIC mp_obj_t jvalue2py(const char *jtypesig, jobject arg) { + if (arg == NULL || MATCH(jtypesig, "void")) { + return mp_const_none; + } else if (MATCH(jtypesig, "boolean")) { + return mp_obj_new_bool((bool)arg); + } else if (MATCH(jtypesig, "int")) { + return mp_obj_new_int((mp_int_t)arg); + } else if (is_object_type(jtypesig)) { + // Non-primitive, object type + return new_jobject(arg); + } + + printf("Unknown return type: %s\n", jtypesig); + + return MP_OBJ_NULL; +} +#endif + +STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool is_constr, size_t n_args, const mp_obj_t *args) { + jvalue jargs[n_args]; +// printf("methods=%p\n", methods); + jsize num_methods = JJ(GetArrayLength, methods); + for (int i = 0; i < num_methods; i++) { + jobject meth = JJ(GetObjectArrayElement, methods, i); + jobject name_o = JJ(CallObjectMethod, meth, Object_toString_mid); + const char *decl = JJ(GetStringUTFChars, name_o, NULL); + const char *arg_types = strchr(decl, '(') + 1; + // const char *arg_types_end = strchr(arg_types, ')'); +// printf("method[%d]=%p %s\n", i, meth, decl); + + const char *meth_name = NULL; + const char *ret_type = NULL; + if (!is_constr) { + meth_name = strprev(arg_types, '.') + 1; + ret_type = strprev(meth_name, ' ') - 1; + ret_type = strprev(ret_type, ' ') + 1; + + int name_len = strlen(name); + if (strncmp(name, meth_name, name_len /*arg_types - meth_name - 1*/) || meth_name[name_len] != '(' /*(*/) { + goto next_method; + } + } +// printf("method[%d]=%p %s\n", i, meth, decl); +// printf("!!!%s\n", arg_types); +// printf("name=%p meth_name=%s\n", name, meth_name); + + bool found = true; + for (size_t j = 0; j < n_args && *arg_types != ')'; j++) { + if (!py2jvalue(&arg_types, args[j], &jargs[j])) { + goto next_method; + } + + if (*arg_types == ',') { + arg_types++; + } + } + + if (*arg_types != ')') { + goto next_method; + } + + if (found) { +// printf("found!\n"); + jmethodID method_id = JJ(FromReflectedMethod, meth); + if (is_constr) { + JJ(ReleaseStringUTFChars, name_o, decl); + jobject res = JJ(NewObjectA, obj, method_id, jargs); + return new_jobject(res); + } else { + mp_obj_t ret; + if (MATCH(ret_type, "void")) { + JJ(CallVoidMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_const_none; + } else if (MATCH(ret_type, "int")) { + jint res = JJ(CallIntMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_obj_new_int(res); + } else if (MATCH(ret_type, "boolean")) { + jboolean res = JJ(CallBooleanMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_obj_new_bool(res); + } else if (is_object_type(ret_type)) { + jobject res = JJ(CallObjectMethodA, obj, method_id, jargs); + check_exception(); + ret = new_jobject(res); + } else { + JJ(ReleaseStringUTFChars, name_o, decl); + mp_raise_TypeError(MP_ERROR_TEXT("can't handle return type")); + } + + JJ(ReleaseStringUTFChars, name_o, decl); + JJ(DeleteLocalRef, name_o); + JJ(DeleteLocalRef, meth); + return ret; + } + } + + next_method: + JJ(ReleaseStringUTFChars, name_o, decl); + JJ(DeleteLocalRef, name_o); + JJ(DeleteLocalRef, meth); + } + + 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(MP_ERROR_TEXT("kwargs not supported")); + } + mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); + + const char *name = qstr_str(self->name); +// jstring meth_name = JJ(NewStringUTF, name); + + jclass obj_class = self->obj; + if (!self->is_static) { + obj_class = JJ(GetObjectClass, self->obj); + } + jarray methods = JJ(CallObjectMethod, obj_class, Class_getMethods_mid); + + return call_method(self->obj, name, methods, false, n_args, args); +} + +STATIC const mp_obj_type_t jmethod_type = { + { &mp_type_type }, + .name = MP_QSTR_jmethod, + .print = jmethod_print, + .call = jmethod_call, +// .attr = jobject_attr, +// .locals_dict = (mp_obj_dict_t*)&jobject_locals_dict, +}; + +#ifdef __ANDROID__ +#define LIBJVM_SO "libdvm.so" +#else +#define LIBJVM_SO "libjvm.so" +#endif + +STATIC void create_jvm(void) { + JavaVMInitArgs args; + JavaVMOption options; + options.optionString = "-Djava.class.path=."; + args.version = JNI_VERSION_1_6; + args.nOptions = 1; + args.options = &options; + args.ignoreUnrecognized = 0; + + if (env) { + return; + } + + void *libjvm = dlopen(LIBJVM_SO, RTLD_NOW | RTLD_GLOBAL); + if (!libjvm) { + 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, MP_ERROR_TEXT("unable to create JVM")); + } + + Class_class = JJ(FindClass, "java/lang/Class"); + jclass method_class = JJ(FindClass, "java/lang/reflect/Method"); + String_class = JJ(FindClass, "java/lang/String"); + + jclass Object_class = JJ(FindClass, "java/lang/Object"); + Object_toString_mid = JJ(GetMethodID, Object_class, "toString", + MP_ERROR_TEXT("()Ljava/lang/String;")); + + Class_getName_mid = (*env)->GetMethodID(env, Class_class, "getName", + MP_ERROR_TEXT("()Ljava/lang/String;")); + Class_getField_mid = (*env)->GetMethodID(env, Class_class, "getField", + MP_ERROR_TEXT("(Ljava/lang/String;)Ljava/lang/reflect/Field;")); + Class_getMethods_mid = (*env)->GetMethodID(env, Class_class, "getMethods", + MP_ERROR_TEXT("()[Ljava/lang/reflect/Method;")); + Class_getConstructors_mid = (*env)->GetMethodID(env, Class_class, "getConstructors", + MP_ERROR_TEXT("()[Ljava/lang/reflect/Constructor;")); + Method_getName_mid = (*env)->GetMethodID(env, method_class, "getName", + MP_ERROR_TEXT("()Ljava/lang/String;")); + + List_class = JJ(FindClass, "java/util/List"); + List_get_mid = JJ(GetMethodID, List_class, "get", + MP_ERROR_TEXT("(I)Ljava/lang/Object;")); + List_set_mid = JJ(GetMethodID, List_class, "set", + MP_ERROR_TEXT("(ILjava/lang/Object;)Ljava/lang/Object;")); + List_size_mid = JJ(GetMethodID, List_class, "size", + MP_ERROR_TEXT("()I")); + IndexException_class = JJ(FindClass, "java/lang/IndexOutOfBoundsException"); +} + +STATIC mp_obj_t mod_jni_cls(mp_obj_t cls_name_in) { + const char *cls_name = mp_obj_str_get_str(cls_name_in); + if (!env) { + create_jvm(); + } + jclass cls = JJ(FindClass, cls_name); + + mp_obj_jclass_t *o = m_new_obj(mp_obj_jclass_t); + o->base.type = &jclass_type; + o->cls = cls; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_jni_cls_obj, mod_jni_cls); + +STATIC mp_obj_t mod_jni_array(mp_obj_t type_in, mp_obj_t size_in) { + if (!env) { + create_jvm(); + } + mp_int_t size = mp_obj_get_int(size_in); + jobject res = NULL; + + if (mp_obj_is_type(type_in, &jclass_type)) { + + mp_obj_jclass_t *jcls = MP_OBJ_TO_PTR(type_in); + res = JJ(NewObjectArray, size, jcls->cls, NULL); + + } else if (mp_obj_is_str(type_in)) { + const char *type = mp_obj_str_get_str(type_in); + switch (*type) { + case 'Z': + res = JJ(NewBooleanArray, size); + break; + case 'B': + res = JJ(NewByteArray, size); + break; + case 'C': + res = JJ(NewCharArray, size); + break; + case 'S': + res = JJ(NewShortArray, size); + break; + case 'I': + res = JJ(NewIntArray, size); + break; + case 'J': + res = JJ(NewLongArray, size); + break; + case 'F': + res = JJ(NewFloatArray, size); + break; + case 'D': + res = JJ(NewDoubleArray, size); + break; + } + + } + + return new_jobject(res); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_jni_array_obj, mod_jni_array); + + +STATIC mp_obj_t mod_jni_env(void) { + return mp_obj_new_int((mp_int_t)(uintptr_t)env); +} +MP_DEFINE_CONST_FUN_OBJ_0(mod_jni_env_obj, mod_jni_env); + +STATIC const mp_rom_map_elem_t mp_module_jni_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jni) }, + { MP_ROM_QSTR(MP_QSTR_cls), MP_ROM_PTR(&mod_jni_cls_obj) }, + { MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&mod_jni_array_obj) }, + { MP_ROM_QSTR(MP_QSTR_env), MP_ROM_PTR(&mod_jni_env_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_jni_globals, mp_module_jni_globals_table); + +const mp_obj_module_t mp_module_jni = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_jni_globals, +}; diff --git a/ports/wapy-unix/modmachine.c b/ports/wapy-unix/modmachine.c new file mode 100644 index 000000000..a3992662f --- /dev/null +++ b/ports/wapy-unix/modmachine.c @@ -0,0 +1,122 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Paul Sokolovsky + * + * 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/obj.h" + +#include "extmod/machine_mem.h" +#include "extmod/machine_pinbase.h" +#include "extmod/machine_signal.h" +#include "extmod/machine_pulse.h" + +#if MICROPY_PLAT_DEV_MEM +#include +#include +#include +#define MICROPY_PAGE_SIZE 4096 +#define MICROPY_PAGE_MASK (MICROPY_PAGE_SIZE - 1) +#endif + +#if MICROPY_PY_MACHINE + +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) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg_varg( &mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align)) ; +#else + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align); +#endif + } + #if MICROPY_PLAT_DEV_MEM + { + // Not thread-safe + static int fd; + static uintptr_t last_base = (uintptr_t)-1; + static uintptr_t map_page; + if (!fd) { + int _fd = open("/dev/mem", O_RDWR | O_SYNC); + if (_fd == -1) { +#if NO_NLR + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno))); + return 0; +#else + mp_raise_OSError(errno); +#endif + } + fd = _fd; + } + + uintptr_t cur_base = addr & ~MICROPY_PAGE_MASK; + if (cur_base != last_base) { + map_page = (uintptr_t)mmap(NULL, MICROPY_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, cur_base); + last_base = cur_base; + } + addr = map_page + (addr & MICROPY_PAGE_MASK); + } + #endif + + 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) }, + + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, + { 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 + { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); + +const mp_obj_module_t mp_module_machine = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&machine_module_globals, +}; + +#endif // MICROPY_PY_MACHINE diff --git a/ports/wapy-unix/modos.c b/ports/wapy-unix/modos.c new file mode 100644 index 000000000..1414f8766 --- /dev/null +++ b/ports/wapy-unix/modos.c @@ -0,0 +1,335 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Paul Sokolovsky + * Copyright (c) 2014-2018 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 "py/mpconfig.h" + +#include "py/runtime.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "extmod/vfs.h" +#include "extmod/misc.h" + +#ifdef __ANDROID__ +#define USE_STATFS 1 +#endif + +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); + + MP_THREAD_GIL_EXIT(); + int res = stat(path, &sb); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(res, errno); + + 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_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); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); + +#if MICROPY_PY_OS_STATVFS + +#if USE_STATFS +#include +#define STRUCT_STATVFS struct statfs +#define STATVFS statfs +#define F_FAVAIL sb.f_ffree +#define F_NAMEMAX sb.f_namelen +#define F_FLAG sb.f_flags +#else +#include +#define STRUCT_STATVFS struct statvfs +#define STATVFS statvfs +#define F_FAVAIL sb.f_favail +#define F_NAMEMAX sb.f_namemax +#define F_FLAG sb.f_flag +#endif + +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); + + MP_THREAD_GIL_EXIT(); + int res = STATVFS(path, &sb); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(res, errno); + + 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); + t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.f_blocks); + t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.f_bfree); + t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.f_bavail); + t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.f_files); + t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.f_ffree); + t->items[7] = MP_OBJ_NEW_SMALL_INT(F_FAVAIL); + t->items[8] = MP_OBJ_NEW_SMALL_INT(F_FLAG); + t->items[9] = MP_OBJ_NEW_SMALL_INT(F_NAMEMAX); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_statvfs_obj, mod_os_statvfs); +#endif + +STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + // Note that POSIX requires remove() to be able to delete a directory + // too (act as rmdir()). This is POSIX extenstion to ANSI C semantics + // 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); + + return mp_const_none; +} +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); + + return MP_OBJ_NEW_SMALL_INT(r); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_system_obj, mod_os_system); + +STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) { + const char *s = getenv(mp_obj_str_get_str(var_in)); + if (s == NULL) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); +} +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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_mkdir_obj, mod_os_mkdir); + +typedef struct _mp_obj_listdir_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + DIR *dir; +} mp_obj_listdir_t; + +STATIC mp_obj_t listdir_next(mp_obj_t self_in) { + mp_obj_listdir_t *self = MP_OBJ_TO_PTR(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)); + + #ifdef _DIRENT_HAVE_D_TYPE + #ifdef DTTOIF + t->items[1] = MP_OBJ_NEW_SMALL_INT(DTTOIF(dirent->d_type)); + #else + if (dirent->d_type == DT_DIR) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); + } else if (dirent->d_type == DT_REG) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG); + } else { + t->items[1] = MP_OBJ_NEW_SMALL_INT(dirent->d_type); + } + #endif + #else + // DT_UNKNOWN should have 0 value on any reasonable system + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); + #endif + + #ifdef _DIRENT_HAVE_D_INO + t->items[2] = MP_OBJ_NEW_SMALL_INT(dirent->d_ino); + #else + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); + #endif + return MP_OBJ_FROM_PTR(t); +} + +STATIC mp_obj_t mod_os_ilistdir(size_t n_args, const mp_obj_t *args) { + const char *path = "."; + if (n_args > 0) { + path = mp_obj_str_get_str(args[0]); + } + 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); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_ilistdir_obj, 0, 1, mod_os_ilistdir); + +STATIC mp_obj_t mod_os_errno(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return MP_OBJ_NEW_SMALL_INT(errno); + } + + errno = mp_obj_get_int(args[0]); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj, 0, 1, mod_os_errno); + +STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mod_os_stat_obj) }, + #if MICROPY_PY_OS_STATVFS + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mod_os_statvfs_obj) }, + #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) }, + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_os_globals, mp_module_os_globals_table); + +const mp_obj_module_t mp_module_os = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_os_globals, +}; diff --git a/ports/wapy-unix/modtermios.c b/ports/wapy-unix/modtermios.c new file mode 100644 index 000000000..7a578becb --- /dev/null +++ b/ports/wapy-unix/modtermios.c @@ -0,0 +1,150 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2015 Paul Sokolovsky + * + * 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 "py/objlist.h" +#include "py/runtime.h" +#include "py/mphal.h" + +STATIC mp_obj_t mod_termios_tcgetattr(mp_obj_t fd_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + + int res = tcgetattr(fd, &term); + RAISE_ERRNO(res, errno); + + mp_obj_list_t *r = MP_OBJ_TO_PTR(mp_obj_new_list(7, NULL)); + r->items[0] = MP_OBJ_NEW_SMALL_INT(term.c_iflag); + r->items[1] = MP_OBJ_NEW_SMALL_INT(term.c_oflag); + r->items[2] = MP_OBJ_NEW_SMALL_INT(term.c_cflag); + r->items[3] = MP_OBJ_NEW_SMALL_INT(term.c_lflag); + r->items[4] = MP_OBJ_NEW_SMALL_INT(cfgetispeed(&term)); + r->items[5] = MP_OBJ_NEW_SMALL_INT(cfgetospeed(&term)); + + mp_obj_list_t *cc = MP_OBJ_TO_PTR(mp_obj_new_list(NCCS, NULL)); + r->items[6] = MP_OBJ_FROM_PTR(cc); + for (int i = 0; i < NCCS; i++) { + if (i == VMIN || i == VTIME) { + cc->items[i] = MP_OBJ_NEW_SMALL_INT(term.c_cc[i]); + } else { + // https://docs.python.org/3/library/termios.html says value is *string*, + // but no way unicode chars could be there, if c_cc is defined to be a + // a "char". But it's type is actually cc_t, which can be anything. + // TODO: For now, we still deal with it like that. + cc->items[i] = mp_obj_new_bytes((byte *)&term.c_cc[i], 1); + } + } + return MP_OBJ_FROM_PTR(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_termios_tcgetattr_obj, mod_termios_tcgetattr); + +STATIC mp_obj_t mod_termios_tcsetattr(mp_obj_t fd_in, mp_obj_t when_in, mp_obj_t attrs_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + int when = mp_obj_get_int(when_in); + if (when == 0) { + // We don't export TCSANOW and friends to save on code space. Then + // common lazy sense says that passing 0 should be godo enough, and + // it is e.g. for glibc. But for other libc's it's not, so set just + // treat 0 as defauling to TCSANOW. + when = TCSANOW; + } + + assert(mp_obj_is_type(attrs_in, &mp_type_list)); + mp_obj_list_t *attrs = MP_OBJ_TO_PTR(attrs_in); + + term.c_iflag = mp_obj_get_int(attrs->items[0]); + term.c_oflag = mp_obj_get_int(attrs->items[1]); + term.c_cflag = mp_obj_get_int(attrs->items[2]); + term.c_lflag = mp_obj_get_int(attrs->items[3]); + + mp_obj_list_t *cc = MP_OBJ_TO_PTR(attrs->items[6]); + for (int i = 0; i < NCCS; i++) { + if (i == VMIN || i == VTIME) { + term.c_cc[i] = mp_obj_get_int(cc->items[i]); + } else { + term.c_cc[i] = *mp_obj_str_get_str(cc->items[i]); + } + } + + int res = cfsetispeed(&term, mp_obj_get_int(attrs->items[4])); + RAISE_ERRNO(res, errno); + res = cfsetospeed(&term, mp_obj_get_int(attrs->items[5])); + RAISE_ERRNO(res, errno); + + res = tcsetattr(fd, when, &term); + RAISE_ERRNO(res, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_termios_tcsetattr_obj, mod_termios_tcsetattr); + +STATIC mp_obj_t mod_termios_setraw(mp_obj_t fd_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + int res = tcgetattr(fd, &term); + RAISE_ERRNO(res, errno); + + term.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + term.c_oflag = 0; + term.c_cflag = (term.c_cflag & ~(CSIZE | PARENB)) | CS8; + term.c_lflag = 0; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + res = tcsetattr(fd, TCSAFLUSH, &term); + RAISE_ERRNO(res, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_termios_setraw_obj, mod_termios_setraw); + +STATIC const mp_rom_map_elem_t mp_module_termios_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_termios) }, + { MP_ROM_QSTR(MP_QSTR_tcgetattr), MP_ROM_PTR(&mod_termios_tcgetattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_tcsetattr), MP_ROM_PTR(&mod_termios_tcsetattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setraw), MP_ROM_PTR(&mod_termios_setraw_obj) }, + +#define C(name) { MP_ROM_QSTR(MP_QSTR_##name), MP_ROM_INT(name) } + C(TCSANOW), + + C(B9600), + #ifdef B57600 + C(B57600), + #endif + #ifdef B115200 + C(B115200), + #endif +#undef C +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_termios_globals, mp_module_termios_globals_table); + +const mp_obj_module_t mp_module_termios = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_termios_globals, +}; diff --git a/ports/wapy-unix/modtime.c b/ports/wapy-unix/modtime.c new file mode 100644 index 000000000..d39dec8d9 --- /dev/null +++ b/ports/wapy-unix/modtime.c @@ -0,0 +1,225 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Paul Sokolovsky + * Copyright (c) 2014-2017 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/mpconfig.h" +#if MICROPY_PY_UTIME + +#include +#include +#include +#include +#include +#include + +#include "py/runtime.h" +#include "py/smallint.h" +#include "py/mphal.h" +#include "extmod/utime_mphal.h" + +#ifdef _WIN32 +static inline int msec_sleep_tv(struct timeval *tv) { + msec_sleep(tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0); + return 0; +} +#define sleep_select(a,b,c,d,e) msec_sleep_tv((e)) +#else +#define sleep_select select +#endif + +// mingw32 defines CLOCKS_PER_SEC as ((clock_t)) but preprocessor does not handle casts +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) +#define MP_REMOVE_BRACKETSA(x) +#define MP_REMOVE_BRACKETSB(x) MP_REMOVE_BRACKETSA x +#define MP_REMOVE_BRACKETSC(x) MP_REMOVE_BRACKETSB x +#define MP_CLOCKS_PER_SEC MP_REMOVE_BRACKETSC(CLOCKS_PER_SEC) +#else +#define MP_CLOCKS_PER_SEC CLOCKS_PER_SEC +#endif + +#if defined(MP_CLOCKS_PER_SEC) +#define CLOCK_DIV (MP_CLOCKS_PER_SEC / 1000.0F) +#else +#error Unsupported clock() implementation +#endif + +STATIC mp_obj_t mod_time_time(void) { + #if MICROPY_PY_BUILTINS_FLOAT + struct timeval tv; + gettimeofday(&tv, NULL); + mp_float_t val = tv.tv_sec + (mp_float_t)tv.tv_usec / 1000000; + return mp_obj_new_float(val); + #else + return mp_obj_new_int((mp_int_t)time(NULL)); + #endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_obj, mod_time_time); + +// Note: this is deprecated since CPy3.3, but pystone still uses it. +STATIC mp_obj_t mod_time_clock(void) { + #if MICROPY_PY_BUILTINS_FLOAT + // 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. + mp_float_t d = (double)(clock() / 1000) / (double)CLOCK_DIV ; + return mp_obj_new_float(d ); + #else + return mp_obj_new_int((mp_int_t)clock()); + #endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_clock_obj, mod_time_clock); + +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 = (long int)round(modf(val, &ipart) * 1000000); + tv.tv_sec = (long int)ipart; + int res; + while (1) { + MP_THREAD_GIL_EXIT(); + res = sleep_select(0, NULL, NULL, NULL, &tv); + MP_THREAD_GIL_ENTER(); + #if MICROPY_SELECT_REMAINING_TIME + // TODO: This assumes Linux behavior of modifying tv to the remaining + // time. + if (res != -1 || errno != EINTR) { + break; + } + mp_handle_pending(true); + //printf("select: EINTR: %ld:%ld\n", tv.tv_sec, tv.tv_usec); + #else + break; + #endif + } + RAISE_ERRNO(res, errno); + #else + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_obj, mod_time_sleep); + +STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { + time_t t; + if (n_args == 0) { + t = time(NULL); + } else { + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(args[0]); + t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val); + #else + t = mp_obj_get_int(args[0]); + #endif + } + struct tm *tm = localtime(&t); + + mp_obj_t ret = mp_obj_new_tuple(9, NULL); + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(ret); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(tm->tm_year + 1900); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(tm->tm_mon + 1); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(tm->tm_mday); + tuple->items[3] = MP_OBJ_NEW_SMALL_INT(tm->tm_hour); + tuple->items[4] = MP_OBJ_NEW_SMALL_INT(tm->tm_min); + tuple->items[5] = MP_OBJ_NEW_SMALL_INT(tm->tm_sec); + int wday = tm->tm_wday - 1; + if (wday < 0) { + wday = 6; + } + tuple->items[6] = MP_OBJ_NEW_SMALL_INT(wday); + tuple->items[7] = MP_OBJ_NEW_SMALL_INT(tm->tm_yday + 1); + tuple->items[8] = MP_OBJ_NEW_SMALL_INT(tm->tm_isdst); + + return ret; +} +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("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, "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) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mod_time_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mod_time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, + { 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); + +const mp_obj_module_t mp_module_time = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_time_globals, +}; + +#endif // MICROPY_PY_UTIME diff --git a/ports/wapy-unix/moduos_vfs.c b/ports/wapy-unix/moduos_vfs.c new file mode 100644 index 000000000..6e4f352aa --- /dev/null +++ b/ports/wapy-unix/moduos_vfs.c @@ -0,0 +1,94 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "extmod/vfs_fat.h" +#include "extmod/vfs_lfs.h" + +#if MICROPY_VFS + +// 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[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos_vfs) }, + { MP_ROM_QSTR(MP_QSTR_sep), MP_ROM_QSTR(MP_QSTR__slash_) }, + + { 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) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, + + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename),MP_ROM_PTR(&mp_vfs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove + + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif + + #if MICROPY_VFS_POSIX + { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, + #endif + #if MICROPY_VFS_FAT + { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, + #endif + #if MICROPY_VFS_LFS1 + { MP_ROM_QSTR(MP_QSTR_VfsLfs1), MP_ROM_PTR(&mp_type_vfs_lfs1) }, + #endif + #if MICROPY_VFS_LFS2 + { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(uos_vfs_module_globals, uos_vfs_module_globals_table); + +const mp_obj_module_t mp_module_uos_vfs = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&uos_vfs_module_globals, +}; + +#endif // MICROPY_VFS diff --git a/ports/wapy-unix/moduselect.c b/ports/wapy-unix/moduselect.c new file mode 100644 index 000000000..400040c61 --- /dev/null +++ b/ports/wapy-unix/moduselect.c @@ -0,0 +1,363 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * Copyright (c) 2015-2017 Paul Sokolovsky + * + * 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/mpconfig.h" + +#if MICROPY_PY_USELECT_POSIX + +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#define DEBUG 0 + +#if MICROPY_PY_SOCKET +extern const mp_obj_type_t mp_type_socket; +#endif + +// Flags for poll() +#define FLAG_ONESHOT (1) + +/// \class Poll - poll class + +typedef struct _mp_obj_poll_t { + mp_obj_base_t base; + unsigned short alloc; + unsigned short len; + struct pollfd *entries; + mp_obj_t *obj_map; + short iter_cnt; + short iter_idx; + int flags; + // callee-owned tuple + mp_obj_t ret_tuple; +} mp_obj_poll_t; + +STATIC int get_fd(mp_obj_t fdlike) { + if (mp_obj_is_obj(fdlike)) { + const mp_stream_p_t *stream_p = mp_get_stream_raise(fdlike, MP_STREAM_OP_IOCTL); + int err; + mp_uint_t res = stream_p->ioctl(fdlike, MP_STREAM_GET_FILENO, 0, &err); + if (res != MP_STREAM_ERROR) { + return res; + } + } + return mp_obj_get_int(fdlike); +} + +/// \method register(obj[, eventmask]) +STATIC mp_obj_t poll_register(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + bool is_fd = mp_obj_is_int(args[1]); + int fd = get_fd(args[1]); + + mp_uint_t flags; + if (n_args == 3) { + flags = mp_obj_get_int(args[2]); + } else { + flags = POLLIN | POLLOUT; + } + + struct pollfd *free_slot = NULL; + + struct pollfd *entry = self->entries; + for (int i = 0; i < self->len; i++, entry++) { + int entry_fd = entry->fd; + if (entry_fd == fd) { + entry->events = flags; + return mp_const_false; + } + if (entry_fd == -1) { + free_slot = entry; + } + } + + if (free_slot == NULL) { + if (self->len >= self->alloc) { + self->entries = m_renew(struct pollfd, self->entries, self->alloc, self->alloc + 4); + if (self->obj_map) { + self->obj_map = m_renew(mp_obj_t, self->obj_map, self->alloc, self->alloc + 4); + } + self->alloc += 4; + } + free_slot = &self->entries[self->len++]; + } + + if (!is_fd) { + if (self->obj_map == NULL) { + self->obj_map = m_new0(mp_obj_t, self->alloc); + } + self->obj_map[free_slot - self->entries] = args[1]; + } + + free_slot->fd = fd; + free_slot->events = flags; + free_slot->revents = 0; + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register); + +/// \method unregister(obj) +STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + struct pollfd *entries = self->entries; + int fd = get_fd(obj_in); + for (int i = self->len - 1; i >= 0; i--) { + if (entries->fd == fd) { + entries->fd = -1; + if (self->obj_map) { + self->obj_map[entries - self->entries] = MP_OBJ_NULL; + } + break; + } + entries++; + } + + // TODO raise KeyError if obj didn't exist in map + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(poll_unregister_obj, poll_unregister); + +/// \method modify(obj, eventmask) +STATIC mp_obj_t poll_modify(mp_obj_t self_in, mp_obj_t obj_in, mp_obj_t eventmask_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + struct pollfd *entries = self->entries; + int fd = get_fd(obj_in); + for (int i = self->len - 1; i >= 0; i--) { + if (entries->fd == fd) { + entries->events = mp_obj_get_int(eventmask_in); + return mp_const_none; + } + entries++; + } + + // obj doesn't exist in poller + return mp_raise_OSError_o(MP_ENOENT); +} +MP_DEFINE_CONST_FUN_OBJ_3(poll_modify_obj, poll_modify); + +STATIC int poll_poll_internal(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + // work out timeout (it's given already in ms) + int timeout = -1; + int flags = 0; + if (n_args >= 2) { + if (args[1] != mp_const_none) { + mp_int_t timeout_i = mp_obj_get_int(args[1]); + if (timeout_i >= 0) { + timeout = timeout_i; + } + } + if (n_args >= 3) { + flags = mp_obj_get_int(args[2]); + } + } + + self->flags = flags; + + MP_THREAD_GIL_EXIT(); + int n_ready = poll(self->entries, self->len, timeout); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO_R(n_ready, errno, -1); + return n_ready; +} + +/// \method poll([timeout]) +/// Timeout is in milliseconds. +STATIC mp_obj_t poll_poll(size_t n_args, const mp_obj_t *args) { + int n_ready = poll_poll_internal(n_args, args); + + if (n_ready < 0) { + return MP_OBJ_NULL; + } + + if (n_ready == 0) { + return mp_const_empty_tuple; + } + + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + mp_obj_list_t *ret_list = MP_OBJ_TO_PTR(mp_obj_new_list(n_ready, NULL)); + int ret_i = 0; + struct pollfd *entries = self->entries; + for (int i = 0; i < self->len; i++, entries++) { + if (entries->revents != 0) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + // If there's an object stored, return it, otherwise raw fd + if (self->obj_map && self->obj_map[i] != MP_OBJ_NULL) { + t->items[0] = self->obj_map[i]; + } else { + t->items[0] = MP_OBJ_NEW_SMALL_INT(entries->fd); + } + t->items[1] = MP_OBJ_NEW_SMALL_INT(entries->revents); + ret_list->items[ret_i++] = MP_OBJ_FROM_PTR(t); + if (self->flags & FLAG_ONESHOT) { + entries->events = 0; + } + } + } + + return MP_OBJ_FROM_PTR(ret_list); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_poll_obj, 1, 3, poll_poll); + +STATIC mp_obj_t poll_ipoll(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->ret_tuple == MP_OBJ_NULL) { + self->ret_tuple = mp_obj_new_tuple(2, NULL); + } + + int n_ready = poll_poll_internal(n_args, args); + if (n_ready < 0) { + return MP_OBJ_NULL; + } + + self->iter_cnt = n_ready; + self->iter_idx = 0; + + return args[0]; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_ipoll_obj, 1, 3, poll_ipoll); + +STATIC mp_obj_t poll_iternext(mp_obj_t self_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->iter_cnt == 0) { + return MP_OBJ_STOP_ITERATION; + } + + self->iter_cnt--; + + struct pollfd *entries = self->entries + self->iter_idx; + for (int i = self->iter_idx; i < self->len; i++, entries++) { + self->iter_idx++; + if (entries->revents != 0) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->ret_tuple); + // If there's an object stored, return it, otherwise raw fd + if (self->obj_map && self->obj_map[i] != MP_OBJ_NULL) { + t->items[0] = self->obj_map[i]; + } else { + t->items[0] = MP_OBJ_NEW_SMALL_INT(entries->fd); + } + t->items[1] = MP_OBJ_NEW_SMALL_INT(entries->revents); + if (self->flags & FLAG_ONESHOT) { + entries->events = 0; + } + return MP_OBJ_FROM_PTR(t); + } + } + + assert(!"inconsistent number of poll active entries"); + self->iter_cnt = 0; + return MP_OBJ_STOP_ITERATION; +} + +#if DEBUG +STATIC mp_obj_t poll_dump(mp_obj_t self_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + + struct pollfd *entries = self->entries; + for (int i = self->len - 1; i >= 0; i--) { + printf("fd: %d ev: %x rev: %x", entries->fd, entries->events, entries->revents); + if (self->obj_map) { + printf(" obj: %p", self->obj_map[entries - self->entries]); + } + printf("\n"); + entries++; + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(poll_dump_obj, poll_dump); +#endif + +STATIC const mp_rom_map_elem_t poll_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_register), MP_ROM_PTR(&poll_register_obj) }, + { MP_ROM_QSTR(MP_QSTR_unregister), MP_ROM_PTR(&poll_unregister_obj) }, + { MP_ROM_QSTR(MP_QSTR_modify), MP_ROM_PTR(&poll_modify_obj) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&poll_poll_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipoll), MP_ROM_PTR(&poll_ipoll_obj) }, + #if DEBUG + { MP_ROM_QSTR(MP_QSTR_dump), MP_ROM_PTR(&poll_dump_obj) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(poll_locals_dict, poll_locals_dict_table); + +STATIC const mp_obj_type_t mp_type_poll = { + { &mp_type_type }, + .name = MP_QSTR_poll, + .getiter = mp_identity_getiter, + .iternext = poll_iternext, + .locals_dict = (void *)&poll_locals_dict, +}; + +STATIC mp_obj_t select_poll(size_t n_args, const mp_obj_t *args) { + int alloc = 4; + if (n_args > 0) { + alloc = mp_obj_get_int(args[0]); + } + mp_obj_poll_t *poll = m_new_obj(mp_obj_poll_t); + poll->base.type = &mp_type_poll; + poll->entries = m_new(struct pollfd, alloc); + poll->alloc = alloc; + poll->len = 0; + poll->obj_map = NULL; + poll->iter_cnt = 0; + poll->ret_tuple = MP_OBJ_NULL; + return MP_OBJ_FROM_PTR(poll); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_select_poll_obj, 0, 1, select_poll); + +STATIC const mp_rom_map_elem_t mp_module_select_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uselect) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mp_select_poll_obj) }, + { MP_ROM_QSTR(MP_QSTR_POLLIN), MP_ROM_INT(POLLIN) }, + { MP_ROM_QSTR(MP_QSTR_POLLOUT), MP_ROM_INT(POLLOUT) }, + { MP_ROM_QSTR(MP_QSTR_POLLERR), MP_ROM_INT(POLLERR) }, + { MP_ROM_QSTR(MP_QSTR_POLLHUP), MP_ROM_INT(POLLHUP) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_select_globals, mp_module_select_globals_table); + +const mp_obj_module_t mp_module_uselect = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_select_globals, +}; + +#endif // MICROPY_PY_USELECT_POSIX diff --git a/ports/wapy-unix/modusocket.c b/ports/wapy-unix/modusocket.c new file mode 100644 index 000000000..e1f9a6b5d --- /dev/null +++ b/ports/wapy-unix/modusocket.c @@ -0,0 +1,667 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Paul Sokolovsky + * 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 "py/mpthread.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); + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, read(o->fd, buf, size), { + // 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 (mp_uint_t)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); + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, write(o->fd, buf, size), { + // 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 (mp_uint_t)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. + MP_THREAD_GIL_EXIT(); + close(self->fd); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int r = connect(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int r = bind(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int r = listen(self->fd, MP_OBJ_SMALL_INT_VALUE(backlog_in)); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int fd = accept(self->fd, (struct sockaddr *)&addr, &addr_len); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int out_sz = recv(self->fd, buf, sz, flags); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int out_sz = recvfrom(self->fd, buf, sz, flags, (struct sockaddr *)&addr, &addr_len); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int out_sz = send(self->fd, bufinfo.buf, bufinfo.len, flags); + MP_THREAD_GIL_ENTER(); + 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); + MP_THREAD_GIL_EXIT(); + int out_sz = sendto(self->fd, bufinfo.buf, bufinfo.len, flags, + (struct sockaddr *)addr_bi.buf, addr_bi.len); + MP_THREAD_GIL_ENTER(); + 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; + } + MP_THREAD_GIL_EXIT(); + int r = setsockopt(self->fd, level, option, optval, optlen); + MP_THREAD_GIL_ENTER(); + 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 = (long int)round(modf(val, &ipart) * 1000000); + tv.tv_sec = (long int)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; + MP_THREAD_GIL_EXIT(); + r = setsockopt(self->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); + if (r == -1) { + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + } + r = setsockopt(self->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval)); + MP_THREAD_GIL_ENTER(); + 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, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + (void)n_kw; + + 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]); + } + } + } + MP_THREAD_GIL_EXIT(); + int fd = socket(family, type, proto); + MP_THREAD_GIL_ENTER(); + 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 = { + .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) { + return mp_raise_OSError_o(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) { + return mp_raise_OSError_o(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; + MP_THREAD_GIL_EXIT(); + int res = getaddrinfo(host, serv, &hints, &addr_list); + MP_THREAD_GIL_ENTER(); + + if (res != 0) { + // CPython: socket.gaierror +#if NO_NLR + return mp_raise_o( mp_obj_new_exception_msg_varg(&mp_type_OSError, "[addrinfo error %d]", res)); +#else + mp_raise_msg_varg( &mp_type_OSError, "[addrinfo error %d]", res); +#endif + } + 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/wapy-unix/mpconfigport.h b/ports/wapy-unix/mpconfigport.h new file mode 100644 index 000000000..0b0901865 --- /dev/null +++ b/ports/wapy-unix/mpconfigport.h @@ -0,0 +1,400 @@ +/* + * 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. + */ +#ifndef NO_NLR +#define NO_NLR (1) +#endif + +#define MICROPY_PY_OS_DUPTERM (0) +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_EMIT_WASM (1) +#define MICROPY_PY_FSTRING (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) + + +#ifdef MICROPY_EMIT_NATIVE + #undef MICROPY_EMIT_NATIVE +#endif + +#ifdef MICROPY_ENABLE_SCHEDULER +#undef MICROPY_ENABLE_SCHEDULER +#endif +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_PY_GENERATOR_PEND_THROW (0) +#define MICROPY_ENABLE_PYSTACK (1) + +// 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) +#if !defined(MICROPY_EMIT_X64) && defined(__x86_64__) + #define MICROPY_EMIT_X64 (1) +#endif +#if !defined(MICROPY_EMIT_X86) && defined(__i386__) + #define MICROPY_EMIT_X86 (1) +#endif +#if !defined(MICROPY_EMIT_THUMB) && defined(__thumb2__) + #define MICROPY_EMIT_THUMB (1) + #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) +#endif +// Some compilers define __thumb2__ and __arm__ at the same time, let +// autodetected thumb2 emitter have priority. +#if !defined(MICROPY_EMIT_ARM) && defined(__arm__) && !defined(__thumb2__) + #define MICROPY_EMIT_ARM (1) +#endif +#define MICROPY_COMP_MODULE_CONST (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_RETURN_IF_EXPR (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#define MICROPY_MEM_STATS (1) +#define MICROPY_DEBUG_PRINTERS (1) +// Printing debug to stderr may give tests which +// check stdout a chance to pass, etc. +#define MICROPY_DEBUG_PRINTER (&mp_stderr_print) +#define MICROPY_READER_POSIX (1) + +#define MICROPY_USE_READLINE_HISTORY (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_REPL_EMACS_KEYS (1) +#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 +#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (1) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_VFS_POSIX_FILE (1) +#if NO_NLR + #undef MICROPY_VFS_POSIX_FILE + #define MICROPY_VFS_POSIX_FILE (0) +#endif +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_DESCRIPTORS (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + #error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_FROZENSET (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) +#define MICROPY_PY_BUILTINS_INPUT (1) +#define MICROPY_PY_BUILTINS_POW3 (1) +#define MICROPY_PY_BUILTINS_ROUND_INT (1) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#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) +#define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#endif +#define MICROPY_PY_MATH_ISCLOSE (MICROPY_PY_MATH_SPECIAL_FUNCTIONS) +#define MICROPY_PY_CMATH (1) +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_GC_COLLECT_RETVAL (1) + +#ifndef MICROPY_STACKLESS +#define MICROPY_STACKLESS (0) +#define MICROPY_STACKLESS_STRICT (0) +#endif + +#define MICROPY_PY_OS_STATVFS (1) +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UZLIB (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UHASHLIB (1) +#if MICROPY_PY_USSL +#define MICROPY_PY_UHASHLIB_MD5 (1) +#define MICROPY_PY_UHASHLIB_SHA1 (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#endif +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UBINASCII_CRC32 (1) +#define MICROPY_PY_URANDOM (1) +#ifndef MICROPY_PY_USELECT_POSIX +#define MICROPY_PY_USELECT_POSIX (1) +#endif +#define MICROPY_PY_UWEBSOCKET (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr +#define MICROPY_MACHINE_MEM_GET_WRITE_ADDR mod_machine_mem_get_addr + +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ + +// Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc. +// names in exception messages (may require more RAM). +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_WARNINGS (1) +#define MICROPY_ERROR_PRINTER (&mp_stderr_print) +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) + +extern const struct _mp_print_t mp_stderr_print; + +#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) +#define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (256) +#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; +extern const struct _mp_obj_module_t mp_module_uselect; +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_termios; +extern const struct _mp_obj_module_t mp_module_socket; +extern const struct _mp_obj_module_t mp_module_ffi; +extern const struct _mp_obj_module_t mp_module_jni; + +#if MICROPY_PY_UOS_VFS +#define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else +#define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, +#endif +#if MICROPY_PY_FFI +#define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else +#define MICROPY_PY_FFI_DEF +#endif +#if MICROPY_PY_JNI +#define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else +#define MICROPY_PY_JNI_DEF +#endif +#if MICROPY_PY_UTIME +#define MICROPY_PY_UTIME_DEF { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_time) }, +#else +#define MICROPY_PY_UTIME_DEF +#endif +#if MICROPY_PY_TERMIOS +#define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else +#define MICROPY_PY_TERMIOS_DEF +#endif +#if MICROPY_PY_SOCKET +#define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else +#define MICROPY_PY_SOCKET_DEF +#endif +#if MICROPY_PY_USELECT_POSIX +#define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else +#define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PORT_BUILTIN_MODULES \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_UTIME_DEF \ + MICROPY_PY_SOCKET_DEF \ + { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&mp_module_machine) }, \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + +// type definitions for the specific machine + +// For size_t and ssize_t +#include + +// assume that if we already defined the obj repr then we also defined types +#ifndef MICROPY_OBJ_REPR +#ifdef __LP64__ +typedef long mp_int_t; // must be pointer size +typedef unsigned long mp_uint_t; // must be pointer size +#else +// These are definitions for machines where sizeof(int) == sizeof(void*), +// regardless of actual size. +typedef int mp_int_t; // must be pointer size +typedef unsigned int mp_uint_t; // must be pointer size +#endif +#endif + +// Cannot include , as it may lead to symbol name clashes +#if _FILE_OFFSET_BITS == 64 && !defined(__LP64__) +typedef long long mp_off_t; +#else +typedef long mp_off_t; +#endif + +void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size); +void mp_unix_free_exec(void *ptr, size_t size); +void mp_unix_mark_exec(void); +#define MP_PLAT_ALLOC_EXEC(min_size, ptr, size) mp_unix_alloc_exec(min_size, ptr, size) +#define MP_PLAT_FREE_EXEC(ptr, size) mp_unix_free_exec(ptr, size) +#ifndef MICROPY_FORCE_PLAT_ALLOC_EXEC +// Use MP_PLAT_ALLOC_EXEC for any executable memory allocation, including for FFI +// (overriding libffi own implementation) +#define MICROPY_FORCE_PLAT_ALLOC_EXEC (1) +#endif + +#ifdef __linux__ +// Can access physical memory using /dev/mem +#define MICROPY_PLAT_DEV_MEM (1) +#endif + +// Assume that select() call, interrupted with a signal, and erroring +// with EINTR, updates remaining timeout value. +#define MICROPY_SELECT_REMAINING_TIME (1) + +#ifdef __ANDROID__ +#include +#if __ANDROID_API__ < 4 +// Bionic libc in Android 1.5 misses these 2 functions +#define MP_NEED_LOG2 (1) +#define nan(x) NAN +#endif +#endif + +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +#define MP_STATE_PORT MP_STATE_VM + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +struct _mp_bluetooth_btstack_root_pointers_t; +#define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers; +#else +#define MICROPY_BLUETOOTH_ROOT_POINTERS +#endif + +#define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[50]; \ + void *mmap_region_head; \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + MICROPY_BLUETOOTH_ROOT_POINTERS \ + +// We need to provide a declaration/definition of alloca() +// unless support for it is disabled. +#if !defined(MICROPY_NO_ALLOCA) || MICROPY_NO_ALLOCA == 0 +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#endif + +// From "man readdir": "Under glibc, programs can check for the availability +// of the fields [in struct dirent] not defined in POSIX.1 by testing whether +// the macros [...], _DIRENT_HAVE_D_TYPE are defined." +// Other libc's don't define it, but proactively assume that dirent->d_type +// is available on a modern *nix system. +#ifndef _DIRENT_HAVE_D_TYPE +#define _DIRENT_HAVE_D_TYPE (1) +#endif +// This macro is not provided by glibc but we need it so ports that don't have +// dirent->d_ino can disable the use of this field. +#ifndef _DIRENT_HAVE_D_INO +#define _DIRENT_HAVE_D_INO (1) +#endif + +#ifndef __APPLE__ +// For debugging purposes, make printf() available to any source file. +#include +#endif + +#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/wapy-unix/mpconfigport.mk b/ports/wapy-unix/mpconfigport.mk new file mode 100644 index 000000000..3a66d997b --- /dev/null +++ b/ports/wapy-unix/mpconfigport.mk @@ -0,0 +1,40 @@ +# Enable/disable modules and 3rd-party libs to be included in interpreter + +# Build 32-bit binaries on a 64-bit host +MICROPY_FORCE_32BIT = 0 + +# This variable can take the following values: +# 0 - no readline, just simple stdin input +# 1 - use MicroPython version of readline +MICROPY_USE_READLINE = 1 + +# btree module using Berkeley DB 1.xx +MICROPY_PY_BTREE = 1 + +# _thread module using pthreads +MICROPY_PY_THREAD = 1 + +# Subset of CPython termios module +MICROPY_PY_TERMIOS = 1 + +# Subset of CPython socket module +MICROPY_PY_SOCKET = 1 + +# ffi module requires libffi (libffi-dev Debian package) +MICROPY_PY_FFI = 1 + +# ussl module requires one of the TLS libraries below +MICROPY_PY_USSL = 1 +# axTLS has minimal size but implements only a subset of modern TLS +# functionality, so may have problems with some servers. +MICROPY_SSL_AXTLS = 1 +# mbedTLS is more up to date and complete implementation, but also +# more bloated. +MICROPY_SSL_MBEDTLS = 0 + +# jni module requires JVM/JNI +MICROPY_PY_JNI = 0 + +# Avoid using system libraries, use copies bundled with MicroPython +# as submodules (currently affects only libffi). +MICROPY_STANDALONE = 0 diff --git a/ports/wapy-unix/mphalport.h b/ports/wapy-unix/mphalport.h new file mode 100644 index 000000000..3c7c3a878 --- /dev/null +++ b/ports/wapy-unix/mphalport.h @@ -0,0 +1,109 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 + +#ifndef CHAR_CTRL_C +#define CHAR_CTRL_C (3) +#endif + +void mp_hal_set_interrupt_char(char c); + +#define mp_hal_stdio_poll unused // this is not implemented, nor needed +void mp_hal_stdio_mode_raw(void); +void mp_hal_stdio_mode_orig(void); + +#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 +#define mp_hal_readline mp_hal_readline +static inline int mp_hal_readline(vstr_t *vstr, const char *p) { + mp_hal_stdio_mode_raw(); + int ret = readline(vstr, p); + mp_hal_stdio_mode_orig(); + return ret; +} + +#endif + +// TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep: +// "The useconds argument shall be less than one million." +static inline void mp_hal_delay_ms(mp_uint_t ms) { + usleep((ms) * 1000); +} +static inline void mp_hal_delay_us(mp_uint_t us) { + usleep(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) \ + { return mp_raise_OSError_o(error_val); } } + +#define RAISE_ERRNO_R(err_flag, error_val, ret_on_err) \ + { if (err_flag == -1) \ + { mp_raise_OSError_o(error_val); return ret_on_err; } } + +#if MICROPY_PY_BLUETOOTH +enum { + MP_HAL_MAC_BDADDR, +}; + +void mp_hal_get_mac(int idx, uint8_t buf[6]); +#endif diff --git a/ports/wapy-unix/mpthreadport.c b/ports/wapy-unix/mpthreadport.c new file mode 100644 index 000000000..c24851576 --- /dev/null +++ b/ports/wapy-unix/mpthreadport.c @@ -0,0 +1,310 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * + * 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 "py/runtime.h" +#include "py/mpthread.h" +#include "py/gc.h" + +#if MICROPY_PY_THREAD + +#include +#include +#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 + int ready; // whether the thread is ready and running + void *arg; // thread Python args, a GC root pointer + struct _thread_t *next; +} thread_t; + +STATIC pthread_key_t tls_key; + +// 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 = PTHREAD_MUTEX_INITIALIZER; +STATIC thread_t *thread; + +// this is used to synchronise the signal handler of the thread +// it's needed because we can't use any pthread calls in a signal handler +#if defined(__APPLE__) +STATIC char thread_signal_done_name[25]; +STATIC sem_t *thread_signal_done_p; +#else +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 == 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 + // gc_collect_root((void**)context, sizeof(ucontext_t) / sizeof(uintptr_t)); + #if MICROPY_ENABLE_PYSTACK + void **ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start); + gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *)); + #endif + #if defined(__APPLE__) + sem_post(thread_signal_done_p); + #else + sem_post(&thread_signal_done); + #endif + } +} + +void mp_thread_init(void) { + pthread_key_create(&tls_key, NULL); + pthread_setspecific(tls_key, &mp_state_ctx.thread); + + // create first entry in linked list of all threads + thread = malloc(sizeof(thread_t)); + thread->id = pthread_self(); + thread->ready = 1; + thread->arg = NULL; + thread->next = NULL; + + #if defined(__APPLE__) + snprintf(thread_signal_done_name, sizeof(thread_signal_done_name), "micropython_sem_%d", (int)thread->id); + thread_signal_done_p = sem_open(thread_signal_done_name, O_CREAT | O_EXCL, 0666, 0); + #else + sem_init(&thread_signal_done, 0, 0); + #endif + + // enable signal handler for garbage collection + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = mp_thread_gc; + sigemptyset(&sa.sa_mask); + sigaction(MP_THREAD_GC_SIGNAL, &sa, NULL); +} + +void mp_thread_deinit(void) { + mp_thread_unix_begin_atomic_section(); + while (thread->next != NULL) { + thread_t *th = thread; + thread = thread->next; + pthread_cancel(th->id); + free(th); + } + mp_thread_unix_end_atomic_section(); + #if defined(__APPLE__) + sem_close(thread_signal_done_p); + sem_unlink(thread_signal_done_name); + #endif + assert(thread->id == pthread_self()); + free(thread); +} + +// This function scans all pointers that are external to the current thread. +// It does this by signalling all other threads and getting them to scan their +// own registers and stack. Note that there may still be some edge cases left +// with race conditions and root-pointer scanning: a given thread may manipulate +// 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) { + 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()) { + continue; + } + if (!th->ready) { + continue; + } + pthread_kill(th->id, MP_THREAD_GC_SIGNAL); + #if defined(__APPLE__) + sem_wait(thread_signal_done_p); + #else + sem_wait(&thread_signal_done); + #endif + } + 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(mp_state_thread_t *state) { + pthread_setspecific(tls_key, state); +} + +void mp_thread_start(void) { + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + 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; + } + } + mp_thread_unix_end_atomic_section(); +} + +void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { + // default stack size is 8k machine-words + if (*stack_size == 0) { + *stack_size = 8192 * BYTES_PER_WORD; + } + + // minimum stack size is set by pthreads + if (*stack_size < PTHREAD_STACK_MIN) { + *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); + if (ret != 0) { + goto er; + } + ret = pthread_attr_setstacksize(&attr, *stack_size); + if (ret != 0) { + goto er; + } + + ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ret != 0) { + goto er; + } + + mp_thread_unix_begin_atomic_section(); + + // create thread + pthread_t id; + ret = pthread_create(&id, &attr, entry, arg); + if (ret != 0) { + mp_thread_unix_end_atomic_section(); + goto er; + } + + // adjust stack_size to provide room to recover from hitting the limit + *stack_size -= THREAD_STACK_OVERFLOW_MARGIN; + + // add thread to linked list of all threads + thread_t *th = malloc(sizeof(thread_t)); + th->id = id; + th->ready = 0; + th->arg = arg; + th->next = thread; + thread = th; + + mp_thread_unix_end_atomic_section(); + + return; + +er: +#if NO_NLR + mp_raise_o( mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(ret)) ); +#else + mp_raise_OSError(ret); +#endif + +} + +void mp_thread_finish(void) { + 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()) { + if (prev == NULL) { + thread = th->next; + } else { + prev->next = th->next; + } + free(th); + break; + } + prev = th; + } + mp_thread_unix_end_atomic_section(); +} + +void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { + pthread_mutex_init(mutex, NULL); +} + +int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { + int ret; + if (wait) { + ret = pthread_mutex_lock(mutex); + if (ret == 0) { + return 1; + } + } else { + ret = pthread_mutex_trylock(mutex); + if (ret == 0) { + return 1; + } else if (ret == EBUSY) { + return 0; + } + } + return -ret; +} + +void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { + pthread_mutex_unlock(mutex); + // TODO check return value +} + +#endif // MICROPY_PY_THREAD diff --git a/ports/wapy-unix/mpthreadport.h b/ports/wapy-unix/mpthreadport.h new file mode 100644 index 000000000..a7dbe08c4 --- /dev/null +++ b/ports/wapy-unix/mpthreadport.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * + * 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 + +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/wapy-unix/qstrdefsport.h b/ports/wapy-unix/qstrdefsport.h new file mode 100644 index 000000000..8b827a8d6 --- /dev/null +++ b/ports/wapy-unix/qstrdefsport.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +// *FORMAT-OFF* diff --git a/ports/wapy-unix/unix_mphal.c b/ports/wapy-unix/unix_mphal.c new file mode 100644 index 000000000..30f14cd3c --- /dev/null +++ b/ports/wapy-unix/unix_mphal.c @@ -0,0 +1,220 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 "py/mphal.h" +#include "py/mpthread.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#ifndef _WIN32 +#include + +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); + // On entry to handler, its signal is blocked, and unblocked on + // normal exit. As we instead perform longjmp, unblock it manually. + sigprocmask(SIG_SETMASK, &mask, NULL); +#if NO_NLR + mp_raise_o(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); +#else + nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); +#endif + #else + if (MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))) { + // this is the second time we are called, so die straight away + exit(1); + } + mp_keyboard_interrupt(); + #endif + } +} +#endif + +void mp_hal_set_interrupt_char(char c) { + // configure terminal settings to (not) let ctrl-C through + if (c == CHAR_CTRL_C) { + #ifndef _WIN32 + // enable signal handler + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = sighandler; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + #endif + } else { + #ifndef _WIN32 + // disable signal handler + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + #endif + } +} + +#if MICROPY_USE_READLINE == 1 + +#include + +static struct termios orig_termios; + +void mp_hal_stdio_mode_raw(void) { + // save and set terminal settings + tcgetattr(0, &orig_termios); + static struct termios termios; + termios = orig_termios; + termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + termios.c_cflag = (termios.c_cflag & ~(CSIZE | PARENB)) | CS8; + termios.c_lflag = 0; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + tcsetattr(0, TCSAFLUSH, &termios); +} + +void mp_hal_stdio_mode_orig(void) { + // restore terminal settings + tcsetattr(0, TCSAFLUSH, &orig_termios); +} + +#endif + +#if MICROPY_PY_OS_DUPTERM +static int call_dupterm_read(size_t idx) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t read_m[3]; + mp_load_method(MP_STATE_VM(dupterm_objs[idx]), MP_QSTR_read, read_m); + read_m[2] = MP_OBJ_NEW_SMALL_INT(1); + mp_obj_t res = mp_call_method_n_kw(1, 0, read_m); + if (res == mp_const_none) { + return -2; + } + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(res, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len == 0) { + mp_printf(&mp_plat_print, "dupterm: EOF received, deactivating\n"); + MP_STATE_VM(dupterm_objs[idx]) = MP_OBJ_NULL; + return -1; + } + nlr_pop(); + return *(byte *)bufinfo.buf; + } else { + // Temporarily disable dupterm to avoid infinite recursion + mp_obj_t save_term = MP_STATE_VM(dupterm_objs[idx]); + MP_STATE_VM(dupterm_objs[idx]) = NULL; + mp_printf(&mp_plat_print, "dupterm: "); + mp_obj_print_exception(&mp_plat_print, nlr.ret_val); + MP_STATE_VM(dupterm_objs[idx]) = save_term; + } + + return -1; +} +#endif + +int mp_hal_stdin_rx_chr(void) { + #if MICROPY_PY_OS_DUPTERM + // TODO only support dupterm one slot at the moment + if (MP_STATE_VM(dupterm_objs[0]) != MP_OBJ_NULL) { + int c; + do { + c = call_dupterm_read(0); + } while (c == -2); + if (c == -1) { + goto main_term; + } + if (c == '\n') { + c = '\r'; + } + return c; + } +main_term:; + #endif + + unsigned char c; + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, read(STDIN_FILENO, &c, 1), {}); + if (ret == 0) { + c = 4; // EOF, ctrl-D + } else if (c == '\n') { + c = '\r'; + } + return c; +} + +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDOUT_FILENO, str, len), {}); + mp_uos_dupterm_tx_strn(str, len); +} + +// cooked is same as uncooked because the terminal does some postprocessing +void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) { + mp_hal_stdout_tx_strn(str, len); +} + +void mp_hal_stdout_tx_str(const char *str) { + mp_hal_stdout_tx_strn(str, strlen(str)); +} + +mp_uint_t mp_hal_ticks_ms(void) { + #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_sec * 1000 + tv.tv_nsec / 1000000; + #else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; + #endif +} + +mp_uint_t mp_hal_ticks_us(void) { + #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_sec * 1000000 + tv.tv_nsec / 1000; + #else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; + #endif +} diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py new file mode 100644 index 000000000..8ad0f1573 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py @@ -0,0 +1 @@ +print('frzmpy1') diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py new file mode 100644 index 000000000..1ad930db2 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py @@ -0,0 +1 @@ +raise ZeroDivisionError diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py new file mode 100644 index 000000000..8c023afeb --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzmpy_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py new file mode 100644 index 000000000..a66b505bf --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzmpy_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py b/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py new file mode 100644 index 000000000..051f2a9c1 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py @@ -0,0 +1,3 @@ +# Checks for regression on MP_QSTR_NULL +def returns_NULL(): + return "NULL" diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py new file mode 100644 index 000000000..6e88ac38d --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py @@ -0,0 +1 @@ +print('frzstr1') diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py new file mode 100644 index 000000000..1d1df9417 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzstr_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py new file mode 100644 index 000000000..bafb5978b --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzstr_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/variants/coverage/manifest.py b/ports/wapy-unix/variants/coverage/manifest.py new file mode 100644 index 000000000..611105088 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/manifest.py @@ -0,0 +1,2 @@ +freeze_as_str("frzstr") +freeze_as_mpy("frzmpy") diff --git a/ports/wapy-unix/variants/coverage/mpconfigvariant.h b/ports/wapy-unix/variants/coverage/mpconfigvariant.h new file mode 100644 index 000000000..802c2fe5f --- /dev/null +++ b/ports/wapy-unix/variants/coverage/mpconfigvariant.h @@ -0,0 +1,68 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-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 enables almost all possible features such that it can be used +// for coverage testing. + +#define MICROPY_VFS (1) +#define MICROPY_PY_UOS_VFS (1) + +#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) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) +#define MICROPY_PY_BUILTINS_RANGE_BINOP (1) +#define MICROPY_PY_BUILTINS_HELP (1) +#define MICROPY_PY_BUILTINS_HELP_MODULES (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_MATH_FACTORIAL (1) +#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) +#define MICROPY_PY_URE_SUB (1) +#define MICROPY_VFS_POSIX (1) +#define MICROPY_PY_FRAMEBUF (1) +#define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#define MICROPY_PY_UCRYPTOLIB_CTR (1) +#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 +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj diff --git a/ports/wapy-unix/variants/coverage/mpconfigvariant.mk b/ports/wapy-unix/variants/coverage/mpconfigvariant.mk new file mode 100644 index 000000000..ddb5027a9 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/mpconfigvariant.mk @@ -0,0 +1,20 @@ +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 + +FROZEN_MANIFEST = manifest_coverage.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/wapy-unix/variants/dev/manifest.py b/ports/wapy-unix/variants/dev/manifest.py new file mode 100644 index 000000000..92a681116 --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/dev/mpconfigvariant.h b/ports/wapy-unix/variants/dev/mpconfigvariant.h new file mode 100644 index 000000000..eb6513471 --- /dev/null +++ b/ports/wapy-unix/variants/dev/mpconfigvariant.h @@ -0,0 +1,32 @@ +/* + * 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_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) +#define MICROPY_ENABLE_SCHEDULER (1) + +#define MICROPY_PY_SYS_SETTRACE (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) diff --git a/ports/wapy-unix/variants/dev/mpconfigvariant.mk b/ports/wapy-unix/variants/dev/mpconfigvariant.mk new file mode 100644 index 000000000..bbd30b623 --- /dev/null +++ b/ports/wapy-unix/variants/dev/mpconfigvariant.mk @@ -0,0 +1,5 @@ +PROG ?= micropython-dev + +MICROPY_ROM_TEXT_COMPRESSION = 1 + +MICROPY_PY_BLUETOOTH = 1 diff --git a/ports/wapy-unix/variants/fast/mpconfigvariant.h b/ports/wapy-unix/variants/fast/mpconfigvariant.h new file mode 100644 index 000000000..6d73275fd --- /dev/null +++ b/ports/wapy-unix/variants/fast/mpconfigvariant.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +// This config file is intended to configure artificially fast uPy build for +// synthetic benchmarking, at the expense of features supported and memory +// usage. This config is not intended to be used in production. + +#define MICROPY_PY___FILE__ (0) +// 91 is a magic number proposed by @dpgeorge, which make pystone run ~ at tie +// with CPython 3.4. +#define MICROPY_MODULE_DICT_SIZE (91) + +#include "variants/DEV/mpconfigvariant.h" diff --git a/ports/wapy-unix/variants/fast/mpconfigvariant.mk b/ports/wapy-unix/variants/fast/mpconfigvariant.mk new file mode 100644 index 000000000..d67f7c8f3 --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/freedos/mpconfigvariant.h b/ports/wapy-unix/variants/freedos/mpconfigvariant.h new file mode 100644 index 000000000..338b34492 --- /dev/null +++ b/ports/wapy-unix/variants/freedos/mpconfigvariant.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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. + */ + +// options to control how MicroPython is built + +#define MICROPY_PY_USELECT_POSIX (0) + +#define MICROPY_STREAMS_NON_BLOCK (0) + +#define MICROPY_PY_SYS_PLATFORM "freedos" + +// djgpp dirent struct does not have d_ino field +#undef _DIRENT_HAVE_D_INO + +#define MICROPY_USE_INTERNAL_ERRNO (1) + +#include "variants/DEV/mpconfigvariant.h" diff --git a/ports/wapy-unix/variants/freedos/mpconfigvariant.mk b/ports/wapy-unix/variants/freedos/mpconfigvariant.mk new file mode 100644 index 000000000..a30db3e0c --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/manifest.py b/ports/wapy-unix/variants/manifest.py new file mode 100644 index 000000000..666b4c0ab --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/minimal/mpconfigvariant.h b/ports/wapy-unix/variants/minimal/mpconfigvariant.h new file mode 100644 index 000000000..e87b5d8ec --- /dev/null +++ b/ports/wapy-unix/variants/minimal/mpconfigvariant.h @@ -0,0 +1,142 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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. + */ + +// 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) +#define MICROPY_ALLOC_PARSE_RESULT_INIT (8) +#define MICROPY_ALLOC_PARSE_RESULT_INC (8) +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) +#define MICROPY_ALLOC_PATH_MAX (PATH_MAX) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GC_ALLOC_THRESHOLD (0) +#define MICROPY_ENABLE_FINALISER (0) +#define MICROPY_STACK_CHECK (0) +#define MICROPY_COMP_CONST (0) +#define MICROPY_MEM_STATS (0) +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_READER_POSIX (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_HELPER_LEXER_UNIX (1) +#define MICROPY_ENABLE_SOURCE_LINE (0) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) +#define MICROPY_WARNINGS (0) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0) +#define MICROPY_KBD_EXCEPTION (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_NONE) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE) +#define MICROPY_STREAMS_NON_BLOCK (0) +#define MICROPY_OPT_COMPUTED_GOTO (0) +#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (0) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CPYTHON_COMPAT (0) +#define MICROPY_PY_BUILTINS_BYTEARRAY (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) +#define MICROPY_PY_BUILTINS_COMPILE (0) +#define MICROPY_PY_BUILTINS_ENUMERATE (0) +#define MICROPY_PY_BUILTINS_FILTER (0) +#define MICROPY_PY_BUILTINS_FROZENSET (0) +#define MICROPY_PY_BUILTINS_REVERSED (0) +#define MICROPY_PY_BUILTINS_SET (0) +#define MICROPY_PY_BUILTINS_SLICE (0) +#define MICROPY_PY_BUILTINS_STR_COUNT (0) +#define MICROPY_PY_BUILTINS_STR_OP_MODULO (0) +#define MICROPY_PY_BUILTINS_STR_UNICODE (0) +#define MICROPY_PY_BUILTINS_PROPERTY (0) +#define MICROPY_PY_BUILTINS_MIN_MAX (0) +#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (0) +#define MICROPY_PY_GC (0) +#define MICROPY_PY_GC_COLLECT_RETVAL (0) +#define MICROPY_PY_ARRAY (0) +#define MICROPY_PY_COLLECTIONS (0) +#define MICROPY_PY_MATH (0) +#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_IO (0) +#define MICROPY_PY_IO_FILEIO (0) +#define MICROPY_PY_STRUCT (0) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_EXIT (0) +#define MICROPY_PY_SYS_PLATFORM "linux" +#define MICROPY_PY_SYS_MAXSIZE (0) +#define MICROPY_PY_SYS_STDFILES (0) +#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_UCTYPES (0) +#define MICROPY_PY_UTIME (0) +#define MICROPY_PY_UZLIB (0) +#define MICROPY_PY_UJSON (0) +#define MICROPY_PY_URE (0) +#define MICROPY_PY_UHEAPQ (0) +#define MICROPY_PY_UHASHLIB (0) +#define MICROPY_PY_UBINASCII (0) + +extern const struct _mp_obj_module_t mp_module_os; + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, \ + +#define MICROPY_PORT_ROOT_POINTERS \ + +////////////////////////////////////////// +// Do not change anything beyond this line +////////////////////////////////////////// + +#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 + +#ifdef __LP64__ +typedef long mp_int_t; // must be pointer size +typedef unsigned long mp_uint_t; // must be pointer size +#else +// These are definitions for machines where sizeof(int) == sizeof(void*), +// regardless for actual size. +typedef int mp_int_t; // must be pointer size +typedef unsigned int mp_uint_t; // must be pointer size +#endif + +// Cannot include , as it may lead to symbol name clashes +#if _FILE_OFFSET_BITS == 64 && !defined(__LP64__) +typedef long long mp_off_t; +#else +typedef long mp_off_t; +#endif + +// We need to provide a declaration/definition of alloca() +#ifdef __FreeBSD__ +#include +#else +#include +#endif diff --git a/ports/wapy-unix/variants/minimal/mpconfigvariant.mk b/ports/wapy-unix/variants/minimal/mpconfigvariant.mk new file mode 100644 index 000000000..ec3b21c0b --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/nanbox/mpconfigvariant.h b/ports/wapy-unix/variants/nanbox/mpconfigvariant.h new file mode 100644 index 000000000..f827158fb --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/nanbox/mpconfigvariant.mk b/ports/wapy-unix/variants/nanbox/mpconfigvariant.mk new file mode 100644 index 000000000..9752b922c --- /dev/null +++ b/ports/wapy-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/wapy-unix/variants/standard/mpconfigvariant.h b/ports/wapy-unix/variants/standard/mpconfigvariant.h new file mode 100644 index 000000000..79b8fe2a3 --- /dev/null +++ b/ports/wapy-unix/variants/standard/mpconfigvariant.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + diff --git a/ports/wapy-unix/variants/standard/mpconfigvariant.mk b/ports/wapy-unix/variants/standard/mpconfigvariant.mk new file mode 100644 index 000000000..cf3efab8a --- /dev/null +++ b/ports/wapy-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/ports/wapy-wasi/Makefile b/ports/wapy-wasi/Makefile new file mode 100644 index 000000000..29eac8e77 --- /dev/null +++ b/ports/wapy-wasi/Makefile @@ -0,0 +1,186 @@ +#PORTS=$(EM_CACHE)/asmjs/ports-builds +#FROZEN_MPY_DIR ?= modules +#FROZEN_DIR ?= flash + + +BASENAME=wapy +PROG=$(BASENAME).html +LIBPYTHON = lib$(BASENAME).a +CROSS = 0 + +# clang has slightly different options to GCC +CLANG = 1 + +# compiler settings +ifdef CWARN +else +CFLAGS += $(CWARN) -Wall -Werror +endif + +CFLAGS += -Wpointer-arith -Wuninitialized -ferror-limit=2 + + +include ../../py/mkenv.mk + +#EM_CACHE ?= $(HOME)/.emscripten_cache + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h + +CPPFLAGS += -D__WASM__=1 -DNO_NLR=1 -Wno-unused-variable +CC=clang -D__WASM__=1 -DNO_NLR=1 +CPP=clang -E -D__CPP__ -D__WASM__=1 -DNO_NLR=1 +CPPFLAGS += $(INC) $(WARN) -ansi -std=gnu11 + + + +# include py core make definitions +include ../../py/py.mk + + +SIZE = echo + +INC += -I. +INC += -I../.. +INC += -I$(BUILD) + + + +#LD_SHARED = -Wl,-map,$@.map -Wl,-dead_strip -Wl,-no_pie +LD_SHARED += -fno-exceptions -fno-rtti + + + +ifneq ($(FROZEN_DIR),) +# To use frozen source modules, put your .py files in a subdirectory (eg scripts/) +# and then invoke make with FROZEN_DIR=scripts (be sure to build from scratch). +CPPFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +ifneq ($(FROZEN_MPY_DIR),) +# To use frozen bytecode, put your .py files in a subdirectory (eg frozen/) and +# then invoke make with FROZEN_MPY_DIR=frozen (be sure to build from scratch). +# //for qstr.c + +CPPFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CPPFLAGS += -DMICROPY_MODULE_FROZEN_MPY +endif + +# //for build/genhdr/qstr.i.last +# QSTR_GEN_EXTRA_CFLAGS=-DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + +MPY_CROSS_FLAGS += -mcache-lookup-bc + +include ../wapy/wapy.mk + + +SRC_C = \ + ../wapy/core/vfs.c \ + ../wapy/core/ringbuf_b.c \ + ../wapy/core/ringbuf_o.c \ + ../wapy/core/file.wapy.c \ + mod/modos.c \ + mod/modtime.c \ + mod/moduos_vfs.c \ + +ifdef FFI +# optionnal experimental FFI +SRC_C+= \ + mod/modffi.c \ + mod/ffi/ffi.c \ + mod/ffi/types.c \ + mod/ffi/prep_cif.c +endif + +LIB_SRC_C= $(addprefix lib/, \ + utils/stdout_helpers.c \ + utils/pyexec.c \ + utils/interrupt_char.c \ + mp-readline/readline.c \ + ) + +# now using user mod dir, SRC_MOD use USER_C_MODULES + +ifdef LVGL + LIB_SRC_C_EXTRA += $(addprefix lib/,\ + lv_bindings/driver/SDL/SDL_monitor.c \ + lv_bindings/driver/SDL/SDL_mouse.c \ + lv_bindings/driver/SDL/modSDL.c \ + timeutils/timeutils.c \ + ) + CPPFLAGS += -Wno-unused-function -Wno-for-loop-analysis +endif + + + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) qstr/objtype.c qstr/modbuiltins.c +SRC_QSTR += $(LIB_SRC_C) + + +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += SRC_QSTR + +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C_EXTRA:.c=.o)) + + +CPPFLAGS += -DNO_NLR=$(NO_NLR) +CFLAGS += $(CPPFLAGS) $(CFLAGS_EXTRA) + + +all: $(PROG) + +include ../../py/mkrules.mk + + +# one day, maybe go via a *embeddable* static lib first ? +LIBPYTHON = lib$(BASENAME)$(TARGET).a + +#force preprocessor env to be created before qstr extraction +ifdef EMSCRIPTEN +$(BUILD)/clang_predefs.h: + $(Q)mkdir -p $(dir $@) + $(Q)emcc $(CFLAGS) $(JSFLAGS) -E -x c /dev/null -dM > $@ + +# Create `clang_predefs.h` as soon as possible, using a Makefile trick + +Makefile: $(BUILD)/clang_predefs.h +endif + + + +clean: + $(RM) -rf $(BUILD) $(CLEAN_EXTRA) $(LIBPYTHON) + $(shell rm $(BASENAME)/$(BASENAME).* || echo echo test data cleaned up) + + +COPT += $(JSFLAGS) $(WASM_FLAGS) + + +lib-static: $(OBJ) + $(ECHO) "Linking static $(LIBPYTHON)" + $(Q)$(AR) rcs $(LIBPYTHON) $(OBJ) + +lib-shared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_SHARED) $(LD_LIB) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) -ldl -lc + +libs: lib-static lib-shared + +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +LD_PROG += $(CFLAGS) $(WASM_FLAGS) +LD_LIB += $(WASM_FLAGS) +CFLAGS += -fPIC + +$(PROG): lib-static + $(ECHO) "Building static executable $@" + $(Q)$(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c lib$(BASENAME)$(TARGET).a -ldl -lm $(BLOBS) $(PF) + $(shell mv $(BASENAME).* $(BASENAME)/) + diff --git a/ports/wapy-wasi/api b/ports/wapy-wasi/api new file mode 120000 index 000000000..caac86790 --- /dev/null +++ b/ports/wapy-wasi/api @@ -0,0 +1 @@ +../wapy-wasm/api \ No newline at end of file diff --git a/ports/wapy-wasi/bc_as_integers.h b/ports/wapy-wasi/bc_as_integers.h new file mode 120000 index 000000000..f1ab533d9 --- /dev/null +++ b/ports/wapy-wasi/bc_as_integers.h @@ -0,0 +1 @@ +../wapy-wasm/bc_as_integers.h \ No newline at end of file diff --git a/ports/wapy-wasi/dev_conf.h b/ports/wapy-wasi/dev_conf.h new file mode 120000 index 000000000..11f5bcb51 --- /dev/null +++ b/ports/wapy-wasi/dev_conf.h @@ -0,0 +1 @@ +../wapy-wasm/dev_conf.h \ No newline at end of file diff --git a/ports/wapy-wasi/main.c b/ports/wapy-wasi/main.c new file mode 100644 index 000000000..f51c5edef --- /dev/null +++ b/ports/wapy-wasi/main.c @@ -0,0 +1,512 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../wapy/core/fdfile.h" + +#include "py/compile.h" +#include "py/emitglue.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/parse.h" +#include "py/bc0.h" +// overwrite the "math" in bytecode value with plain integers +// #include "bc_as_integers.h" +#include "py/bc.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "lib/utils/pyexec.h" + + +// for mp_call_function_0 +#include "py/parsenum.h" +#include "py/compile.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objmodule.h" // <= function defined in +#include "py/objgenerator.h" +#include "py/smallint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stackctrl.h" +#include "py/gc.h" + +#define __MAIN__ (1) +#include "emscripten.h" +#undef __MAIN__ + +#define DBG 0 +#define DLOPEN 0 + +#if !MICROPY_ENABLE_PYSTACK +#error "need MICROPY_ENABLE_PYSTACK (1) +#endif + +static int SHOW_OS_LOOP=0; + +static int g_argc; +static char **g_argv; //[]; + +size_t +bsd_strlen(const char *str) { + const char *s; + for (s = str; *s; ++s); + return (s - str); +} + + +int +endswith(const char * str, const char * suffix) { + int str_len = strlen(str); + int suffix_len = strlen(suffix); + + return + (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix)); +} + +#include "api/wasm_file_api.c" +#include "api/wasm_import_api.c" + +static int KPANIC = 1; + + +extern char* shm_ptr(); + +char ** +copy_argv(int argc, char *argv[]) { + // calculate the contiguous argv buffer size + int length=0; + + size_t ptr_args = argc + 1; + for (int i = 0; i < argc; i++) { + length += (bsd_strlen(argv[i]) + 1); + } + char** new_argv = (char**)malloc((ptr_args) * sizeof(char*) + length); + // copy argv into the contiguous buffer + length = 0; + for (int i = 0; i < argc; i++) { + new_argv[i] = &(((char*)new_argv)[(ptr_args * sizeof(char*)) + length]); + strcpy(new_argv[i], argv[i]); + fprintf(stderr,"argv[%d] = %s\n", i, argv[i]); + length += (bsd_strlen(argv[i]) + 1); + } + // insert NULL terminating ptr at the end of the ptr array + new_argv[ptr_args-1] = NULL; + return (new_argv); +} + + + +#include "upython.c" + + +#include "vmsl/vmreg.h" + +#include "vmsl/vmreg.c" + +extern mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +extern mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); +extern mp_obj_t gen_instance_iternext(mp_obj_t self_in); +extern int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); + +//extern mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +//extern mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); + + +#include + +#if MICROPY_PY_SYS_SETTRACE +#error "248:duplicate symbol: mp_type_code" +/* +wasm-ld: error: duplicate symbol: mp_type_code +>>> defined in libmicropython.a(profile.o) +>>> defined in libmicropython.a(builtinevex.o) +*/ +#include "py/profile.h" +#endif + +//extern +int pyexec_friendly_repl_process_char(int c); +int pyexec_repl_repl_restart(int ret); +int handle_uncaught_exception(void); + +// entry point for implementing core vm parts in python, set via "embed" module +extern void pyv(mp_obj_t value); +extern mp_obj_t pycore(const char *fn); + +// entry point for line tracing +#if 0 //__WASM__ + #pragma message "no embed" + qstr trace_prev_file = 1/*MP_QSTR_*/; + qstr trace_file = 1/*MP_QSTR_*/; + + size_t trace_prev_line; + size_t trace_line; + + int trace_on; +#else +extern qstr trace_prev_file ; +extern size_t trace_prev_line; + +extern qstr trace_file ; +extern size_t trace_line; + +extern int trace_on; +#endif + +#define io_stdin i_main.shm_stdio + +FILE *cc; +FILE *kv; +FILE *sc; + +// to use kb buffer space for scripts +// if (strlen(io_stdin)>=IO_KBD){ io_stdin[IO_KBD] = 0; +#define IO_CODE_DONE { io_stdin[0] = 0; } + +#if MICROPY_PY_THREAD_GIL && MICROPY_PY_THREAD_GIL_VM_DIVISOR +// This needs to be volatile and outside the VM loop so it persists across handling +// of any exceptions. Otherwise it's possible that the VM never gives up the GIL. +volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; +#endif + + + +// wasi support + +//static +struct timespec t_timespec; +//static +struct timeval t_timeval; + +//static +struct timeval wa_tv; +//static +unsigned int wa_ts_nsec; + +/* +#include +#include + +static struct timeval wa_tv; +static unsigned int wa_ts_nsec; + +int wa_clock_gettime(clockid_t clockid, struct timespec *ts) { + sched_yield(); + ts->tv_sec = wa_tv.tv_sec; + ts->tv_nsec = wa_ts_nsec; + fprintf(sc,"[\"clock_gettime\", %d, %d, %lu]\n", (int)clockid, (int)&ts, ts->tv_nsec); + return 0; +} + + +int wa_gettimeofday(struct timeval *tv, struct timezone *tz) { + + sched_yield(); + memcpy( &tv, &wa_tv, sizeof(tv)); + + //tv->tv_sec = 9223372036854775807 ;// + + fprintf(sc,"[\"gettimeofday\",%d,%d, %lld, %lld ]\n", (int)&tv, (int)&tz, tv->tv_sec, tv->tv_usec); + return 0; +} +*/ + +void wa_setenv(const char *key, int value) { + fprintf(kv,"{\"%s\":%d}\n", key, value); +} + +void wa_syscall(const char *code) { + fprintf(sc," %s\n", code); + fflush(sc); +} + +void __wa_env(const char *key, unsigned int sign, unsigned int bitwidth, void* addr ) { + const char *csign[2] = { "i", "u" }; + fprintf(kv,"{\"%s\": [\"%s%u\", %u]}\n", key, csign[sign], bitwidth, (unsigned int)addr); +} + +#define wa_env(key,x) __wa_env(key, ( x >= 0 && ~x >= 0 ), sizeof(x)*8 , &x ) + +void Py_Init() { + cc = fdopen(3, "r+"); + kv = fdopen(4, "r+"); + sc = fdopen(5, "r+"); + +// do not call cpython names directly + wPy_Initialize(); + wPy_NewInterpreter(); + + wa_setenv("shm", (int)shm_ptr()); + +// js<->wa adapters tests +/* + { + unsigned char uc; // beh + wa_env("uc", uc ); // beh + + unsigned int ui; + wa_env("ui", ui ); + + unsigned long long ul; + wa_env("ul", ul ); + } + + { + signed char sc; + wa_env("sc", sc ); + + signed int si; + wa_env("si", si ); + + signed long long sl; + wa_env("sl", sl ); + } +*/ + + wa_env("tsec", wa_tv.tv_sec); + wa_env("tusec", wa_tv.tv_usec ); + + wa_env("tnsec", wa_ts_nsec); + + + + + + wa_setenv("PyRun_SimpleString_MAXSIZE", IO_KBD); + wa_setenv("io_port_kbd", (int)&io_stdin[IO_KBD]); + wa_setenv("MP_IO_SIZE", MP_IO_SIZE); + wa_syscall("window.setTimeout( init_repl_begin , 1000 )"); + + IO_CODE_DONE; + +} + +static bool def_PyRun_SimpleString_is_repl = false ; +static int async_loop = 1; +static int async_state; + + +extern int emscripten_GetProcAddress(const char * name); + + +#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8_t *idx_cache) { + size_t idx = *idx_cache; + mp_obj_t key = MP_OBJ_NEW_QSTR(qst); + mp_map_elem_t *elem = NULL; + if (idx < map->alloc && map->table[idx].key == key) { + elem = &map->table[idx]; + } else { + elem = mp_map_lookup(map, key, MP_MAP_LOOKUP); + if (elem != NULL) { + *idx_cache = (elem - &map->table[0]) & 0xff; + } + } + return elem; +} +#endif + + +STATIC void stderr_print_strn2(void *env, const char *str, size_t len) { + (void)env; + mp_hal_stdout_tx_strn(str,len); +} + +const mp_print_t mp_stderr_print2 = {NULL, stderr_print_strn2}; + +int uncaught_exception_handler(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + #if __EMSCRIPTEN__ + EM_ASM({console.log("91:SystemExit");}); + #endif + + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + cdbg("mp_stderr_print2=%p",exc) + cdbg("mp_obj=%p", MP_OBJ_FROM_PTR(exc) ); + mp_obj_print_exception(&mp_stderr_print2, MP_OBJ_FROM_PTR(exc)); + return 0; +} + +void +dump_args2(const mp_obj_t *a, size_t sz) { + fprintf(stderr,"%p: ", a); + for (size_t i = 0; i < sz; i++) { + fprintf(stderr,"%p ", a[i]); + } + fprintf(stderr,"\n"); +} + + +// this is reserved to max speed asynchronous code + +void +noint_aio_fsync() { + + if (!io_stdin[0]) + return; + + if (!endswith(io_stdin, "#aio.step\n")) + return; + + int ex=-1; + async_state = VMFLAGS_IF; + // CLI + VMFLAGS_IF = 0; + + //TODO: maybe somehow consumme kbd data for async inputs ? + //expect script to be properly async programmed and run them full speed via C stack ? + + if (async_loop) { + + if ( (async_loop = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT)) ) { + ex=0; + } else { + fprintf(stdout, "ERROR[%s]\n", io_stdin); + // ex check + ex=1; + } + + } + +// TODO: here we may able to tranform toplevel sync code to async and re eval +// WARNING: it may have side effects because could have run until async exception is caught + if (ex>=0) { + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("646: uncaught exception") + //mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + if (uncaught_exception_handler()) { + clog("651:SystemExit"); + } else { + clog("653: exception done"); + } + async_loop = 0; + } + } + IO_CODE_DONE; + // STI + VMFLAGS_IF = async_state; +} + + + +size_t +has_io() { + size_t check = strlen(io_stdin); + if (io_stdin[0] && (check != 38)) + return check; + return 0; +} + + + + +//void +//main_loop_or_step(void) { + + + +//*************************************************************************************** + + + +int PyArg_ParseTuple(PyObject *argv, const char *fmt, ...) { + va_list argptr; + va_start (argptr, fmt ); + vfprintf(stdout,fmt,argptr); + va_end (argptr); + return 0; +} + + + +static int loops = 0; + +//unsigned long long TV_NS = 16000; + +int +main(int argc, char *argv[]) { + //TV_NS += 16000; + + if (KPANIC!=0) { + + if (KPANIC>0) { + + g_argc = argc; + g_argv = copy_argv(argc, argv); + KPANIC = 0; + // init + crash_point = &&VM_stackmess; + + fprintf(stderr, "argv[0..%d] env=%s memory=%p\n", argc, getenv("WAPY"), shm_ptr() ); + + if (argc) { + printf("\nnode.js detected, running repl in infinite loop ...\n"); + // do not multiplex output + cc = stdout; + } + + #include "vmsl/vmwarmup.c" + + if (!argc) { + // WASI syscall + return 0; + } + + } else { + printf("\nno guru meditation [%d,%d]", getchar(), fgetc(cc) ); + // FIXME: exitcode + return 1; + } + + } + +while (!KPANIC) { + + #include "../wapy/vmsl/vm_loop.c" + +// node.js + if(argc) { + fflush(stdout); + fgets( io_stdin, MP_IO_SHM_SIZE, stdin ); + continue; + } + +// wasi + break; +} + + return 0; +} // main_loop_or_step + + + + +// + + diff --git a/ports/wapy-wasi/mod b/ports/wapy-wasi/mod new file mode 120000 index 000000000..058d8128b --- /dev/null +++ b/ports/wapy-wasi/mod @@ -0,0 +1 @@ +../wapy-wasm/mod \ No newline at end of file diff --git a/ports/wapy-wasi/mpconfigport.h b/ports/wapy-wasi/mpconfigport.h new file mode 100644 index 000000000..fcd2fd2cf --- /dev/null +++ b/ports/wapy-wasi/mpconfigport.h @@ -0,0 +1,487 @@ +#define MODULES_LINKS + +#include "dev_conf.h" + +#if __DEV__ +#define MICROPY_VM_HOOK_BC 1 + +#ifdef STATIC + #undef STATIC +#endif + +#define STATIC // mpconfig.h:1402 => bad pointer cast and bad linking +#endif + + +#define MICROPY_ROM_TEXT_COMPRESSION (0) +#define MICROPY_PY_FSTRING (1) + +#undef NO_NLR +#define NO_NLR (1) +#define WAPY (1) + + +#define False false +#define True true + + +#define mp_hal_ticks_cpu() 0 + +#ifdef MICROPY_OBJ_IMMEDIATE_OBJS +//https://github.com/pfalcon/pycopy/commit/2a362af5bcdeaadd94ddac7f90ea33092a7ac7be +#undef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (0) +#endif + +// options to control how Micro Python is built +#define MICROPY_QSTR_BYTES_IN_HASH (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +//just in case of long urls and a local cache ? +#define MICROPY_ALLOC_PATH_MAX (1024) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) + +#if 0 + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) +#else + //#pragma message "build/frozen_mpy.c:7:2: error: 'incompatible MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE'" + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (0) + +#define MICROPY_MODULE_GETATTR (1) +#define MICROPY_MODULE_SPECIAL_METHODS (1) + + +#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool + +#define MICROPY_PY_BUILTINS_FROZENSET (1) + +#define MICROPY_LONGINT_IMPL_MPZ (2) +#if 1 + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#else + #pragma message "build/frozen_mpy.c:12:2: error: 'incompatible MICROPY_LONGINT_IMPL'" + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) +#endif + +#define MPZ_DIG_SIZE (16) + +#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_SYS_EXC_INFO (0) // <============================ NON STANDARD PY2 + +#define MICROPY_PERSISTENT_CODE (1) +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_PERSISTENT_CODE_SAVE (1) +//#define MICROPY_EMIT_MACHINE_CODE (1) + + +#define MICROPY_HAS_FILE_READER (1) +#define MICROPY_HELPER_LEXER_UNIX (1) + +#define MICROPY_READER_POSIX (0) +#define MICROPY_VFS_POSIX (0) + +#define HAVE_mp_raw_code_save_file (1) + +#define MICROPY_EMIT_WASM (1) + +//MICROPY_EMIT_INLINE_ASM ((MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) +//#define MICROPY_EMIT_NATIVE + +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) + + +#if 1 +// 0 0 0 0 ! RuntimeError: memory access out of bounds +// 0 0 0 1 ! silent crash +// 0 0 1 0 = 11.15 Kpy/s + #define MICROPY_STACKLESS (0) + #define MICROPY_STACKLESS_STRICT (0) + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#else +// 1 0 1 1 = 10.6 Kpy/s +// 1 0 1 0 = 10.8 K +// 1 1 1 0 = 10.6 +// 1 1 1 1 = 10.6 + #define MICROPY_STACKLESS (1) + #define MICROPY_STACKLESS_STRICT (1) // <=============== 1! or too much collect + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#endif + +// nlr.h MICROPY_NLR_* must match a supported arch +// define or autodetect will fail to select WASM +#if __WASM__ +#else +#define MICROPY_NLR_SETJMP (1) +#endif +#define MICROPY_DYNAMIC_COMPILER (0) + +#if MICROPY_NLR_SETJMP + // can't have MICROPY_NLR_SETJMP==MICROPY_DYNAMIC_COMPILER==0 + #define MICROPY_NLR_OS_WINDOWS (0) + #define MICROPY_NLR_X86 (0) + #define MICROPY_NLR_X64 (0) +#endif + +// mpconfig +#define MICROPY_PY_GENERATOR_PEND_THROW (1) // def 1 +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) // def 0 + + +// MEMORY +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GCREGS_SETJMP (1) +#define MICROPY_GC_ALLOC_THRESHOLD (1) + + +//like ESP/UNIX +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) + + +#if MICROPY_PY_LVGL +#define MICROPY_MEM_STATS (0) +#else +#define MICROPY_MEM_STATS (1) +#endif + +#if MICROPY_MEM_STATS +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#endif + + +#define MICROPY_KBD_EXCEPTION (1) + + +//?? +#define MICROPY_USE_INTERNAL_ERRNO (0) + +#define MICROPY_USE_READLINE (0) +#define MICROPY_PY_BUILTINS_INPUT (1) + + +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#define MICROPY_PY_MATH_FACTORIAL (1) + + +#define MICROPY_COMP_MODULE_CONST (1) + +#if 0 + #define MICROPY_COMP_CONST (0) + #define MICROPY_PY_SYS_SETTRACE (1) +#else + #define MICROPY_COMP_CONST (1) + #define MICROPY_PY_SYS_SETTRACE (0) +#endif +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) + +#define MICROPY_ENABLE_SOURCE_LINE (1) + +#define MICROPY_HELPER_REPL (1) + +#define MICROPY_PY___FILE__ (1) + +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) + +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ASYNC_AWAIT (1) +#define MICROPY_PY_ATTRTUPLE (1) + +#define MICROPY_PY_BTREE (0) + +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_EXECFILE (1) // <============================ NON STANDARD PY2 +#define MICROPY_PY_BUILTINS_FILTER (1) +//#define MICROPY_PY_BUILTINS_FLOAT (1) because MICROPY_FLOAT_IMPL_DOUBLE +#define MICROPY_PY_BUILTINS_HELP_MODULES (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) // <=============== next2 make next() compat with cpython +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + //#error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) + +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_CMATH (1) + +//? TEST THAT THING ! +#if __WASM__ + #define MICROPY_PY_FFI (0) +#else +#define MICROPY_PY_FFI (1) // <========================== NON STANDARD NOT CPY +#endif + +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_MODULES (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_PLATFORM "wasm" + +// IO is cooked and multiplexed via mp_hal_stdout_tx_strn in file.c +// so do not modify those +// https://github.com/python/cpython/blob/v3.7.3/Python/bltinmodule.c#L1849-L1932 + + +#define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_PY_IO (1) // <=============== only 1 for wasm port +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_IO_BYTESIO (1) +#define MICROPY_PY_IO_BUFFEREDWRITER (1) + +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_SYS_STDFILES (1) +#define MICROPY_PY_OS_DUPTERM (1) // <==== or js console will get C stdout too ( C-OUT [spam] ) + + +#define MICROPY_PY_THREAD (0) +#define MICROPY_PY_THREAD_GIL (0) +#define MICROPY_PY_TIME (0) + +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UERRNO_ERRORCODE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UHASHLIB (1) + +//F +#define MICROPY_PY_UHASHLIB_SHA1 (0) + +#define MICROPY_PY_UHASHLIB_SHA256 (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_UOS (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_SUB (1) + + +//TODO: need #define MICROPY_EVENT_POLL_HOOK + select_select on io demultiplexer. +//F +#define MICROPY_PY_USELECT (0) + + +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UZLIB (1) + +#define MICROPY_VFS (0) + +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_MPY_CODE_SAVE (1) +#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_ENABLE_DOC_STRING (1) + + +//asyncio implemented +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_SCHEDULER_DEPTH (4) + + +// type definitions for the specific machine + +#define BYTES_PER_WORD (4) + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void*)((mp_uint_t)(p) | 1)) + +// This port is intended to be 32-bit, but unfortunately, int32_t for +// different targets may be defined in different ways - either as int +// or as long. This requires different printf formatting specifiers +// to print such value. So, we avoid int32_t and use int directly. +#define UINT_FMT "%u" +#define INT_FMT "%d" +typedef int mp_int_t; // must be pointer size +typedef unsigned mp_uint_t; // must be pointer size + +typedef long mp_off_t; + +#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +//#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn(str, len) + + +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_io; +extern const struct _mp_obj_module_t mp_module_os; + +#if MICROPY_PY_UOS_VFS + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), (mp_obj_t)&mp_module_os }, +#endif + +#if MICROPY_PY_FFI + extern const struct _mp_obj_module_t mp_module_ffi; + #define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else + #define MICROPY_PY_FFI_DEF +#endif + +#if MICROPY_PY_JNI + #define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else + #define MICROPY_PY_JNI_DEF +#endif + +#if MICROPY_PY_TERMIOS + #define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else + #define MICROPY_PY_TERMIOS_DEF +#endif + +#if MICROPY_PY_SOCKET + #define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else + #define MICROPY_PY_SOCKET_DEF +#endif + +#if MICROPY_PY_USELECT_POSIX + #define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else + #define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PY_MACHINE_DEF + + +#if MICROPY_PY_LVGL + extern const struct _mp_obj_module_t mp_module_utime; + extern const struct _mp_obj_module_t mp_module_lvgl; + extern const struct _mp_obj_module_t mp_module_lvindev; + extern const struct _mp_obj_module_t mp_module_SDL; + + #include "lib/lv_bindings/lvgl/src/lv_misc/lv_gc.h" + + #define MICROPY_PY_LVGL_DEF \ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvgl), (mp_obj_t)&mp_module_lvgl },\ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvindev), (mp_obj_t)&mp_module_lvindev},\ + { MP_OBJ_NEW_QSTR(MP_QSTR_SDL), (mp_obj_t)&mp_module_SDL }, + + #define MPR_void_mp_lv_user_data void *mp_lv_user_data; + #define MPR_LVGL LV_GC_ROOTS(LV_NO_PREFIX) +#else + #define MICROPY_PY_LVGL_DEF + #define MPR_void_mp_lv_user_data + #define MPR_LVGL +#endif + + +// { MP _ ROM_QSTR(MP_QSTR_umachine), MP _ ROM_PTR(&mp_module_machine) }, + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_SOCKET_DEF \ + MICROPY_PY_MACHINE_DEF \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + MICROPY_PY_LVGL_DEF \ + MODULES_LINKS + + + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, \ + + + +#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_binascii), (mp_obj_t)&mp_module_ubinascii }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_errno), (mp_obj_t)&mp_module_uerrno }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_hashlib), (mp_obj_t)&mp_module_uhashlib }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_heapq), (mp_obj_t)&mp_module_uheapq }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_json), (mp_obj_t)&mp_module_ujson }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&mp_module_os }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&mp_module_urandom }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_re), (mp_obj_t)&mp_module_ure }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_select), (mp_obj_t)&mp_module_uselect }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&mp_module_usocket }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_ssl), (mp_obj_t)&mp_module_ussl }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_struct), (mp_obj_t)&mp_module_ustruct }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_zlib), (mp_obj_t)&mp_module_uzlib }, \ + + + +// We need to provide a declaration/definition of alloca() +#include + +#define MICROPY_HW_BOARD_NAME "emscripten" +#define MICROPY_HW_MCU_NAME "wasm" + +#ifdef __linux__ +#define MICROPY_MIN_USE_STDOUT (1) +#endif + +#ifdef __thumb__ +#define MICROPY_MIN_USE_CORTEX_CPU (1) +#define MICROPY_MIN_USE_STM32_MCU (1) +#endif + +#define MP_STATE_PORT MP_STATE_VM + +#define MPR_const_char_readline_hist const char *readline_hist[16]; + +#define MICROPY_PORT_ROOT_POINTERS \ + MPR_LVGL \ + MPR_void_mp_lv_user_data \ + MPR_const_char_readline_hist \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + + +#define FFCONF_H "lib/oofatfs/ffconf.h" + + + + + + + + +// diff --git a/ports/wapy-wasi/mphalport.h b/ports/wapy-wasi/mphalport.h new file mode 120000 index 000000000..f4e81f423 --- /dev/null +++ b/ports/wapy-wasi/mphalport.h @@ -0,0 +1 @@ +../wapy-wasm/mphalport.h \ No newline at end of file diff --git a/ports/wapy-wasi/qstr b/ports/wapy-wasi/qstr new file mode 120000 index 000000000..ddbda9e5d --- /dev/null +++ b/ports/wapy-wasi/qstr @@ -0,0 +1 @@ +../wapy-wasm/qstr \ No newline at end of file diff --git a/ports/wapy-wasi/qstrdefsport.h b/ports/wapy-wasi/qstrdefsport.h new file mode 120000 index 000000000..6cb761a72 --- /dev/null +++ b/ports/wapy-wasi/qstrdefsport.h @@ -0,0 +1 @@ +../wapy/qstrdefsport.h \ No newline at end of file diff --git a/ports/wapy-wasi/vmsl b/ports/wapy-wasi/vmsl new file mode 120000 index 000000000..7b656927b --- /dev/null +++ b/ports/wapy-wasi/vmsl @@ -0,0 +1 @@ +../wapy-wasm/vmsl \ No newline at end of file diff --git a/ports/wapy-wasm/Makefile b/ports/wapy-wasm/Makefile new file mode 100644 index 000000000..50b14885b --- /dev/null +++ b/ports/wapy-wasm/Makefile @@ -0,0 +1,366 @@ +#PORTS=$(EM_CACHE)/asmjs/ports-builds +#FROZEN_MPY_DIR ?= modules +#FROZEN_DIR ?= flash + + +BASENAME=wapy +PROG=$(BASENAME).html +LIBPYTHON = lib$(BASENAME).a +CROSS = 0 +# clang has slightly different options to GCC +#CLANG = 1 + +# compiler settings +ifdef CWARN +else +CFLAGS += $(CWARN) -Wall -Werror +endif + +CFLAGS += -Wpointer-arith -Wuninitialized -ferror-limit=2 + + +include ../../py/mkenv.mk + +#EM_CACHE ?= $(HOME)/.emscripten_cache + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h + +ifdef EMSCRIPTEN + ifdef WASM_FILE_API +# note that WASM_FILE_API will pull filesystem function into the wasm lib + CPPFLAGS += -DWASM_FILE_API=1 + LD_LIB += -s FORCE_FILESYSTEM=1 + COPT += -s FORCE_FILESYSTEM=1 + endif + + CC = emcc + LD = $(CC) + + ifeq ($(DEBUG), 1) + OG = -O0 -g4 + else + #OPTIM=1 + #DLO=1 + CPPFLAGS += -DNDEBUG + OG = -Oz -g0 + endif + + + ifdef LVGL + CPPFLAGS = -DMICROPY_PY_LVGL=1 + JSFLAGS += -s USE_SDL=2 + CFLAGS_USERMOD += -DMICROPY_PY_LVGL=1 -Wno-unused-function -Wno-for-loop-analysis + CPP += -DMICROPY_PY_LVGL=1 + endif + + #Debugging/Optimization + WASM_FLAGS += $(OG) --source-map-base http://localhost:8000/ + LD_SHARED += $(OG) --source-map-base http://localhost:8000/ + + # make clang++ Act like 'emcc' but for preprocessing use + CPP = ${EMSCRIPTEN}/../bin/clang -E -undef -D__CPP__ -D__EMSCRIPTEN__ + CPP += --sysroot $(EMSCRIPTEN)/system + CPP += -include $(BUILD)/clang_predefs.h + CPP += $(addprefix -isystem, $(shell env LC_ALL=C $(CC) $(JSFLAGS) $(CFLAGS_EXTRA) -E -x c++ /dev/null -v 2>&1 |sed -e '/^\#include <...>/,/^End of search/{ //!b };d')) + + CPPFLAGS += -D__WASM__ -DMICROPY_PY_FFI=1 -Wno-unused-variable + CPP += -D__WASM__ -DMICROPY_PY_FFI=1 + CPPFLAGS += $(INC) -Wall -ansi -std=gnu11 + + # NO FFI on upstream for now + +else + CPPFLAGS += $(INC) $(WARN) -DMICROPY_PY_FFI=1 -ansi -std=gnu11 + CPP += -D__CPP__ -D__EMSCRIPTEN__ -DMICROPY_PY_FFI=1 +endif + + +CC += $(OG) +LD += $(OG) + + +# include py core make definitions +include ../../py/py.mk + + +SIZE = echo + +INC += -I. +INC += -I../.. +INC += -I$(BUILD) + + + +#LD_SHARED = -Wl,-map,$@.map -Wl,-dead_strip -Wl,-no_pie +LD_SHARED += -fno-exceptions -fno-rtti + + + + + +ifneq ($(FROZEN_DIR),) +# To use frozen source modules, put your .py files in a subdirectory (eg scripts/) +# and then invoke make with FROZEN_DIR=scripts (be sure to build from scratch). +CPPFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +ifneq ($(FROZEN_MPY_DIR),) +# To use frozen bytecode, put your .py files in a subdirectory (eg frozen/) and +# then invoke make with FROZEN_MPY_DIR=frozen (be sure to build from scratch). +# //for qstr.c + +CPPFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CPPFLAGS += -DMICROPY_MODULE_FROZEN_MPY +endif + +# //for build/genhdr/qstr.i.last +# QSTR_GEN_EXTRA_CFLAGS=-DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + +MPY_CROSS_FLAGS += -mcache-lookup-bc + +include ../wapy/wapy.mk + + +SRC_C = \ + ../wapy/core/vfs.c \ + ../wapy/core/ringbuf_b.c \ + ../wapy/core/ringbuf_o.c \ + ../wapy/core/file.wapy.c \ + mod/modos.c \ + mod/modtime.c \ + mod/moduos_vfs.c \ + + +LIB_SRC_C= $(addprefix lib/, \ + utils/stdout_helpers.c \ + utils/pyexec.c \ + utils/interrupt_char.c \ + mp-readline/readline.c \ + ) + +# now using user mod dir, SRC_MOD use USER_C_MODULES + +ifdef LVGL + LIB_SRC_C_EXTRA += $(addprefix lib/,\ + lv_bindings/driver/SDL/SDL_monitor.c \ + lv_bindings/driver/SDL/SDL_mouse.c \ + lv_bindings/driver/SDL/modSDL.c \ + timeutils/timeutils.c \ + ) + CPPFLAGS += -Wno-unused-function -Wno-for-loop-analysis +endif + + +ifdef NDK + SRC_C += ../wapy-wasm/main.c mod/modffi.c +else +# optionnal experimental FFI + SRC_C+= \ + mod/modffi.c \ + mod/ffi/ffi.c \ + mod/ffi/types.c \ + mod/ffi/prep_cif.c +endif + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) qstr/objtype.c qstr/modbuiltins.c +SRC_QSTR += $(LIB_SRC_C) + + +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += SRC_QSTR + +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C_EXTRA:.c=.o)) + + +CPPFLAGS += -DNO_NLR=$(NO_NLR) +CFLAGS += $(CPPFLAGS) $(CFLAGS_EXTRA) + + +all: $(PROG) + +include ../../py/mkrules.mk + + +# one day, maybe go via a *embeddable* static lib first ? +LIBPYTHON = lib$(BASENAME)$(TARGET).a + +#force preprocessor env to be created before qstr extraction +ifdef EMSCRIPTEN +$(BUILD)/clang_predefs.h: + $(Q)mkdir -p $(dir $@) + $(Q)emcc $(CFLAGS) $(JSFLAGS) -E -x c /dev/null -dM > $@ + +# Create `clang_predefs.h` as soon as possible, using a Makefile trick + +Makefile: $(BUILD)/clang_predefs.h +endif + + + +ifdef NDK +COPT += +LD_PROG += +WASM_FLAGS = +endif + +ifndef NDK +COPT += --memory-init-file 0 +COPT += -s NO_EXIT_RUNTIME=1 +COPT += -s ALLOW_MEMORY_GROWTH=0 -s TOTAL_STACK=16777216 +COPT += -s TOTAL_MEMORY=512MB +# COPT+= -s "ENVIRONMENT=web" +# COPT+= -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +# compression and dlopen(wasm) can fail on chrom* ? +# -s LZ4=0 +COPT += $(JSFLAGS) $(WASM_FLAGS) -s ASSERTIONS=2 + +LD_PROG += -s FORCE_FILESYSTEM=1 +LD_PROG += -s EXPORTED_FUNCTIONS="['_main', '_shm_ptr', '_shm_get_ptr','_repl_run', '_show_os_loop']" +#LD_PROG += -s EXTRA_EXPORTED_RUNTIME_METHODS="['addRunDependency','removeRunDependency']" + +WASM_FLAGS += -s LZ4=1 -s WASM=1 -s EXPORT_ALL=1 +WASM_FLAGS += -s ERROR_ON_UNDEFINED_SYMBOLS=1 +WASM_FLAGS += -s ASSERTIONS=1 -s DEMANGLE_SUPPORT=1 -s DISABLE_EXCEPTION_CATCHING=1 + +# -s USE_ZLIB=1 -s USE_LIBPNG=1 -s USE_OGG=1 +# COPT += -s USE_BULLET=1 -s USE_FREETYPE=1 +# LD_EXTRA += -lbullet -lfreetype +THR_FLAGS=-s FETCH=1 -s USE_PTHREADS=0 +endif + + +clean: + $(RM) -rf $(BUILD) $(CLEAN_EXTRA) $(LIBPYTHON) + $(shell rm $(BASENAME)/$(BASENAME).* || echo echo test data cleaned up) + + +lib-static: $(OBJ) + $(ECHO) "Linking static $(LIBPYTHON)" + $(Q)$(AR) rcs $(LIBPYTHON) $(OBJ) + + +lib-shared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) -ldl -lc -s FORCE_FILESYSTEM=1 --use-preload-plugins + +lib-android: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).so" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).so $(OBJ) $(LD_EXTRA) + +lib-allshared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) +# + $(ECHO) "Linking shared libsdl2.wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o libsdl2.wasm -lSDL2 -logg -lfreetype + +libs: lib-static lib-shared + +ifdef HALF +#============ +LD_PROG += $(WASM_FLAGS) -s MAIN_MODULE=1 +LD_LIB += $(WASM_FLAGS) -s SIDE_MODULE=1 +CFLAGS += -fPIC + +# lib-static <= beware to clean the .a could be linked in +$(PROG): lib-allshared + $(ECHO) "Building shared executable $@" + $(shell cp lib$(BASENAME)$(TARGET).wasm lib$(BASENAME)$(TARGET).so) + $(shell cp libsdl2.wasm libsdl2.so) + $(Q)$(CC) $(CFLAGS) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) $(BLOBS)\ + -s BINARYEN_ASYNC_COMPILATION=1 -s FORCE_FILESYSTEM=1 --use-preload-plugins\ + -o $@ main.c -l$(BASENAME)$(TARGET) -lSDL2 -lGL -lEGL -lGLESv2 -ldl -lm + $(shell mv $(BASENAME).* $(BASENAME)/) +#-L. lib$(BASENAME)$(TARGET).a +endif + +ifdef SHARED +#============ +LD_PROG += $(WASM_FLAGS) -s MAIN_MODULE=1 +LD_LIB += $(WASM_FLAGS) -s SIDE_MODULE=1 +CFLAGS += -fPIC + +$(PROG): lib-static lib-allshared + $(ECHO) "Building shared executable $@" + $(shell cp lib$(BASENAME)$(TARGET).wasm lib$(BASENAME)$(TARGET).so) + $(shell cp libsdl2.wasm libsdl2.so) + $(Q)$(CC) $(CFLAGS) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) $(BLOBS)\ + -s BINARYEN_ASYNC_COMPILATION=1 -s FORCE_FILESYSTEM=1 --use-preload-plugins\ + -o $@ main.c -L. -lwapy -lsdl2 -ldl -lm + $(shell mv $(BASENAME).* $(BASENAME)/) + +endif + + +ifdef STATIC +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +LD_PROG += $(CFLAGS) $(WASM_FLAGS) -s MAIN_MODULE=1 -s EXPORT_ALL=1 -s LZ4=1 +LD_LIB += $(WASM_FLAGS) +CFLAGS += -fPIC + +$(PROG): lib-static + $(ECHO) "Building static executable $@" + $(Q)$(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c lib$(BASENAME)$(TARGET).a $(BLOBS) -lGL -lEGL -lGLESv2 -ldl -lm $(LD_EXTRA) + $(shell mv $(BASENAME).* $(BASENAME)/) +# ============================================================ +endif + +ifdef NDK +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +LD_PROG += $(CFLAGS) +CFLAGS += -fPIC +LD_LIB = + +$(PROG): lib-android + $(ECHO) "Building static executable $@" + $(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c -L. -lwapy $(BLOBS) -lEGL -lGLESv2 -ldl -lm $(LD_EXTRA) + $(shell mv $(BASENAME).* $(BASENAME)/) +# ============================================================ +endif + + + +check: + $(ECHO) + $(ECHO) "============================================================" + $(ECHO) "CC=[$(CC)]" + $(ECHO) "CPP=[$(CPP)]" + $(ECHO) "COPT=[$(COPT)]" + $(ECHO) JSFLAGS=$(JSFLAGS) + $(ECHO) CPPFLAGS=$(CPPFLAGS) + $(ECHO) + $(ECHO) CXX=$(CXX) + $(ECHO) AS=$(AS) + $(ECHO) LD=$(LD) + $(ECHO) OBJCOPY=$(OBJCOPY) + $(ECHO) SIZE=$(SIZE) + $(ECHO) STRIP=$(STRIP) + $(ECHO) AR=$(AR) + $(ECHO) +#emscripten specifics + $(ECHO) EMSDK=$(EMSDK) + $(ECHO) UPSTREAM=$(UPSTREAM) + $(ECHO) EMSCRIPTEN=$(EMSCRIPTEN) + $(ECHO) EMSDK_NODE=$(EMSDK_NODE) + $(ECHO) EMSCRIPTEN_TOOLS=$(EMSCRIPTEN_TOOLS) + $(ECHO) EM_CONFIG=$(EM_CONFIG) + $(ECHO) EMMAKEN_COMPILER=$(EMMAKEN_COMPILER) + $(ECHO) EMMAKEN_CFLAGS=$(EMMAKEN_CFLAGS) + $(ECHO) EMCC_FORCE_STDLIBS=$(EMCC_FORCE_STDLIBS) + $(ECHO) EM_CACHE=$(EM_CACHE) + $(ECHO) EM_CONFIG=$(EM_CONFIG) + $(shell env|grep ^EM) + $(ECHO) + $(shell ${EMSCRIPTEN}/../bin/clang -v 2>&1|grep ^clang ) + #terminate on error so python webserver won't start diff --git a/ports/wapy-wasm/README.txt b/ports/wapy-wasm/README.txt new file mode 100644 index 000000000..3e2d3cc3b --- /dev/null +++ b/ports/wapy-wasm/README.txt @@ -0,0 +1,3 @@ +WIP +CPP / C / Assembly skills required +freenode irc , #micropython-fr diff --git a/ports/wapy-wasm/api/wasm_file_api.c b/ports/wapy-wasm/api/wasm_file_api.c new file mode 100644 index 000000000..5812fac4b --- /dev/null +++ b/ports/wapy-wasm/api/wasm_file_api.c @@ -0,0 +1,33 @@ +#include +#include +#include "py/mpconfig.h" + +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#endif + +// VERY BAD +int +wasm_file_open(const char *url) { + fprintf(stderr,"10:wasm_file_open[%s]\n", url); + + if (strlen(url)>1 && url[0]==':') { + fprintf(stderr," -> same host[%s]\n", url); + int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url ); + + // TODO rebuild local path in ramdisk relative to getcwd() + char fname[MICROPY_ALLOC_PATH_MAX]; + + snprintf(fname, sizeof(fname), "cache_%d", fidx); + return fileno( fopen(fname,"r") ); + } + if ( (strlen(url)>6) && (url[4]==':' || url[5]==':') ) { + fprintf(stderr," -> remote host[%s]\n", url); + int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url ); + char fname[20]; + snprintf(fname, sizeof(fname), "cache_%d", fidx); + return fileno( fopen(fname,"r") ); + } + return 0; +} + diff --git a/ports/wapy-wasm/api/wasm_import_api.c b/ports/wapy-wasm/api/wasm_import_api.c new file mode 100644 index 000000000..4efa9d65f --- /dev/null +++ b/ports/wapy-wasm/api/wasm_import_api.c @@ -0,0 +1,30 @@ +// TODO: that can't go inside a shared wasm lib + +mp_import_stat_t +wasm_find_module(const char *modname) { + if( access( modname, F_OK ) != -1 ) { + struct stat stats; + stat(modname, &stats); + if (S_ISDIR(stats.st_mode)) + return MP_IMPORT_STAT_DIR; + return MP_IMPORT_STAT_FILE; + } + + int found = EM_ASM_INT({return wasm_file_exists(UTF8ToString($0), true); }, modname ) ; + + if ( found>0 ) { + int dl = EM_ASM_INT({return wasm_file_open(UTF8ToString($0),UTF8ToString($0)); }, modname ); + + if (found==1) { + fprintf(stderr,"wasm_find_module: DL FILE %s size=%d ", modname, dl); + return MP_IMPORT_STAT_FILE; + } + if (found==2) { + fprintf(stderr,"wasm_find_module: IS DIR %s size=%d ", modname, dl); + return MP_IMPORT_STAT_DIR; + } + } + fprintf(stderr,"404:wasm_find_module '%s' (%d)\n", modname, found); + return MP_IMPORT_STAT_NO_EXIST; +} + diff --git a/ports/wapy-wasm/bc_as_integers.h b/ports/wapy-wasm/bc_as_integers.h new file mode 100644 index 000000000..64adfd7f4 --- /dev/null +++ b/ports/wapy-wasm/bc_as_integers.h @@ -0,0 +1,282 @@ + +#undef MP_BC_MASK_FORMAT +#define MP_BC_MASK_FORMAT (240) + +#undef MP_BC_MASK_EXTRA_BYTE +#define MP_BC_MASK_EXTRA_BYTE (158) + +#undef MP_BC_FORMAT_BYTE +#define MP_BC_FORMAT_BYTE (0) + +#undef MP_BC_FORMAT_QSTR +#define MP_BC_FORMAT_QSTR (1) + +#undef MP_BC_FORMAT_VAR_UINT +#define MP_BC_FORMAT_VAR_UINT (2) + +#undef MP_BC_FORMAT_OFFSET +#define MP_BC_FORMAT_OFFSET (3) + +#undef MP_BC_BASE_RESERVED +#define MP_BC_BASE_RESERVED (0) + +#undef MP_BC_BASE_QSTR_O +#define MP_BC_BASE_QSTR_O (16) + +#undef MP_BC_BASE_VINT_E +#define MP_BC_BASE_VINT_E (32) + +#undef MP_BC_BASE_VINT_O +#define MP_BC_BASE_VINT_O (48) + +#undef MP_BC_BASE_JUMP_E +#define MP_BC_BASE_JUMP_E (64) + +#undef MP_BC_BASE_BYTE_O +#define MP_BC_BASE_BYTE_O (80) + +#undef MP_BC_BASE_BYTE_E +#define MP_BC_BASE_BYTE_E (96) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI (112) + +#undef MP_BC_LOAD_FAST_MULTI +#define MP_BC_LOAD_FAST_MULTI (176) + +#undef MP_BC_STORE_FAST_MULTI +#define MP_BC_STORE_FAST_MULTI (192) + +#undef MP_BC_UNARY_OP_MULTI +#define MP_BC_UNARY_OP_MULTI (208) + +#undef MP_BC_BINARY_OP_MULTI +#define MP_BC_BINARY_OP_MULTI (215) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM (64) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS (16) + +#undef MP_BC_LOAD_FAST_MULTI_NUM +#define MP_BC_LOAD_FAST_MULTI_NUM (16) + +#undef MP_BC_STORE_FAST_MULTI_NUM +#define MP_BC_STORE_FAST_MULTI_NUM (16) + +#undef MP_BC_LOAD_CONST_FALSE +#define MP_BC_LOAD_CONST_FALSE (80) + +#undef MP_BC_LOAD_CONST_NONE +#define MP_BC_LOAD_CONST_NONE (81) + +#undef MP_BC_LOAD_CONST_TRUE +#define MP_BC_LOAD_CONST_TRUE (82) + +#undef MP_BC_LOAD_CONST_SMALL_INT +#define MP_BC_LOAD_CONST_SMALL_INT (34) + +#undef MP_BC_LOAD_CONST_STRING +#define MP_BC_LOAD_CONST_STRING (16) + +#undef MP_BC_LOAD_CONST_OBJ +#define MP_BC_LOAD_CONST_OBJ (35) + +#undef MP_BC_LOAD_NULL +#define MP_BC_LOAD_NULL (83) + +#undef MP_BC_LOAD_FAST_N +#define MP_BC_LOAD_FAST_N (36) + +#undef MP_BC_LOAD_DEREF +#define MP_BC_LOAD_DEREF (37) + +#undef MP_BC_LOAD_NAME +#define MP_BC_LOAD_NAME (17) + +#undef MP_BC_LOAD_GLOBAL +#define MP_BC_LOAD_GLOBAL (18) + +#undef MP_BC_LOAD_ATTR +#define MP_BC_LOAD_ATTR (19) + +#undef MP_BC_LOAD_METHOD +#define MP_BC_LOAD_METHOD (20) + +#undef MP_BC_LOAD_SUPER_METHOD +#define MP_BC_LOAD_SUPER_METHOD (21) + +#undef MP_BC_LOAD_BUILD_CLASS +#define MP_BC_LOAD_BUILD_CLASS (84) + +#undef MP_BC_LOAD_SUBSCR +#define MP_BC_LOAD_SUBSCR (85) + +#undef MP_BC_STORE_FAST_N +#define MP_BC_STORE_FAST_N (38) + +#undef MP_BC_STORE_DEREF +#define MP_BC_STORE_DEREF (39) + +#undef MP_BC_STORE_NAME +#define MP_BC_STORE_NAME (22) + +#undef MP_BC_STORE_GLOBAL +#define MP_BC_STORE_GLOBAL (23) + +#undef MP_BC_STORE_ATTR +#define MP_BC_STORE_ATTR (24) + +#undef MP_BC_STORE_SUBSCR +#define MP_BC_STORE_SUBSCR (86) + +#undef MP_BC_DELETE_FAST +#define MP_BC_DELETE_FAST (40) + +#undef MP_BC_DELETE_DEREF +#define MP_BC_DELETE_DEREF (41) + +#undef MP_BC_DELETE_NAME +#define MP_BC_DELETE_NAME (25) + +#undef MP_BC_DELETE_GLOBAL +#define MP_BC_DELETE_GLOBAL (26) + +#undef MP_BC_DUP_TOP +#define MP_BC_DUP_TOP (87) + +#undef MP_BC_DUP_TOP_TWO +#define MP_BC_DUP_TOP_TWO (88) + +#undef MP_BC_POP_TOP +#define MP_BC_POP_TOP (89) + +#undef MP_BC_ROT_TWO +#define MP_BC_ROT_TWO (90) + +#undef MP_BC_ROT_THREE +#define MP_BC_ROT_THREE (91) + +#undef MP_BC_JUMP +#define MP_BC_JUMP (66) + +#undef MP_BC_POP_JUMP_IF_TRUE +#define MP_BC_POP_JUMP_IF_TRUE (67) + +#undef MP_BC_POP_JUMP_IF_FALSE +#define MP_BC_POP_JUMP_IF_FALSE (68) + +#undef MP_BC_JUMP_IF_TRUE_OR_POP +#define MP_BC_JUMP_IF_TRUE_OR_POP (69) + +#undef MP_BC_JUMP_IF_FALSE_OR_POP +#define MP_BC_JUMP_IF_FALSE_OR_POP (70) + +#undef MP_BC_UNWIND_JUMP +#define MP_BC_UNWIND_JUMP (64) + +#undef MP_BC_SETUP_WITH +#define MP_BC_SETUP_WITH (71) + +#undef MP_BC_SETUP_EXCEPT +#define MP_BC_SETUP_EXCEPT (72) + +#undef MP_BC_SETUP_FINALLY +#define MP_BC_SETUP_FINALLY (73) + +#undef MP_BC_POP_EXCEPT_JUMP +#define MP_BC_POP_EXCEPT_JUMP (74) + +#undef MP_BC_FOR_ITER +#define MP_BC_FOR_ITER (75) + +#undef MP_BC_WITH_CLEANUP +#define MP_BC_WITH_CLEANUP (92) + +#undef MP_BC_END_FINALLY +#define MP_BC_END_FINALLY (93) + +#undef MP_BC_GET_ITER +#define MP_BC_GET_ITER (94) + +#undef MP_BC_GET_ITER_STACK +#define MP_BC_GET_ITER_STACK (95) + +#undef MP_BC_BUILD_TUPLE +#define MP_BC_BUILD_TUPLE (42) + +#undef MP_BC_BUILD_LIST +#define MP_BC_BUILD_LIST (43) + +#undef MP_BC_BUILD_MAP +#define MP_BC_BUILD_MAP (44) + +#undef MP_BC_STORE_MAP +#define MP_BC_STORE_MAP (98) + +#undef MP_BC_BUILD_SET +#define MP_BC_BUILD_SET (45) + +#undef MP_BC_BUILD_SLICE +#define MP_BC_BUILD_SLICE (46) + +#undef MP_BC_STORE_COMP +#define MP_BC_STORE_COMP (47) + +#undef MP_BC_UNPACK_SEQUENCE +#define MP_BC_UNPACK_SEQUENCE (48) + +#undef MP_BC_UNPACK_EX +#define MP_BC_UNPACK_EX (49) + +#undef MP_BC_RETURN_VALUE +#define MP_BC_RETURN_VALUE (99) + +#undef MP_BC_RAISE_LAST +#define MP_BC_RAISE_LAST (100) + +#undef MP_BC_RAISE_OBJ +#define MP_BC_RAISE_OBJ (101) + +#undef MP_BC_RAISE_FROM +#define MP_BC_RAISE_FROM (102) + +#undef MP_BC_YIELD_VALUE +#define MP_BC_YIELD_VALUE (103) + +#undef MP_BC_YIELD_FROM +#define MP_BC_YIELD_FROM (104) + +#undef MP_BC_MAKE_FUNCTION +#define MP_BC_MAKE_FUNCTION (50) + +#undef MP_BC_MAKE_FUNCTION_DEFARGS +#define MP_BC_MAKE_FUNCTION_DEFARGS (51) + +#undef MP_BC_MAKE_CLOSURE +#define MP_BC_MAKE_CLOSURE (32) + +#undef MP_BC_MAKE_CLOSURE_DEFARGS +#define MP_BC_MAKE_CLOSURE_DEFARGS (33) + +#undef MP_BC_CALL_FUNCTION +#define MP_BC_CALL_FUNCTION (52) + +#undef MP_BC_CALL_FUNCTION_VAR_KW +#define MP_BC_CALL_FUNCTION_VAR_KW (53) + +#undef MP_BC_CALL_METHOD +#define MP_BC_CALL_METHOD (54) + +#undef MP_BC_CALL_METHOD_VAR_KW +#define MP_BC_CALL_METHOD_VAR_KW (55) + +#undef MP_BC_IMPORT_NAME +#define MP_BC_IMPORT_NAME (27) + +#undef MP_BC_IMPORT_FROM +#define MP_BC_IMPORT_FROM (28) + +#undef MP_BC_IMPORT_STAR +#define MP_BC_IMPORT_STAR (105) diff --git a/ports/wapy-wasm/dev_conf.h b/ports/wapy-wasm/dev_conf.h new file mode 100644 index 000000000..505ca3a07 --- /dev/null +++ b/ports/wapy-wasm/dev_conf.h @@ -0,0 +1,3 @@ +#ifndef __DEV__ +#define __DEV__ 1 +#endif diff --git a/ports/wapy-wasm/file_packager.py b/ports/wapy-wasm/file_packager.py new file mode 100755 index 000000000..8fa86cc43 --- /dev/null +++ b/ports/wapy-wasm/file_packager.py @@ -0,0 +1,1005 @@ +#!/usr/bin/env python3.8 +# Copyright 2012 The Emscripten Authors. All rights reserved. +# Emscripten is available under two separate licenses, the MIT license and the +# University of Illinois/NCSA Open Source License. Both these licenses can be +# found in the LICENSE file. + +""" +A tool that generates FS API calls to generate a filesystem, and packages the files +to work with that. + +This is called by emcc. You can also call it yourself. + +You can split your files into "asset bundles", and create each bundle separately +with this tool. Then just include the generated js for each and they will load +the data and prepare it accordingly. This allows you to share assets and reduce +data downloads. + + * If you run this yourself, separately/standalone from emcc, then the main program + compiled by emcc must be built with filesystem support. You can do that with + -s FORCE_FILESYSTEM=1 (if you forget that, an unoptimized build or one with + ASSERTIONS enabled will show an error suggesting you use that flag). + +Usage: + + file_packager.py TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--no-heap-copy] [--separate-metadata] [--lz4] [--use-preload-plugins] + + --preload , + --embed See emcc --help for more details on those options. + + --exclude E [F..] Specifies filename pattern matches to use for excluding given files from being added to the package. + See https://docs.python.org/2/library/fnmatch.html for syntax. + + --from-emcc Indicate that `file_packager.py` was called from `emcc.py` and will be further processed by it, so some code generation can be skipped here + + --js-output=FILE Writes output in FILE, if not specified, standard output is used. + + --export-name=EXPORT_NAME Use custom export name (default is `Module`) + + --no-force Don't create output if no valid input file is specified. + + --use-preload-cache Stores package in IndexedDB so that subsequent loads don't need to do XHR. Checks package version. + + --indexedDB-name Use specified IndexedDB database name (Default: 'EM_PRELOAD_CACHE') + + --no-heap-copy If specified, the preloaded filesystem is not copied inside the Emscripten HEAP, but kept in a separate typed array outside it. + The default, if this is not specified, is to embed the VFS inside the HEAP, so that mmap()ing files in it is a no-op. + Passing this flag optimizes for fread() usage, omitting it optimizes for mmap() usage. + + --separate-metadata Stores package metadata separately. Only applicable when preloading and js-output file is specified. + + --lz4 Uses LZ4. This compresses the data using LZ4 when this utility is run, then the client decompresses chunks on the fly, avoiding storing + the entire decompressed data in memory at once. See LZ4 in src/settings.js, you must build the main program with that flag. + + --use-preload-plugins Tells the file packager to run preload plugins on the files as they are loaded. This performs tasks like decoding images + and audio using the browser's codecs. + +Notes: + + * The file packager generates unix-style file paths. So if you are on windows and a file is accessed at + subdir\file, in JS it will be subdir/file. For simplicity we treat the web platform as a *NIX. +""" + +import os +import sys +import shutil +import random +import uuid +import ctypes + +sys.path.insert(1, f"{os.environ['EMSDK']}/upstream/emscripten") + +def make_hash(data): + return __import__('hashlib').md5(data.encode('utf-8')).hexdigest() + +PYDIR='/tmp/py' +try: + if not os.path.isdir(PYDIR): + os.mkdir(PYDIR) +except: + raise SystemExit(f"{PYDIR} is not writeable") + + +try: + #new and shiny + from future_fstrings import fstring_decode +except: + #old and buggy + try: + from fstrings_helper import decode as fstring_decode + except: + raise SystemExit(f"future_fstrings[rewrite] or fstrings_helper not installed") + +import posixpath +from tools import shared +from tools.jsrun import run_js +from subprocess import PIPE +import fnmatch +import json + +if len(sys.argv) == 1: + print( + """Usage: file_packager.py TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--no-heap-copy] [--separate-metadata] [--lz4] [--use-preload-plugins] +See the source for more details.""" + ) + sys.exit(0) + +DEBUG = os.environ.get("EMCC_DEBUG") + +data_target = sys.argv[1] + +IMAGE_SUFFIXES = (".jpg", ".png", ".bmp") +AUDIO_SUFFIXES = (".ogg", ".wav", ".mp3") +AUDIO_MIMETYPES = {"ogg": "audio/ogg", "wav": "audio/wav", "mp3": "audio/mpeg"} + +DDS_HEADER_SIZE = 128 + +# Set to 1 to randomize file order and add some padding, +# to work around silly av false positives +AV_WORKAROUND = 0 + +excluded_patterns = [] +new_data_files = [] + + +def has_hidden_attribute(filepath): + """Win32 code to test whether the given file has the hidden property set.""" + + if sys.platform != "win32": + return False + + try: + attrs = ctypes.windll.kernel32.GetFileAttributesW("%s" % filepath) + assert attrs != -1 + result = bool(attrs & 2) + except Exception: + result = False + return result + + +def should_ignore(fullname, is_file=0): + """The packager should never preload/embed files if the file + is hidden (Win32) or it matches any pattern specified in --exclude""" + + global process_filter + process_filter = None + + if has_hidden_attribute(fullname): + return True + + for p in excluded_patterns: + if fnmatch.fnmatch(fullname, p): + return True + + if is_file and fullname.endswith('.py'): + process_filter = f"{PYDIR}/{make_hash(fullname)}" + with open(fullname,'rb') as src, open(process_filter,'wb') as dst: + content, _ = fstring_decode(src.read()) + dst.write( content.encode('UTF-8') ) + print(fullname, process_filter, file=sys.stderr) + + return False + + +def add(mode, rootpathsrc, rootpathdst): + """Expand directories into individual files + + rootpathsrc: The path name of the root directory on the local FS we are + adding to emscripten virtual FS. + rootpathdst: The name we want to make the source path available on the + emscripten virtual FS. + """ + for dirpath, dirnames, filenames in os.walk(rootpathsrc): + new_dirnames = [] + for name in dirnames: + fullname = os.path.join(dirpath, name) + if not should_ignore(fullname): + new_dirnames.append(name) + elif DEBUG: + print( + 'Skipping directory "%s" from inclusion in the emscripten ' "virtual file system." % fullname, file=sys.stderr + ) + for name in filenames: + fullname = os.path.join(dirpath, name) + if not should_ignore(fullname, is_file=1): + # Convert source filename relative to root directory of target FS. + dstpath = os.path.join(rootpathdst, os.path.relpath(fullname, rootpathsrc)) + if process_filter is None: + new_data_files.append({"srcpath": fullname, "dstpath": dstpath, "mode": mode, "explicit_dst_path": True}) + else: + new_data_files.append({"srcpath": process_filter, "dstpath": dstpath, "mode": mode, "explicit_dst_path": True}) + elif DEBUG: + print('Skipping file "%s" from inclusion in the emscripten ' "virtual file system." % fullname, file=sys.stderr) + del dirnames[:] + dirnames.extend(new_dirnames) + + +def main(): + data_files = [] + export_name = "Module" + leading = "" + has_preloaded = False + plugins = [] + jsoutput = None + from_emcc = False + force = True + # If set to True, IndexedDB (IDBFS in library_idbfs.js) is used to locally + # cache VFS XHR so that subsequent page loads can read the data from the + # offline cache instead. + use_preload_cache = False + indexeddb_name = "EM_PRELOAD_CACHE" + # If set to True, the blob received from XHR is moved to the Emscripten HEAP, + # optimizing for mmap() performance (if ALLOW_MEMORY_GROWTH=0). + # If set to False, the XHR blob is kept intact, and fread()s etc. are performed + # directly to that data. This optimizes for minimal memory usage and fread() + # performance. + heap_copy = True + # If set to True, the package metadata is stored separately from js-output + # file which makes js-output file immutable to the package content changes. + # If set to False, the package metadata is stored inside the js-output file + # which makes js-output file to mutate on each invocation of this packager tool. + separate_metadata = False + lz4 = False + use_preload_plugins = False + + for arg in sys.argv[2:]: + if arg == "--preload": + has_preloaded = True + leading = "preload" + elif arg == "--embed": + leading = "embed" + elif arg == "--exclude": + leading = "exclude" + elif arg == "--no-force": + force = False + leading = "" + elif arg == "--use-preload-cache": + use_preload_cache = True + leading = "" + elif arg.startswith("--indexedDB-name"): + indexeddb_name = arg.split("=", 1)[1] if "=" in arg else None + leading = "" + elif arg == "--no-heap-copy": + heap_copy = False + leading = "" + elif arg == "--separate-metadata": + separate_metadata = True + leading = "" + elif arg == "--lz4": + lz4 = True + leading = "" + elif arg == "--use-preload-plugins": + use_preload_plugins = True + leading = "" + elif arg.startswith("--js-output"): + jsoutput = arg.split("=", 1)[1] if "=" in arg else None + leading = "" + elif arg.startswith("--export-name"): + if "=" in arg: + export_name = arg.split("=", 1)[1] + leading = "" + elif arg.startswith("--from-emcc"): + from_emcc = True + leading = "" + elif arg.startswith("--plugin"): + with open(arg.split("=", 1)[1]) as f: + plugin = f.read() + eval(plugin) # should append itself to plugins + leading = "" + elif leading == "preload" or leading == "embed": + mode = leading + # position of @ if we're doing 'src@dst'. '__' is used to keep the index + # same with the original if they escaped with '@@'. + at_position = arg.replace("@@", "__").find("@") + # '@@' in input string means there is an actual @ character, a single '@' + # means the 'src@dst' notation. + uses_at_notation = at_position != -1 + + if uses_at_notation: + srcpath = arg[0:at_position].replace("@@", "@") # split around the @ + dstpath = arg[at_position + 1 :].replace("@@", "@") + else: + # Use source path as destination path. + srcpath = dstpath = arg.replace("@@", "@") + if os.path.isfile(srcpath) or os.path.isdir(srcpath): + data_files.append({"srcpath": srcpath, "dstpath": dstpath, "mode": mode, "explicit_dst_path": uses_at_notation}) + else: + print("Warning: " + arg + " does not exist, ignoring.", file=sys.stderr) + elif leading == "exclude": + excluded_patterns.append(arg) + else: + print("Unknown parameter:", arg, file=sys.stderr) + sys.exit(1) + + if (not force) and not data_files: + has_preloaded = False + if not has_preloaded or jsoutput is None: + assert not separate_metadata, "cannot separate-metadata without both --preloaded files " "and a specified --js-output" + + if not from_emcc: + print( + "Remember to build the main file with -s FORCE_FILESYSTEM=1 " + "so that it includes support for loading this file package", + file=sys.stderr, + ) + + ret = "" + # emcc.py will add this to the output itself, so it is only needed for + # standalone calls + if not from_emcc: + ret = """ + var Module = typeof %(EXPORT_NAME)s !== 'undefined' ? %(EXPORT_NAME)s : {}; + """ % { + "EXPORT_NAME": export_name + } + + ret += """ + if (!Module.expectedDataFileDownloads) { + Module.expectedDataFileDownloads = 0; + Module.finishedDataFileDownloads = 0; + } + Module.expectedDataFileDownloads++; + (function() { + var loadPackage = function(metadata) { + """ + + code = """ + function assert(check, msg) { + if (!check) throw msg + new Error().stack; + } + """ + + for file_ in data_files: + if not should_ignore(file_["srcpath"]): + if os.path.isdir(file_["srcpath"]): + add(file_["mode"], file_["srcpath"], file_["dstpath"]) + else: + should_ignore(file_["srcpath"], is_file=1) + if process_filter: + newfile = {**file_} + newfile["srcpath"] = process_filter + new_data_files.append(newfile) + print(newfile, file=sys.stderr) + else: + new_data_files.append(file_) + + data_files = [file_ for file_ in new_data_files if not os.path.isdir(file_["srcpath"])] + if len(data_files) == 0: + print("Nothing to do!", file=sys.stderr) + sys.exit(1) + + # Absolutize paths, and check that they make sense + # os.getcwd() always returns the hard path with any symbolic links resolved, + # even if we cd'd into a symbolic link. + curr_abspath = os.path.abspath(os.getcwd()) + + for file_ in data_files: + if not file_["explicit_dst_path"]: + # This file was not defined with src@dst, so we inferred the destination + # from the source. In that case, we require that the destination not be + # under the current location + path = file_["dstpath"] + # Use os.path.realpath to resolve any symbolic links to hard paths, + # to match the structure in curr_abspath. + abspath = os.path.realpath(os.path.abspath(path)) + if DEBUG: + print(path, abspath, curr_abspath, file=sys.stderr) + if not abspath.startswith(curr_abspath): + print( + 'Error: Embedding "%s" which is below the current directory ' + '"%s". This is invalid since the current directory becomes the ' + "root that the generated code will see" % (path, curr_abspath), + file=sys.stderr, + ) + sys.exit(1) + file_["dstpath"] = abspath[len(curr_abspath) + 1 :] + if os.path.isabs(path): + print( + 'Warning: Embedding an absolute file/directory name "%s" to the ' + "virtual filesystem. The file will be made available in the " + 'relative path "%s". You can use the explicit syntax ' + "--preload-file srcpath@dstpath to explicitly specify the target " + "location the absolute source path should be directed to." % (path, file_["dstpath"]), + file=sys.stderr, + ) + + for file_ in data_files: + # name in the filesystem, native and emulated + file_["dstpath"] = file_["dstpath"].replace(os.path.sep, "/") + # If user has submitted a directory name as the destination but omitted + # the destination filename, use the filename from source file + if file_["dstpath"].endswith("/"): + file_["dstpath"] = file_["dstpath"] + os.path.basename(file_["srcpath"]) + # make destination path always relative to the root + file_["dstpath"] = posixpath.normpath(os.path.join("/", file_["dstpath"])) + if DEBUG: + print('Packaging file "%s" to VFS in path "%s".' % (file_["srcpath"], file_["dstpath"]), file=sys.stderr) + + # Remove duplicates (can occur naively, for example preload dir/, preload dir/subdir/) + seen = {} + + def was_seen(name): + if seen.get(name): + return True + seen[name] = 1 + return False + + data_files = [file_ for file_ in data_files if not was_seen(file_["dstpath"])] + + if AV_WORKAROUND: + random.shuffle(data_files) + + # Apply plugins + for file_ in data_files: + for plugin in plugins: + plugin(file_) + + metadata = {"files": []} + + # Set up folders + partial_dirs = [] + for file_ in data_files: + dirname = os.path.dirname(file_["dstpath"]) + dirname = dirname.lstrip("/") # absolute paths start with '/', remove that + if dirname != "": + parts = dirname.split("/") + for i in range(len(parts)): + partial = "/".join(parts[: i + 1]) + if partial not in partial_dirs: + code += """Module['FS_createPath']('/%s', '%s', true, true);\n""" % ("/".join(parts[:i]), parts[i]) + partial_dirs.append(partial) + + if has_preloaded: + # Bundle all datafiles into one archive. Avoids doing lots of simultaneous + # XHRs which has overhead. + start = 0 + with open(data_target, "wb") as data: + for file_ in data_files: + file_["data_start"] = start + with open(file_["srcpath"], "rb") as f: + curr = f.read() + file_["data_end"] = start + len(curr) + if AV_WORKAROUND: + curr += "\x00" + start += len(curr) + data.write(curr) + + # TODO: sha256sum on data_target + if start > 256 * 1024 * 1024: + print( + "warning: file packager is creating an asset bundle of %d MB. " + "this is very large, and browsers might have trouble loading it. " + "see https://hacks.mozilla.org/2015/02/synchronous-execution-and-filesystem-access-in-emscripten/" + % (start / (1024 * 1024)), + file=sys.stderr, + ) + + create_preloaded = """ + Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() { + Module['removeRunDependency']('fp ' + that.name); + }, function() { + if (that.audio) { + Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) + } else { + err('Preloading file ' + that.name + ' failed'); + } + }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change + """ + create_data = """ + Module['FS_createDataFile'](this.name, null, byteArray, true, true, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change + Module['removeRunDependency']('fp ' + that.name); + """ + + # Data requests - for getting a block of data out of the big archive - have + # a similar API to XHRs + code += """ + /** @constructor */ + function DataRequest(start, end, audio) { + this.start = start; + this.end = end; + this.audio = audio; + } + DataRequest.prototype = { + requests: {}, + open: function(mode, name) { + this.name = name; + this.requests[name] = this; + Module['addRunDependency']('fp ' + this.name); + }, + send: function() {}, + onload: function() { + var byteArray = this.byteArray.subarray(this.start, this.end); + this.finish(byteArray); + }, + finish: function(byteArray) { + var that = this; + %s + this.requests[this.name] = null; + } + }; + %s + """ % ( + create_preloaded if use_preload_plugins else create_data, + """ + var files = metadata['files']; + for (var i = 0; i < files.length; ++i) { + new DataRequest(files[i]['start'], files[i]['end'], files[i]['audio']).open('GET', files[i]['filename']); + } + """ + if not lz4 + else "", + ) + + counter = 0 + for file_ in data_files: + filename = file_["dstpath"] + dirname = os.path.dirname(filename) + basename = os.path.basename(filename) + if file_["mode"] == "embed": + # Embed + data = list(bytearray(open(file_["srcpath"], "rb").read())) + code += """var fileData%d = [];\n""" % counter + if data: + parts = [] + chunk_size = 10240 + start = 0 + while start < len(data): + parts.append( + """fileData%d.push.apply(fileData%d, %s);\n""" % (counter, counter, str(data[start : start + chunk_size])) + ) + start += chunk_size + code += "".join(parts) + code += """Module['FS_createDataFile']('%s', '%s', fileData%d, true, true, false);\n""" % (dirname, basename, counter) + counter += 1 + elif file_["mode"] == "preload": + # Preload + counter += 1 + metadata["files"].append( + { + "filename": file_["dstpath"], + "start": file_["data_start"], + "end": file_["data_end"], + "audio": 1 if filename[-4:] in AUDIO_SUFFIXES else 0, + } + ) + else: + assert 0 + + if has_preloaded: + if not lz4: + # Get the big archive and split it up + if heap_copy: + use_data = """ + // copy the entire loaded file into a spot in the heap. Files will refer to slices in that. They cannot be freed though + // (we may be allocating before malloc is ready, during startup). + var ptr = Module['getMemory'](byteArray.length); + Module['HEAPU8'].set(byteArray, ptr); + DataRequest.prototype.byteArray = Module['HEAPU8'].subarray(ptr, ptr+byteArray.length); + """ + else: + use_data = """ + // Reuse the bytearray from the XHR as the source for file reads. + DataRequest.prototype.byteArray = byteArray; + """ + use_data += """ + var files = metadata['files']; + for (var i = 0; i < files.length; ++i) { + DataRequest.prototype.requests[files[i].filename].onload(); + } + """ + use_data += " Module['removeRunDependency']('datafile_%s');\n" % shared.JS.escape_for_js_string(data_target) + + else: + # LZ4FS usage + temp = data_target + ".orig" + shutil.move(data_target, temp) + meta = run_js( + shared.path_from_root("tools", "lz4-compress.js"), + shared.NODE_JS, + [shared.path_from_root("src", "mini-lz4.js"), temp, data_target], + stdout=PIPE, + ) + os.unlink(temp) + use_data = """ + var compressedData = %s; + compressedData['data'] = byteArray; + assert(typeof LZ4 === 'object', 'LZ4 not present - was your app build with -s LZ4=1 ?'); + LZ4.loadPackage({ 'metadata': metadata, 'compressedData': compressedData }); + Module['removeRunDependency']('datafile_%s'); + """ % ( + meta, + shared.JS.escape_for_js_string(data_target), + ) + + package_uuid = uuid.uuid4() + package_name = data_target + remote_package_size = os.path.getsize(package_name) + remote_package_name = os.path.basename(package_name) + ret += r""" + var PACKAGE_PATH; + if (typeof window === 'object') { + PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); + } else if (typeof location !== 'undefined') { + // worker + PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); + } else { + throw 'using preloaded data can only be done on a web page or in a web worker'; + } + var PACKAGE_NAME = '%s'; + var REMOTE_PACKAGE_BASE = '%s'; + if (typeof Module['locateFilePackage'] === 'function' && !Module['locateFile']) { + Module['locateFile'] = Module['locateFilePackage']; + err('warning: you defined Module.locateFilePackage, that has been renamed to Module.locateFile (using your locateFilePackage for now)'); + } + var REMOTE_PACKAGE_NAME = Module['locateFile'] ? Module['locateFile'](REMOTE_PACKAGE_BASE, '') : REMOTE_PACKAGE_BASE; + """ % ( + shared.JS.escape_for_js_string(data_target), + shared.JS.escape_for_js_string(remote_package_name), + ) + metadata["remote_package_size"] = remote_package_size + metadata["package_uuid"] = str(package_uuid) + ret += """ + var REMOTE_PACKAGE_SIZE = metadata['remote_package_size']; + var PACKAGE_UUID = metadata['package_uuid']; + """ + + if use_preload_cache: + code += ( + r''' + var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + var IDB_RO = "readonly"; + var IDB_RW = "readwrite"; + var DB_NAME = "''' + + indexeddb_name + + """"; + var DB_VERSION = 1; + var METADATA_STORE_NAME = 'METADATA'; + var PACKAGE_STORE_NAME = 'PACKAGES'; + function openDatabase(callback, errback) { + try { + var openRequest = indexedDB.open(DB_NAME, DB_VERSION); + } catch (e) { + return errback(e); + } + openRequest.onupgradeneeded = function(event) { + var db = event.target.result; + + if(db.objectStoreNames.contains(PACKAGE_STORE_NAME)) { + db.deleteObjectStore(PACKAGE_STORE_NAME); + } + var packages = db.createObjectStore(PACKAGE_STORE_NAME); + + if(db.objectStoreNames.contains(METADATA_STORE_NAME)) { + db.deleteObjectStore(METADATA_STORE_NAME); + } + var metadata = db.createObjectStore(METADATA_STORE_NAME); + }; + openRequest.onsuccess = function(event) { + var db = event.target.result; + callback(db); + }; + openRequest.onerror = function(error) { + errback(error); + }; + }; + + // This is needed as chromium has a limit on per-entry files in IndexedDB + // https://cs.chromium.org/chromium/src/content/renderer/indexed_db/webidbdatabase_impl.cc?type=cs&sq=package:chromium&g=0&l=177 + // https://cs.chromium.org/chromium/src/out/Debug/gen/third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h?type=cs&sq=package:chromium&g=0&l=60 + // We set the chunk size to 64MB to stay well-below the limit + var CHUNK_SIZE = 64 * 1024 * 1024; + + function cacheRemotePackage( + db, + packageName, + packageData, + packageMeta, + callback, + errback + ) { + var transactionPackages = db.transaction([PACKAGE_STORE_NAME], IDB_RW); + var packages = transactionPackages.objectStore(PACKAGE_STORE_NAME); + var chunkSliceStart = 0; + var nextChunkSliceStart = 0; + var chunkCount = Math.ceil(packageData.byteLength / CHUNK_SIZE); + var finishedChunks = 0; + for (var chunkId = 0; chunkId < chunkCount; chunkId++) { + nextChunkSliceStart += CHUNK_SIZE; + var putPackageRequest = packages.put( + packageData.slice(chunkSliceStart, nextChunkSliceStart), + 'package/' + packageName + '/' + chunkId + ); + chunkSliceStart = nextChunkSliceStart; + putPackageRequest.onsuccess = function(event) { + finishedChunks++; + if (finishedChunks == chunkCount) { + var transaction_metadata = db.transaction( + [METADATA_STORE_NAME], + IDB_RW + ); + var metadata = transaction_metadata.objectStore(METADATA_STORE_NAME); + var putMetadataRequest = metadata.put( + { + 'uuid': packageMeta.uuid, + 'chunkCount': chunkCount + }, + 'metadata/' + packageName + ); + putMetadataRequest.onsuccess = function(event) { + callback(packageData); + }; + putMetadataRequest.onerror = function(error) { + errback(error); + }; + } + }; + putPackageRequest.onerror = function(error) { + errback(error); + }; + } + } + + /* Check if there's a cached package, and if so whether it's the latest available */ + function checkCachedPackage(db, packageName, callback, errback) { + var transaction = db.transaction([METADATA_STORE_NAME], IDB_RO); + var metadata = transaction.objectStore(METADATA_STORE_NAME); + var getRequest = metadata.get('metadata/' + packageName); + getRequest.onsuccess = function(event) { + var result = event.target.result; + if (!result) { + return callback(false, null); + } else { + return callback(PACKAGE_UUID === result['uuid'], result); + } + }; + getRequest.onerror = function(error) { + errback(error); + }; + } + + function fetchCachedPackage(db, packageName, metadata, callback, errback) { + var transaction = db.transaction([PACKAGE_STORE_NAME], IDB_RO); + var packages = transaction.objectStore(PACKAGE_STORE_NAME); + + var chunksDone = 0; + var totalSize = 0; + var chunkCount = metadata['chunkCount']; + var chunks = new Array(chunkCount); + + for (var chunkId = 0; chunkId < chunkCount; chunkId++) { + var getRequest = packages.get('package/' + packageName + '/' + chunkId); + getRequest.onsuccess = function(event) { + // If there's only 1 chunk, there's nothing to concatenate it with so we can just return it now + if (chunkCount == 1) { + callback(event.target.result); + } else { + chunksDone++; + totalSize += event.target.result.byteLength; + chunks.push(event.target.result); + if (chunksDone == chunkCount) { + if (chunksDone == 1) { + callback(event.target.result); + } else { + var tempTyped = new Uint8Array(totalSize); + var byteOffset = 0; + for (var chunkId in chunks) { + var buffer = chunks[chunkId]; + tempTyped.set(new Uint8Array(buffer), byteOffset); + byteOffset += buffer.byteLength; + buffer = undefined; + } + chunks = undefined; + callback(tempTyped.buffer); + tempTyped = undefined; + } + } + } + }; + getRequest.onerror = function(error) { + errback(error); + }; + } + } + """ + ) + + ret += r""" + function fetchRemotePackage(packageName, packageSize, callback, errback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', packageName, true); + xhr.responseType = 'arraybuffer'; + xhr.onprogress = function(event) { + var url = packageName; + var size = packageSize; + if (event.total) size = event.total; + if (event.loaded) { + if (!xhr.addedTotal) { + xhr.addedTotal = true; + if (!Module.dataFileDownloads) Module.dataFileDownloads = {}; + Module.dataFileDownloads[url] = { + loaded: event.loaded, + total: size + }; + } else { + Module.dataFileDownloads[url].loaded = event.loaded; + } + var total = 0; + var loaded = 0; + var num = 0; + for (var download in Module.dataFileDownloads) { + var data = Module.dataFileDownloads[download]; + total += data.total; + loaded += data.loaded; + num++; + } + total = Math.ceil(total * Module.expectedDataFileDownloads/num); + if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')'); + } else if (!Module.dataFileDownloads) { + if (Module['setStatus']) Module['setStatus']('Downloading data...'); + } + }; + xhr.onerror = function(event) { + throw new Error("NetworkError for: " + packageName); + } + xhr.onload = function(event) { + if (xhr.status == 200 || xhr.status == 304 || xhr.status == 206 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + var packageData = xhr.response; + callback(packageData); + } else { + throw new Error(xhr.statusText + " : " + xhr.responseURL); + } + }; + xhr.send(null); + }; + + function handleError(error) { + console.error('package error:', error); + }; + """ + + code += r""" + function processPackageData(arrayBuffer) { + Module.finishedDataFileDownloads++; + assert(arrayBuffer, 'Loading data file failed.'); + assert(arrayBuffer instanceof ArrayBuffer, 'bad input to processPackageData'); + var byteArray = new Uint8Array(arrayBuffer); + var curr; + %s + }; + Module['addRunDependency']('datafile_%s'); + """ % ( + use_data, + shared.JS.escape_for_js_string(data_target), + ) + # use basename because from the browser's point of view, + # we need to find the datafile in the same dir as the html file + + code += r""" + if (!Module.preloadResults) Module.preloadResults = {}; + """ + + if use_preload_cache: + code += r""" + function preloadFallback(error) { + console.error(error); + console.error('falling back to default preload behavior'); + fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, processPackageData, handleError); + }; + + openDatabase( + function(db) { + checkCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME, + function(useCached, metadata) { + Module.preloadResults[PACKAGE_NAME] = {fromCache: useCached}; + if (useCached) { + fetchCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME, metadata, processPackageData, preloadFallback); + } else { + fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, + function(packageData) { + cacheRemotePackage(db, PACKAGE_PATH + PACKAGE_NAME, packageData, {uuid:PACKAGE_UUID}, processPackageData, + function(error) { + console.error(error); + processPackageData(packageData); + }); + } + , preloadFallback); + } + } + , preloadFallback); + } + , preloadFallback); + + if (Module['setStatus']) Module['setStatus']('Downloading...'); + """ + else: + # Not using preload cache, so we might as well start the xhr ASAP, + # potentially before JS parsing of the main codebase if it's after us. + # Only tricky bit is the fetch is async, but also when runWithFS is called + # is async, so we handle both orderings. + ret += r""" + var fetchedCallback = null; + var fetched = Module['getPreloadedPackage'] ? Module['getPreloadedPackage'](REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE) : null; + + if (!fetched) fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, function(data) { + if (fetchedCallback) { + fetchedCallback(data); + fetchedCallback = null; + } else { + fetched = data; + } + }, handleError); + """ + + code += r""" + Module.preloadResults[PACKAGE_NAME] = {fromCache: false}; + if (fetched) { + processPackageData(fetched); + fetched = null; + } else { + fetchedCallback = processPackageData; + } + """ + + ret += """ + function runWithFS() { + """ + ret += code + ret += """ + } + if (Module['calledRun']) { + runWithFS(); + } else { + if (!Module['preRun']) Module['preRun'] = []; + Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it + } + """ + + if separate_metadata: + _metadata_template = """ + Module['removeRunDependency']('%(metadata_file)s'); + } + + function runMetaWithFS() { + Module['addRunDependency']('%(metadata_file)s'); + var REMOTE_METADATA_NAME = Module['locateFile'] ? Module['locateFile']('%(metadata_file)s', '') : '%(metadata_file)s'; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4 && xhr.status === 200) { + loadPackage(JSON.parse(xhr.responseText)); + } + } + xhr.open('GET', REMOTE_METADATA_NAME, true); + xhr.overrideMimeType('application/json'); + xhr.send(null); + } + + if (Module['calledRun']) { + runMetaWithFS(); + } else { + if (!Module['preRun']) Module['preRun'] = []; + Module["preRun"].push(runMetaWithFS); + } + """ % { + "metadata_file": os.path.basename(jsoutput + ".metadata") + } + + else: + _metadata_template = """ + } + loadPackage(%s); + """ % json.dumps( + metadata + ) + + ret += ( + """%s + })(); + """ + % _metadata_template + ) + + if force or len(data_files): + if jsoutput is None: + print(ret) + else: + # Overwrite the old jsoutput file (if exists) only when its content + # differs from the current generated one, otherwise leave the file + # untouched preserving its old timestamp + if os.path.isfile(jsoutput): + with open(jsoutput) as f: + old = f.read() + if old != ret: + with open(jsoutput, "w") as f: + f.write(ret) + else: + with open(jsoutput, "w") as f: + f.write(ret) + if separate_metadata: + with open(jsoutput + ".metadata", "w") as f: + json.dump(metadata, f, separators=(",", ":")) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ports/wapy-wasm/main.c b/ports/wapy-wasm/main.c new file mode 100644 index 000000000..8effc359a --- /dev/null +++ b/ports/wapy-wasm/main.c @@ -0,0 +1,1173 @@ +#define OUTER_INIT (1) + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../wapy/core/fdfile.h" + +#include "py/compile.h" +#include "py/emitglue.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/parse.h" +#include "py/bc0.h" +// overwrite the "math" in bytecode value with plain integers +// #include "bc_as_integers.h" +#include "py/bc.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "lib/utils/pyexec.h" + + +// for mp_call_function_0 +#include "py/parsenum.h" +#include "py/compile.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objmodule.h" // <= function defined in +#include "py/objgenerator.h" +#include "py/smallint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stackctrl.h" +#include "py/gc.h" + +#define __MAIN__ (1) +#include "emscripten.h" +#undef __MAIN__ + + + + +#if 0 // OUTER_INIT + #pragma message " ============= FULL SHARED ==============" +#include "py/mpstate.h" // mp_state_ctx +mp_state_ctx_t mp_state_ctx; +//const mp_obj_type_t mp_type_SystemExit; +//const mp_obj_type_t mp_type_TypeError; +#endif + + +/* +LOGO: + https://hackernoon.com/screamin-speed-with-webassembly-b30fac90cd92 + https://www.gatherdigital.co.uk/news/2016/08/an-early-look-at-webassembly + +Python and web: + https://twitter.com/sfermigier/status/1193457847008403456 + https://www.mail-archive.com/web-sig@python.org/msg04347.html + https://bugs.python.org/issue40280 + https://discuss.python.org/t/python-in-the-browser/4248/6 + +OS ? : + https://github.com/S2E/PyKVM + https://github.com/plasma-umass/browsix + +NOGIL: + https://github.com/plasma-umass/snakefish + +THE USER POINT OF VIEW: + https://danluu.com/input-lag/ + + +BOOKMARKS: + +https://faster-cpython.readthedocs.io/implementations.html + +https://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html + +https://doc.pypy.org/en/latest/interpreter.html#introduction-and-overview + +https://github.com/stackless-dev/stackless/tree/master-slp/Stackless + +transpile/compile : + https://github.com/almarklein/wasmfun + https://github.com/pfalcon/pycopy-lib/tree/master/utokenize + https://github.com/pfalcon/ullvm_c + https://ppci.readthedocs.io/en/latest/reference/lang/python.html + https://pypi.org/project/py-ts-interfaces/ + https://github.com/numba/numba/issues/3284 + https://00f.net/2019/04/07/compiling-to-webassembly-with-llvm-and-clang/ + + https://github.com/pfalcon/awesome-python-compilers + + https://docs.python.org/3/library/inspect.html#retrieving-source-code + + https://01alchemist.com/projects/turboscript/playground/ + + + https://github.com/lark-parser/lark/blob/master/examples/python_parser.py + https://github.com/lark-parser/lark + + https://github.com/pyparsing/pyparsing + + https://wiki.python.org/moin/LanguageParsing + https://github.com/timothycrosley/jiphy + https://github.com/philhassey/tinypy + + + http://gambitscheme.org/wiki/index.php/Main_Page + + +jit: + http://llvmlite.pydata.org/en/latest/ + +interfacing: + + https://foss.heptapod.net/pypy/cffi + https://github.com/saghul/wasi-lab + +native use: + https://github.com/wasmerio/python-ext-wasm + https://pypi.org/project/wasmbind/ + +remote use: + https://pypi.org/project/jsii/ + + https://www.plynth.net/docs?id=async_await + +sixel support for remote + https://github.com/risacher/ttyx/issues/15 + + +ilyaigpetrov/ncurses-for-emscripten +ncurses 6.1 compiled by emscripten for usage in a browser. +It is compiled, loaded, but doesn't work! You are wanted to make it work! + https://github.com/ilyaigpetrov/ncurses-for-emscripten + + +------------- architecture / runtimes ------------------------- + +Stackless design? + https://github.com/micropython/micropython/issues/1036 + +Tasking for Emscripten/Wasm target #32532 + https://github.com/JuliaLang/julia/pull/32532/files#diff-15aaf3bb4e0956d73f314b03ddf85c8cR92 + +hyperdivision/async-wasm + https://github.com/hyperdivision/async-wasm/blob/master/index.js + + +wasm/gc/continuations: + https://github.com/WebAssembly/exception-handling/blob/master/proposals/Exceptions.md + https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html + https://github.com/emscripten-core/emscripten/issues/8268# + http://troubles.md/wasm-is-not-a-stack-machine/ + https://github.com/WebAssembly/design/issues/919#issuecomment-348000242 + + + https://bitbucket.org/pypy/stmgc/src/default/ + + https://github.com/WebAssembly/design/issues/1345#issuecomment-638228041 + + finalizers: thx stinos ! + https://github.com/micropython/micropython/issues/1878 + + WAPY implements C3: + https://github.com/WebAssembly/WASI/issues/276 + https://github.com/WebAssembly/design/issues/1252#issuecomment-461604032 + +Protothreads + http://dunkels.com/adam/pt/ + http://dunkels.com/adam/download/graham-pt.h + +C tools: + https://github.com/jamesmunns/bbqueue + https://github.com/willemt/bipbuffer/blob/master/bipbuffer.c + + A circular buffer written in C using Posix calls to create a contiguously mapped memory space. + https://github.com/willemt/cbuffer + + https://vstinner.readthedocs.io/c_language.html + + +ASYNC: + https://github.com/python-trio/trio/issues/79 + https://www.encode.io/httpx/async/ + https://www.python.org/dev/peps/pep-0492/ + https://www.python.org/dev/peps/pep-0525/ + https://www.pythonsheets.com/notes/python-asyncio.html + + https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ + + Re: [PATCH 09/13] aio: add support for async openat() + [Posted January 12, 2016 by corbet] + + https://lwn.net/Articles/671657/ + "In fact, if we do it well, we can go the other way, and try to +implement the nasty AIO interface on top of the generic "just do +things asynchronously". Linus + + +VM support: + https://github.com/cretz/asmble + + https://github.com/CraneStation/wasmtime + + https://nick.zoic.org/art/web-assembly-on-esp32-with-wasm-wamr/ + https://github.com/zephyrproject-rtos/zephyr/issues/21329 + + https://github.com/vshymanskyy/Wasm3_RGB_Lamp + + +pywasm: Support WASI(WebAssembly System Interface) + https://github.com/mohanson/pywasm/issues/25 + +webuse: + https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + https://makitweb.com/how-to-detect-browser-window-active-or-not-javascript/ + https://codepen.io/jonathan/full/sxgJl + +repl: + Unicode Character 'RIGHT-TO-LEFT OVERRIDE' (U+202E) + +couldclose: + https://github.com/micropython/micropython/issues/3313 + +net layer: + https://github.com/moshest/p2p-index + https://rangermauve.hashbase.io/ + +*/ + + + + + +/* +CFLAGS="-Wfatal-errors -Wall -Wextra -Wunused -Werror -Wno-format-extra-args -Wno-format-zero-length\ + -Winit-self -Wimplicit -Wimplicit-int -Wmissing-include-dirs -Wswitch-default -Wswitch-enum\ + -Wunused-parameter -Wdouble-promotion -Wchkp -Wno-coverage-mismatch -Wstrict-overflow\ + -Wformat-nonliteral -Wformat-security -Wformat-signedness -Wnonnull -Wnonnull-compare\ + -Wnull-dereference -Wignored-qualifiers -Wignored-attributes\ + -Wmain -Wpedantic -Wmisleading-indentation -Wmissing-braces -Wmissing-include-dirs\ + -Wparentheses -Wsequence-point -Wshift-overflow=2 -Wswitch -Wswitch-default -Wswitch-bool\ + -Wsync-nand -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label\ + -Wunused-parameter -Wunused-result -Wunused-variable -Wunused-const-variable=2 -Wunused-value\ + -Wuninitialized -Winvalid-memory-model -Wmaybe-uninitialized -Wstrict-aliasing=3\ + -Wsuggest-attribute=pure -Wsuggest-attribute=const\ + -Wsuggest-attribute=noreturn -Wsuggest-attribute=format -Wmissing-format-attribute\ + -Wdiv-by-zero -Wunknown-pragmas -Wbool-compare -Wduplicated-cond\ + -Wtautological-compare -Wtrampolines -Wfloat-equal -Wfree-nonheap-object -Wunsafe-loop-optimizations\ + -Wpointer-arith -Wnonnull-compare -Wtype-limits -Wcomments -Wtrigraphs -Wundef\ + -Wendif-labels -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wclobbered\ + -Wconversion -Wdate-time -Wempty-body -Wjump-misses-init -Wsign-compare -Wsign-conversion\ + -Wfloat-conversion -Wsizeof-pointer-memaccess -Wsizeof-array-argument -Wpadded -Wredundant-decls\ + -Wnested-externs -Winline -Wbool-compare -Wno-int-to-pointer-cast -Winvalid-pch -Wlong-long\ + -Wvariadic-macros -Wvarargs -Wvector-operation-performance -Wvla -Wvolatile-register-var\ + -Wpointer-sign -Wstack-protector -Woverlength-strings -Wunsuffixed-float-constants\ + -Wno-designated-init -Whsa\ + -march=x86-64 -m64 -Wformat=2 -Warray-bounds=2 -Wstack-usage=120000 -Wstrict-overflow=5 -fmax-errors=5 -g\ + -std=c99 -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=700 -pedantic-errors" +*/ + +#define DBG 0 +#define DLOPEN 0 + + +#if !MICROPY_ENABLE_PYSTACK +#error "need MICROPY_ENABLE_PYSTACK (1) +#endif + +static int SHOW_OS_LOOP=0; + +static int g_argc; +static char **g_argv; //[]; + +size_t +bsd_strlen(const char *str) { + const char *s; + for (s = str; *s; ++s); + return (s - str); +} + + +#if 0 // for esp broken SDK strcmp + + int + bsd_strcmp(const char *s1, const char *s2) { + while (*s1 == *s2++) + if (*s1++ == '\0') + return (0); + return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1)); + } + + int + endswith(const char * str, const char * suffix) { + int str_len = bsd_strlen(str); + int suffix_len = bsd_strlen(suffix); + + return + (str_len >= suffix_len) && (0 == bsd_strcmp(str + (str_len-suffix_len), suffix)); + } +#endif + +int +endswith(const char * str, const char * suffix) { + int str_len = strlen(str); + int suffix_len = strlen(suffix); + + return + (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix)); +} + + + + + +/* ===================================================================================== + bad sync experiment with file access trying to help on + https://github.com/littlevgl/lvgl/issues/792 + + status: better than nothing, but wapy can/could/must use aio_* for that. +*/ + +#include "api/wasm_file_api.c" +#include "api/wasm_import_api.c" + +static int KPANIC = 0; + + + + + + + +char **copy_argv(int argc, char *argv[]) { + // calculate the contiguous argv buffer size + int length=0; + size_t ptr_args = argc + 1; + for (int i = 0; i < argc; i++) + { + length += (bsd_strlen(argv[i]) + 1); + } + char** new_argv = (char**)malloc((ptr_args) * sizeof(char*) + length); + // copy argv into the contiguous buffer + length = 0; + for (int i = 0; i < argc; i++) + { + new_argv[i] = &(((char*)new_argv)[(ptr_args * sizeof(char*)) + length]); + strcpy(new_argv[i], argv[i]); + length += (bsd_strlen(argv[i]) + 1); + } + // insert NULL terminating ptr at the end of the ptr array + new_argv[ptr_args-1] = NULL; + return (new_argv); +} + + + +#include "upython.c" + +#include "vmsl/vmreg.h" + +#include "vmsl/vmreg.c" + +extern mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +extern mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); +extern mp_obj_t gen_instance_iternext(mp_obj_t self_in); +extern int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); + +//extern mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +//extern mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); + + +#include + +#if MICROPY_PY_SYS_SETTRACE +#error "248:duplicate symbol: mp_type_code" +/* +wasm-ld: error: duplicate symbol: mp_type_code +>>> defined in libmicropython.a(profile.o) +>>> defined in libmicropython.a(builtinevex.o) +*/ +#include "py/profile.h" +#endif + +//extern +int pyexec_friendly_repl_process_char(int c); +int pyexec_repl_repl_restart(int ret); +int handle_uncaught_exception(void); + +// entry point for implementing core vm parts in python, set via "embed" module +extern void pyv(mp_obj_t value); +extern mp_obj_t pycore(const char *fn); + + +#define io_stdin i_main.shm_stdio + +// to use kb buffer space for scripts +// if (strlen(io_stdin)>=IO_KBD){ io_stdin[IO_KBD] = 0; +#define IO_CODE_DONE { io_stdin[0] = 0; } + +#if MICROPY_PY_THREAD_GIL && MICROPY_PY_THREAD_GIL_VM_DIVISOR +// This needs to be volatile and outside the VM loop so it persists across handling +// of any exceptions. Otherwise it's possible that the VM never gives up the GIL. +volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; +#endif + + +// can't be in loop body because of EM_ASM +void Py_Init() { + +#if 0 + fprintf(stdout,"Testing STDOUT : "); + printf("\nstdout\n"); + fprintf(stdout,"done\n"); + + fprintf(stdout,"Testing STDERR : "); + fprintf(stderr,"\nstderr\n"); + fprintf(stdout,"done\n"); +#endif + + Py_Initialize(); + Py_NewInterpreter(); + + EM_ASM( { + vm.aio.plink.MAXSIZE = $0; + vm.aio.plink.shm = $1; + vm.aio.plink.io_port_kbd = $2; + vm.aio.plink.MP_IO_SIZE = $3; + console.log("aio.plink.shm=" + vm.aio.plink.shm+" +" + vm.aio.plink.MAXSIZE); + console.log("aio.plink.io_port_kbd=" + vm.aio.plink.io_port_kbd+" +"+ vm.aio.plink.MP_IO_SIZE); + window.setTimeout( vm.scripting.init_repl, 1000 ); + }, IO_KBD, shm_ptr(), &io_stdin[IO_KBD], MP_IO_SIZE); + + IO_CODE_DONE; + +} + + +static bool def_PyRun_SimpleString_is_repl = false ; +static int async_loop = 1; +static int async_state; + + +extern int emscripten_GetProcAddress(const char * name); + + +#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8_t *idx_cache) { + size_t idx = *idx_cache; + mp_obj_t key = MP_OBJ_NEW_QSTR(qst); + mp_map_elem_t *elem = NULL; + if (idx < map->alloc && map->table[idx].key == key) { + elem = &map->table[idx]; + } else { + elem = mp_map_lookup(map, key, MP_MAP_LOOKUP); + if (elem != NULL) { + *idx_cache = (elem - &map->table[0]) & 0xff; + } + } + return elem; +} +#endif + + +STATIC void stderr_print_strn2(void *env, const char *str, size_t len) { + (void)env; + mp_hal_stdout_tx_strn(str,len); + +} + +const mp_print_t mp_stderr_print2 = {NULL, stderr_print_strn2}; + +int uncaught_exception_handler(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + #if __EMSCRIPTEN__ + EM_ASM({console.log("91:SystemExit");}); + #endif + + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + +/* + if (mp_obj_is_exception_instance(exc)) { + size_t n, *values; + mp_obj_exception_get_traceback(exc, &n, &values); + if (n > 0) { + assert(n % 3 == 0); + mp_print_str(print, "Traceback (most recent call last):\n"); + for (int i = n - 3; i >= 0; i -= 3) { + #if MICROPY_ENABLE_SOURCE_LINE + mp_printf(print, " File \"%q\", line %d", values[i], (int)values[i + 1]); + #else + mp_printf(print, " File \"%q\"", values[i]); + #endif + // the block name can be NULL if it's unknown + qstr block = values[i + 2]; + if (block == MP_QSTRnull) { + mp_print_str(print, "\n"); + } else { + mp_printf(print, ", in %q\n", block); + } + } + } + } +*/ + cdbg("mp_stderr_print2->mp_obj=%p", MP_OBJ_FROM_PTR(exc) ); + pyv( mp_obj_get_type(exc) ); + pyv( MP_OBJ_FROM_PTR(exc) ); + pyv( MP_ROM_NONE ); + pycore("pyc_excepthook"); + mp_obj_print_exception(&mp_stderr_print2, MP_OBJ_FROM_PTR(exc)); + return 0; +} + +void +dump_args2(const mp_obj_t *a, size_t sz) { + fprintf(stderr,"%p: ", a); + for (size_t i = 0; i < sz; i++) { + fprintf(stderr,"%p ", a[i]); + } + fprintf(stderr,"\n"); +} + + +// this is reserved to max speed asynchronous code + +void +noint_aio_fsync() { + + if (!io_stdin[0]) + return; + + if (!endswith(io_stdin, "#aio.step\n")) + return; + + int ex=-1; + async_state = VMFLAGS_IF; + // CLI + VMFLAGS_IF = 0; + + //TODO: maybe somehow consumme kbd data for async inputs ? + //expect script to be properly async programmed and run them full speed via C stack ? + + if (async_loop) { + + if ( (async_loop = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT)) ) { + ex=0; + } else { + fprintf(stdout, "ERROR[%s]\n", io_stdin); + // ex check + ex=1; + } + + } + +// TODO: here we may able to tranform toplevel sync code to async and re eval +// WARNING: it may have side effects because could have run until async exception is caught + if (ex>=0) { + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("646: uncaught exception") + //mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + if (uncaught_exception_handler()) { + clog("651:SystemExit"); + } else { + clog("653: exception done"); + } + async_loop = 0; + } + } + IO_CODE_DONE; + // STI + VMFLAGS_IF = async_state; +} + + + +size_t +has_io() { + size_t check = strlen(io_stdin); + if (io_stdin[0] && (check != 38)) + return check; + return 0; +} + + + + +void +main_loop_or_step(void) { + #if OUTER_INIT + #pragma message "check for uncaught unwind " + crash_point = &&VM_stackmess; + #else + if (VMOP <= VMOP_INIT) { + crash_point = &&VM_stackmess; + #include "vmsl/vmwarmup.c" + return; + } + #endif + size_t iolen; + + if (VMOP>= VMOP_PAUSE) { + VMOP--; + if ( (iolen = has_io()) ) { + // we have a chance to process pending Inputs, so ... + cdbg("syscall/pause WITH IO=%zu", iolen ); + noint_aio_fsync(); + } else { + clog("syscall/pause -> io flush"); + // else just jump to flush Outputs + } + goto VM_syscall; + } + +// TODO: is it usefull ? + if ( (ENTRY_POINT != JMP_NONE) && !JUMPED_IN) { + clog("re-enter-on-entry %d => %d\n", ctx_current, CTX.pointer); + void* jump_entry; + jump_entry = ENTRY_POINT; + // Never to re-enter as this point. can only use the previous exit point. + JUMPED_IN = 1; + goto *jump_entry; + } + +// ========================================================================================== + // this call the async loop , no preemption should be allowed in there. + noint_aio_fsync(); +// ========================================================================================== + + // return to where we were going just before giving hand to host + if ( (EXIT_POINT != JMP_NONE) && JUMPED_IN) { + //cdbg("re-enter-on-exit IO=%lu", strlen(io_stdin) ); + + // was it gosub + if (JUMP_TYPE == TYPE_SUB) + RETURN; + + // was it branching + if (JUMP_TYPE == TYPE_JUMP) + COME_FROM; + } + + + +// All I/O stuff IS WRONG and should use a circular buffer. + + + if (io_stdin[0]) { + //then it is toplevel or TODO: it's sync top level ( tranpiled by aio on the heap) + def_PyRun_SimpleString_is_repl = true; +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + JUMP( def_PyRun_SimpleString, "main_loop_or_step_repl"); +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + // mark done + IO_CODE_DONE; + pyexec_repl_repl_restart(0); + def_PyRun_SimpleString_is_repl = false; + } + + + + //now flush kbd port + char *keybuf; + keybuf = shm_get_ptr( IO_KBD, 0); + // only when scripting interface is idle and repl ready + while (!KPANIC && repl_started) { + // should give a way here to discard repl events feeding "await input()" instead + int rx = keybuf[0] ; //& 0xFF; + if (rx) { + // if (rx==12) fprintf(stdout,"IO(12): Form Feed "); //clear screen + //if (rx>127) cdbg("FIXME:stdin-utf8:%u", rx ); + //pyexec_event_repl_process_char(rx); + if (pyexec_friendly_repl_process_char(rx)<0) + cdbg("REPL[%s]", io_stdin); + *keybuf++ = 0; + } else break; + } + + // always reset io buffer no matter what since io_stdin can overwrite it in script mode + keybuf[0]=0; + + + if (VMOP==VM_HCF) { +VM_stackmess: + puts("no guru meditation, bye"); + #if !ASYNCIFY + emscripten_cancel_main_loop(); + #endif + } +goto VM_syscall; + +//================================================================== + + +// def PyRun_SimpleString(const_char_p src) -> int; +def_PyRun_SimpleString: { +//return: + // should set a global integer + //int ret = 0; +//args: + char* src = i_main.shm_stdio; + mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT; + +//vars + +//code + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + + if (lex == NULL) { + clog("syntax error"); + handle_uncaught_exception(); + } else { + qstr source_name = lex->source_name; + + mp_obj_t exret = MP_OBJ_NULL; + + #if MICROPY_PY___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, def_PyRun_SimpleString_is_repl); + + + if ( module_fun != MP_OBJ_NULL) { + //STACKLESS STARTS HERE + + // unlock I/O buffer + IO_CODE_DONE; + + CTX.self_in = module_fun; + CTX.n_args = 0; + CTX.n_kw = 0; + CTX.args = NULL ; + + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + exret = MP_OBJ_NULL; + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + exret = SUBVAL; //CTX.sub_value; + } else { + exret = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + } + + if ( exret != MP_OBJ_NULL ) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + clog("645: PENDING EXCEPTION CLEARED AND RAISED"); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + } + } + + } + + // ex check + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("656: uncaught exception") + //mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + //handle_uncaught_exception(); + if (uncaught_exception_handler()) { + clog("689:SystemExit"); + } else { + clog("691: exception done"); + } + } + RETVAL = exret ; + } + + COME_FROM; +} // PyRun_SimpleString + + +#define VM_CANCEL_ACTIVE_FINALLY(sptr) do { \ + if (mp_obj_is_small_int(sptr[-1])) { \ + /* Stack: (..., prev_dest_ip, prev_cause, dest_ip) */ \ + /* Cancel the unwind through the previous finally, replace with current one */ \ + sptr[-2] = sptr[0]; \ + sptr -= 2; \ + } else { \ + assert(sptr[-1] == mp_const_none || mp_obj_is_exception_instance(sptr[-1])); \ + /* Stack: (..., None/exception, dest_ip) */ \ + /* Silence the finally's exception value (may be None or an exception) */ \ + sptr[-1] = sptr[0]; \ + --sptr; \ + } \ +} while (0) + + +#if MICROPY_PY_SYS_SETTRACE + +#define FRAME_SETUP() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_ENTER() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + CTX.code_state->prev_state = MP_STATE_THREAD(current_code_state); \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + if (!mp_prof_is_executing) { \ + mp_prof_frame_enter(CTX.code_state); \ + } \ +} while(0) + +#define FRAME_LEAVE() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state->prev_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_UPDATE() do { \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing) { \ + CTX.code_state->frame = MP_OBJ_TO_PTR(mp_prof_frame_update(CTX.code_state)); \ + } \ +} while(0) + +#define TRACE_TICK(current_ip, current_sp, is_exception) do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing && CTX.code_state->frame && MP_STATE_THREAD(prof_trace_callback)) { \ + MP_PROF_INSTR_DEBUG_PRINT(code_state->ip); \ + } \ + if (!mp_prof_is_executing && CTX.code_state->frame && CTX.code_state->frame->callback) { \ + mp_prof_instr_tick(CTX.code_state, is_exception); \ + } \ +} while(0) + +#else // MICROPY_PY_SYS_SETTRACE + +#define FRAME_SETUP() +#define FRAME_ENTER() +#define FRAME_LEAVE() +#define FRAME_UPDATE() +#define TRACE_TICK(current_ip, current_sp, is_exception) + +#endif // MICROPY_PY_SYS_SETTRACE + + +def_mp_call_function_n_kw: { + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + RETVAL = SUBVAL; //CTX.sub_value; + } else { +#if VMTRACE +clog(" 899: native call"); +#endif + RETVAL = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + clog("919:def_mp_call_function_n_kw ex!"); + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + RETVAL = MP_OBJ_NULL; + } + + RETURN; +} + + +#define VM_DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ + { \ + n_state_out_var = mp_decode_uint_value(bytecode); \ + size_t n_exc_stack = mp_decode_uint_value(mp_decode_uint_skip(bytecode)); \ + \ + state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + + n_exc_stack * sizeof(mp_exc_stack_t); \ + } + + + + +/* NOT OK + + when a function has tuple with default value init style the state_size calculated by + VM_DECODE_CODESTATE_SIZE is just huge and wrong. + +*/ + +//266:objfun.c +def_func_bc_call: { + + RETVAL = MP_OBJ_NULL; + + if (MP_STACK_CHECK()) { + clog("974:def_func_bc_call: MP_STACK_CHECK ex!"); + goto def_func_bc_call_ret; + } + + CTX.self_fun = MP_OBJ_TO_PTR(CTX.self_in); + + VM_DECODE_CODESTATE_SIZE(CTX.self_fun->bytecode, CTX.n_state, CTX.state_size); + + + // allocate state for locals and stack + // new frame == new code state. + if ( CTX.state_size > 41360 ) { +#define TRACE_ON (1) + #include "vmsl/vmbc_trace.c" +#undef TRACE_ON + cdbg("882:BUG: =======> start=%p cur=%p end=%p state_size=%ld", + MP_STATE_THREAD(pystack_start), MP_STATE_THREAD(pystack_cur), MP_STATE_THREAD(pystack_end) + , CTX.state_size); + +#pragma message "Silly workaround for func with tuple init" +#if 0 + if (CTX.code_state = fun_bc_call_pre(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args) ){ +#if 1 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, MP_OBJ_NULL); +#else + ctx_get_next(CTX_COPY); + NEXT.inject_exc = MP_OBJ_NULL; + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; +#endif + RETVAL = fun_bc_call_past(CTX.code_state, CTX.vm_return_kind, CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); +#endif + RETVAL = fun_bc_call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + goto def_func_bc_call_ret; + } + + CTX.code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + CTX.state_size); + + if (!CTX.code_state) { + clog("908:def_func_bc_call: MP_PYSTACK_ALLOC ex!"); + goto def_func_bc_call_ret; + } + + CTX.inject_exc = MP_OBJ_NULL ; + CTX.code_state->fun_bc = CTX.self_fun; + CTX.code_state->ip = 0; + CTX.code_state->n_state = CTX.n_state; + + clog("917:TODO: can we save old_globals before this call ?"); + + mp_obj_t ret = mp_setup_code_state(CTX.code_state, CTX.n_args, CTX.n_kw, CTX.args); + + //? + CTX.code_state->old_globals = mp_globals_get(); + + + if ( ret == MP_OBJ_NULL) { + clog("999:def_func_bc_call: INIT_CODESTATE ex!"); + mp_nonlocal_free(CTX.code_state, sizeof(mp_code_state_t)); + goto def_func_bc_call_ret; + } + + ctx_get_next(CTX_COPY); + + // execute the byte code with the correct globals context + mp_globals_set(NEXT.self_fun->globals); + + + if (VMFLAGS_IF>0) { // FIXED ! + clog("132:unwrap.c ALLOWINT def_func_bc_call->def_mp_execute_bytecode"); + + // ip sp would not be set on NEXT + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; + + } else { + clog("136:unwrap.c NOINTERRUPT"); + ctx_abort(); //128 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, CTX.inject_exc); + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + CTX.sub_value= *CTX.code_state->sp; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + CTX.sub_value= CTX.code_state->state[0]; + else + clog("1031:unwrap.c unrouted .vm_return_kind") + } + + } + + mp_globals_set(CTX.code_state->old_globals); + mp_pystack_free(CTX.code_state); + + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + #error "[...]" + #endif + +//353:objfun.c + // work in done juste before def_mp_execute_bytecode RETURN + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + RETVAL = CTX.sub_value; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + RETVAL = mp_raise_o(CTX.sub_value); + else + clog("1050:unwrap.c unrouted .sub_vm_return_kind") + } + + +def_func_bc_call_ret: + RETURN; +} + + +#include "vmsl/unwrap.c" + + +//================================================================== +// VM_syscall_verbose:; +// puts("-syscall-"); + +VM_syscall:; +// TODO: flush all at once + // STDOUT flush before eventually filling it again + if (!rbb_is_empty(&out_rbb)) { + // flush stdout + unsigned char out_c = 0; + printf("{\"%c\":\"",49); + //TODO put a 0 at end and printf buffer directly + while (rbb_pop(&out_rbb, &out_c)) + printf("%c", out_c ); + printf("\"}\n"); + } +} // main_loop_or_step + + +void +main_loop_warmup(void) { + #include "vmsl/vmwarmup.c" +} // main_loop_warmup + + +//*************************************************************************************** + + + + + +int PyArg_ParseTuple(PyObject *argv, const char *fmt, ...) { + va_list argptr; + va_start (argptr, fmt ); + vfprintf(stdout,fmt,argptr); + va_end (argptr); + return 0; +} + + +#include + +void * +import(const char * LIB_NAME) { + void *lib_handle = dlopen(LIB_NAME, RTLD_NOW | RTLD_GLOBAL); + + if (!lib_handle) + fprintf(stderr,"\n\nDL ======> cannot load %s module\n\n\n", LIB_NAME); + else + fprintf(stderr,"\n\nDL ======> %s module OK !!!!! \n\n\n", LIB_NAME); + return lib_handle; +} + + +int +main(int argc, char *argv[]) { + + g_argc = argc; + g_argv = copy_argv(argc, argv); + + //assert(emscripten_run_preload_plugins(LIB_NAME, NULL, NULL) == 0); + + void *lib_wapy = import( "libwapy.so"); + void *lib_sdl2 = import( "libsdl2.so"); + + +#if !ASYNCIFY + #if OUTER_INIT + // this one creates an "uncaught unwind" ex + emscripten_set_main_loop( main_loop_warmup, 0, 1); // <= this will exit to js now. + #else + emscripten_set_main_loop( main_loop_or_step, 0, 1); + #endif +#else + while (!KPANIC) { + emscripten_sleep(1); + main_loop_or_step(); + } +#endif + puts("no guru meditation"); + return 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// + + diff --git a/ports/wapy-wasm/mod/ffi/ffi.c b/ports/wapy-wasm/mod/ffi/ffi.c new file mode 100644 index 000000000..b8509041e --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi.c @@ -0,0 +1,218 @@ +#ifdef __EMSCRIPTEN__ + +#include "mod/ffi/ffi.h" +#include "ffi_common.h" +#include +#include + +#include "emscripten.h" + + +/* + + +https://groups.google.com/forum/#!topic/emscripten-discuss/cE3hUV3fDSw + +https://github.com/emscripten-core/emscripten/wiki/Linking + +https://github.com/dgym/cpython-emscripten/pull/1 + + +#FIXME: + +ffi_prep_closure_loc BROKEN +ffi_closure_alloc BROKEN + + +*/ + + + + +//===================================================================================================== +// following is a ripoff from https://github.com/brion/libffi/tree/emscripten-work +// + +ffi_status FFI_HIDDEN +ffi_prep_cif_machdep(ffi_cif *cif) +{ + return FFI_OK; +} + +void +ffi_call (ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue) +{ + EM_ASM_ ({ + var cif = $0; + var fn = $1; + var rvalue = $2; + var avalue = $3; + + var cif_abi = HEAPU32[cif >> 2]; + var cif_nargs = HEAPU32[(cif + 4) >> 2]; + var cif_arg_types = HEAPU32[(cif + 8) >> 2]; + var cif_rtype = HEAPU32[(cif + 12) >> 2]; + + // emscripten/wasm function pointers are indirected through a table, + // which is further subdivided by function signature -- each pointer + // must be added to a constant to find its real sig. This doesn't seem + // to be a strict wasm requirement, but is a leftover from asm.js + // which used separate arrays for each signature. + // + // This is neatly encapsulated into dynCall_* wrapper functions for us, + // which take a function pointer as first parameter and pass the rest on. + // + // Generate an emscripten call signature and look up the correct wrapper + // via name here... + var args = [fn]; + var sig = ""; + var sigFloat = Module['usingWasm'] ? "f" : "d"; + + var rtype = HEAPU16[(cif_rtype + 6 /* rtype->type*/ ) >> 1]; + //console.error('rtype is', rtype); + if (rtype === /* FFI_TYPE_VOID */ 0) { + sig = 'v'; + } else if (rtype === /* FFI_TYPE_INT */ 1 || + rtype === /* FFI_TYPE_UINT8 */ 5 || + rtype === /* FFI_TYPE_SINT8 */ 6 || + rtype === /* FFI_TYPE_UINT16 */ 7 || + rtype === /* FFI_TYPE_SINT16 */ 8 || + rtype === /* FFI_TYPE_UINT32 */ 9 || + rtype === /* FFI_TYPE_SINT32 */ 10 || + rtype === /* FFI_TYPE_POINTER */ 14) { + sig = 'i'; + } else if (rtype === /* FFI_TYPE_FLOAT */ 2) { + sig = sigFloat; + } else if (rtype === /* FFI_TYPE_DOUBLE */ 3 || + rtype === /* FFI_TYPE_LONGDOUBLE */ 4) { + sig = 'd'; + } else if (rtype === /* FFI_TYPE_UINT64 */ 11 || + rtype === /* FFI_TYPE_SINT64 */ 12) { + // Warning: returns a truncated 32-bit integer directly. + // High bits are in $tempRet0 + sig = 'j'; + } else if (rtype === /* FFI_TYPE_STRUCT */ 13) { + throw new Error('struct ret marshalling nyi'); + } else if (rtype === /* FFI_TYPE_COMPLEX */ 15) { + throw new Error('complex ret marshalling nyi'); + } else { + throw new Error('Unexpected rtype ' + rtype); + } + + for (var i = 0; i < cif_nargs; i++) { + var ptr = HEAPU32[(avalue >> 2) + i]; + + var arg_type = HEAPU32[(cif_arg_types >> 2) + i]; + var typ = HEAPU16[(arg_type + 6) >> 1]; + + if (typ === /* FFI_TYPE_INT*/ 1 || typ === /* FFI_TYPE_SINT32 */ 10) { + args.push(HEAP32[ptr >> 2]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_FLOAT */ 2) { + args.push(HEAPF32[ptr >> 2]); + sig += sigFloat; + } else if (typ === /* FFI_TYPE_DOUBLE */ 3 || typ === /* FFI_TYPE_LONGDOUBLE */ 4) { + args.push(HEAPF64[ptr >> 3]); + sig += 'd'; + } else if (typ === /* FFI_TYPE_UINT8*/ 5) { + args.push(HEAPU8[ptr]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_SINT8 */ 6) { + args.push(HEAP8[ptr]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT16 */ 7) { + args.push(HEAPU16[ptr >> 1]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_SINT16 */ 8) { + args.push(HEAP16[ptr >> 1]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT32 */ 9 || typ === /* FFI_TYPE_POINTER */ 14) { + args.push(HEAPU32[ptr >> 2]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT64 */ 11 || typ === /* FFI_TYPE_SINT64 */ 12) { + // LEGALIZE_JS_FFI mode splits i64 (j) into two i32 args + // for compatibility with JavaScript's f64-based numbers. + args.push(HEAPU32[ptr >> 2]); + args.push(HEAPU32[(ptr + 4) >> 2]); + sig += 'j'; + } else if (typ === /* FFI_TYPE_STRUCT */ 13) { + throw new Error('struct marshalling nyi'); + } else if (typ === /* FFI_TYPE_COMPLEX */ 15) { + throw new Error('complex marshalling nyi'); + } else { + throw new Error('Unexpected type ' + typ); + } + } + + //console.error('fn is',fn); + //console.error('sig is',sig); + var func = Module['dynCall_' + sig]; + //console.error('func is', func); + //console.error('args is', args); + if (func) { + var result = func.apply(null, args); + } else { + console.error('fn is', fn); + console.error('sig is', sig); + console.error('args is', args); + for (var x in Module) { + console.error('-- ' + x); + } + throw new Error('invalid function pointer in ffi_call'); + } + //console.error('result is',result); + + if (rtype === 0) { + // void + } else if (rtype === 1 || rtype === 9 || rtype === 10 || rtype === 14) { + HEAP32[rvalue >> 2] = result; + } else if (rtype === 2) { + HEAPF32[rvalue >> 2] = result; + } else if (rtype === 3 || rtype === 4) { + HEAPF64[rvalue >> 3] = result; + } else if (rtype === 5 || rtype === 6) { + HEAP8[rvalue] = result; + } else if (rtype === 7 || rtype === 8) { + HEAP16[rvalue >> 1] = result; + } else if (rtype === 11 || rtype === 12) { + // Warning: returns a truncated 32-bit integer directly. + // High bits are in $tempRet0 + HEAP32[rvalue >> 2] = result; + HEAP32[(rvalue + 4) >> 2] = Module.getTempRet0(); + } else if (rtype === 13) { + throw new Error('struct ret marshalling nyi'); + } else if (rtype === 15) { + throw new Error('complex ret marshalling nyi'); + } else { + throw new Error('Unexpected rtype ' + rtype); + } + }, cif, fn, rvalue, avalue); +} +#else + +#include "ffi.h" + +#endif + + +ffi_status +ffi_prep_closure_loc (ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, void**, void*), + void *user_data, + void *codeloc) +{ + closure->cif = cif; + closure->fun = fun; + closure->user_data = user_data; + return FFI_OK; +} + + + +void * +ffi_closure_alloc (size_t size, void **code) +{ + return 0; +} + diff --git a/ports/wapy-wasm/mod/ffi/ffi.h b/ports/wapy-wasm/mod/ffi/ffi.h new file mode 100644 index 000000000..580430539 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi.h @@ -0,0 +1,481 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 3.3-rc0 - Copyright (c) 2011, 2014 Anthony Green + - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. + + 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. + + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + Most of the API is documented in doc/libffi.texi. + + The raw API is designed to bypass some of the argument packing and + unpacking on architectures for which it can be avoided. Routines + are provided to emulate the raw API if the underlying platform + doesn't allow faster implementation. + + More details on the raw API can be found in: + + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + + and + + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + + + +/* Specify which architecture libffi is configured for. */ +#ifndef X86 +#define X86 +#endif + +// Begin : ============================================================================ + +// ?? +typedef unsigned long ffi_arg; +typedef signed long ffi_sarg; + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + FFI_WASM32, // "raw", no structures or varargs + FFI_WASM32_EMSCRIPTEN, // structures, varargs, and split 64-bit params + FFI_LAST_ABI, +#ifdef EMSCRIPTEN + FFI_DEFAULT_ABI = FFI_WASM32_EMSCRIPTEN +#else + FFI_DEFAULT_ABI = FFI_WASM32 +#endif +} ffi_abi; + +#define FFI_CLOSURES 1 //PMPP +#define FFI_GO_CLOSURES 0 +#define FFI_TRAMPOLINE_SIZE 24 +#define FFI_NATIVE_RAW_API 0 + +// End : ============================================================================= + + + + +#ifndef LIBFFI_ASM + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ + +#define FFI_64_BIT_MAX 9223372036854775807 + +#ifdef LONG_LONG_MAX + #define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else + # ifdef LLONG_MAX + # define FFI_LONG_LONG_MAX LLONG_MAX + # ifdef _AIX52 /* or newer has C99 LLONG_MAX */ + # undef FFI_64_BIT_MAX + # define FFI_64_BIT_MAX 9223372036854775807LL + # endif /* _AIX52 or newer */ + # else + # ifdef __GNUC__ + # define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ + # endif + # endif +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t can hold a pointer. */ + +typedef struct _ffi_type +{ + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type **elements; +} ffi_type; + +# define FFI_API + +/* The externally visible type declarations also need the MSVC DLL + decorations, or they will not be exported from the object file. */ +#if defined LIBFFI_HIDE_BASIC_TYPES +# define FFI_EXTERN FFI_API +#else +# define FFI_EXTERN extern FFI_API +#endif + +#ifndef LIBFFI_HIDE_BASIC_TYPES +#if SCHAR_MAX == 127 +# define ffi_type_uchar ffi_type_uint8 +# define ffi_type_schar ffi_type_sint8 +#else + #error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else + #error "short size not supported" +#endif + +#if INT_MAX == 32767 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else + #error "int size not supported" +#endif + +#if LONG_MAX == 2147483647 +# if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX + #error "no 64-bit data type supported" +# endif +#elif LONG_MAX != FFI_64_BIT_MAX + #error "long size not supported" +#endif + +#if LONG_MAX == 2147483647 +# define ffi_type_ulong ffi_type_uint32 +# define ffi_type_slong ffi_type_sint32 +#elif LONG_MAX == FFI_64_BIT_MAX +# define ffi_type_ulong ffi_type_uint64 +# define ffi_type_slong ffi_type_sint64 +#else + #error "long size not supported" +#endif + +/* These are defined in ffi_types.c. */ +FFI_EXTERN ffi_type ffi_type_void; +FFI_EXTERN ffi_type ffi_type_uint8; +FFI_EXTERN ffi_type ffi_type_sint8; +FFI_EXTERN ffi_type ffi_type_uint16; +FFI_EXTERN ffi_type ffi_type_sint16; +FFI_EXTERN ffi_type ffi_type_uint32; +FFI_EXTERN ffi_type ffi_type_sint32; +FFI_EXTERN ffi_type ffi_type_uint64; +FFI_EXTERN ffi_type ffi_type_sint64; +FFI_EXTERN ffi_type ffi_type_float; +FFI_EXTERN ffi_type ffi_type_double; +FFI_EXTERN ffi_type ffi_type_pointer; + +#define ffi_type_longdouble ffi_type_double + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_EXTERN ffi_type ffi_type_complex_float; +FFI_EXTERN ffi_type ffi_type_complex_double; +#if 0 +FFI_EXTERN ffi_type ffi_type_complex_longdouble; +#else +#define ffi_type_complex_longdouble ffi_type_complex_double +#endif +#endif +#endif /* LIBFFI_HIDE_BASIC_TYPES */ + +typedef enum { + FFI_OK = 0, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI +} ffi_status; + +typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type **arg_types; + ffi_type *rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifndef FFI_SIZEOF_ARG +# if LONG_MAX == 2147483647 +# define FFI_SIZEOF_ARG 4 +# elif LONG_MAX == FFI_64_BIT_MAX +# define FFI_SIZEOF_ARG 8 +# endif +#endif + +#ifndef FFI_SIZEOF_JAVA_RAW +# define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 +/* This is a special case for mips64/n32 ABI (and perhaps others) where + sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ +typedef union { + signed int sint; + unsigned int uint; + float flt; + char data[FFI_SIZEOF_JAVA_RAW]; + void* ptr; +} ffi_java_raw; +#else +typedef ffi_raw ffi_java_raw; +#endif + + +FFI_API +void ffi_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_raw *avalue); + +FFI_API void ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +FFI_API void ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +FFI_API size_t ffi_raw_size (ffi_cif *cif); + +/* This is analogous to the raw API, except it uses Java parameter + packing, even on 64-bit machines. I.e. on 64-bit machines longs + and doubles are followed by an empty 64-bit word. */ + +FFI_API +void ffi_java_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_java_raw *avalue); + +FFI_API +void ffi_java_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_java_raw *raw); +FFI_API +void ffi_java_raw_to_ptrarray (ffi_cif *cif, ffi_java_raw *raw, void **args); +FFI_API +size_t ffi_java_raw_size (ffi_cif *cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#if FFI_CLOSURES + +#ifdef _MSC_VER +__declspec(align(8)) +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + void (*fun)(ffi_cif*,void*,void**,void*); + void *user_data; +} ffi_closure +#ifdef __GNUC__ + __attribute__((aligned (8))) +#endif + ; + +#ifndef __GNUC__ +# ifdef __sgi +# pragma pack 0 +# endif +#endif + +FFI_API void *ffi_closure_alloc (size_t size, void **code); +FFI_API void ffi_closure_free (void *); + +FFI_API ffi_status +ffi_prep_closure (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data) +#if defined(__GNUC__) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 405) + __attribute__((deprecated ("use ffi_prep_closure_loc instead"))) +#elif defined(__GNUC__) && __GNUC__ >= 3 + __attribute__((deprecated)) +#endif + ; + +FFI_API ffi_status +ffi_prep_closure_loc (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void*codeloc); + +#ifdef __sgi +# pragma pack 8 +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* If this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_raw*,void*); + void *user_data; + +} ffi_raw_closure; + +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* If this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the translation, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*); + void *user_data; + +} ffi_java_raw_closure; + +FFI_API ffi_status +ffi_prep_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +FFI_API ffi_status +ffi_prep_raw_closure_loc (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data, + void *codeloc); + +FFI_API ffi_status +ffi_prep_java_raw_closure (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data); + +FFI_API ffi_status +ffi_prep_java_raw_closure_loc (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data, + void *codeloc); + +#endif /* FFI_CLOSURES */ + + +/* ---- Public interface definition -------------------------------------- */ + +FFI_API +ffi_status ffi_prep_cif(ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, + ffi_type **atypes); + +FFI_API +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +FFI_API +void ffi_call(ffi_cif *cif, + void (*fn)(void), + void *rvalue, + void **avalue); + +FFI_API +ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, + size_t *offsets); + +/* Useful for eliminating compiler warnings. */ +#define FFI_FN(f) ((void (*)(void))f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 0 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 +#define FFI_TYPE_COMPLEX 15 + +/* This should always refer to the last type code (for sanity checks). */ +#define FFI_TYPE_LAST FFI_TYPE_COMPLEX + + +#endif diff --git a/ports/wapy-wasm/mod/ffi/ffi_common.h b/ports/wapy-wasm/mod/ffi/ffi_common.h new file mode 100644 index 000000000..727f39e65 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi_common.h @@ -0,0 +1,149 @@ +/* ----------------------------------------------------------------------- + ffi_common.h - Copyright (C) 2011, 2012, 2013 Anthony Green + Copyright (C) 2007 Free Software Foundation, Inc + Copyright (c) 1996 Red Hat, Inc. + + Common internal definitions and macros. Only necessary for building + libffi. + ----------------------------------------------------------------------- */ + +#ifndef FFI_COMMON_H +#define FFI_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Do not move this. Some versions of AIX are very picky about where + this is positioned. */ +#ifdef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else + /* mingw64 defines this already in malloc.h. */ +# ifndef alloca +# define alloca __builtin_alloca +# endif +# endif +# define MAYBE_UNUSED __attribute__((__unused__)) +#else +# define MAYBE_UNUSED +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX +# pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +# ifdef _MSC_VER +# define alloca _alloca +# else +char *alloca (); +# endif +# endif +# endif +# endif +#endif + +/* Check for the existence of memcpy. */ +#if STDC_HEADERS +# include +#else +# ifndef HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#if defined(FFI_DEBUG) +#include +#endif + +#ifdef FFI_DEBUG +void ffi_assert(char *expr, char *file, int line); +void ffi_stop_here(void); +void ffi_type_test(ffi_type *a, char *file, int line); + +#define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__,__LINE__)) +#define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l))) +#define FFI_ASSERT_VALID_TYPE(x) ffi_type_test (x, __FILE__, __LINE__) +#else +#define FFI_ASSERT(x) +#define FFI_ASSERT_AT(x, f, l) +#define FFI_ASSERT_VALID_TYPE(x) +#endif + +/* v cast to size_t and aligned up to a multiple of a */ +#define FFI_ALIGN(v, a) (((((size_t) (v))-1) | ((a)-1))+1) +/* v cast to size_t and aligned down to a multiple of a */ +#define FFI_ALIGN_DOWN(v, a) (((size_t) (v)) & -a) + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif *cif); +ffi_status ffi_prep_cif_machdep_var(ffi_cif *cif, + unsigned int nfixedargs, unsigned int ntotalargs); + + +#if HAVE_LONG_DOUBLE_VARIANT +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types (ffi_abi abi); +#endif + +/* Used internally, but overridden by some architectures */ +ffi_status ffi_prep_cif_core(ffi_cif *cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +/* Extended cif, used in callback from assembly routine */ +typedef struct +{ + ffi_cif *cif; + void *rvalue; + void **avalue; +} extended_cif; + +/* Terse sized type definitions. */ +#if defined(_MSC_VER) || defined(__sgi) || defined(__SUNPRO_C) +typedef unsigned char UINT8; +typedef signed char SINT8; +typedef unsigned short UINT16; +typedef signed short SINT16; +typedef unsigned int UINT32; +typedef signed int SINT32; +# ifdef _MSC_VER +typedef unsigned __int64 UINT64; +typedef signed __int64 SINT64; +# else +# include +typedef uint64_t UINT64; +typedef int64_t SINT64; +# endif +#else +typedef unsigned int UINT8 __attribute__((__mode__(__QI__))); +typedef signed int SINT8 __attribute__((__mode__(__QI__))); +typedef unsigned int UINT16 __attribute__((__mode__(__HI__))); +typedef signed int SINT16 __attribute__((__mode__(__HI__))); +typedef unsigned int UINT32 __attribute__((__mode__(__SI__))); +typedef signed int SINT32 __attribute__((__mode__(__SI__))); +typedef unsigned int UINT64 __attribute__((__mode__(__DI__))); +typedef signed int SINT64 __attribute__((__mode__(__DI__))); +#endif + +typedef float FLOAT32; + +#ifndef __GNUC__ +#define __builtin_expect(x, expected_value) (x) +#endif +#define LIKELY(x) __builtin_expect(!!(x),1) +#define UNLIKELY(x) __builtin_expect((x)!=0,0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/wapy-wasm/mod/ffi/fficonfig.h b/ports/wapy-wasm/mod/ffi/fficonfig.h new file mode 100644 index 000000000..13a80dd63 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/fficonfig.h @@ -0,0 +1,215 @@ +/* fficonfig.h. Generated from fficonfig.h.in by configure. */ +/* fficonfig.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +/* #undef CRAY_STACKSEG_END */ + +/* Define to 1 if using `alloca.c'. */ +/* #undef C_ALLOCA */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +#define EH_FRAME_FLAGS "aw" + +/* Define this if you want extra debugging. */ +/* #undef FFI_DEBUG */ + +/* Cannot use PROT_EXEC on this target, so, we revert to alternative means */ +/* #undef FFI_EXEC_TRAMPOLINE_TABLE */ + +/* Define this if you want to enable pax emulated trampolines */ +/* #undef FFI_MMAP_EXEC_EMUTRAMP_PAX */ + +/* Cannot use malloc on this target, so, we revert to alternative means */ +/* #undef FFI_MMAP_EXEC_WRIT */ + +/* Define this if you do not want support for the raw API. */ +/* #undef FFI_NO_RAW_API */ + +/* Define this if you do not want support for aggregate types. */ +/* #undef FFI_NO_STRUCTS */ + +/* Define to 1 if you have `alloca', as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define if your assembler supports .cfi_* directives. */ +#define HAVE_AS_CFI_PSEUDO_OP 1 + +/* Define if your assembler supports .register. */ +/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ + +/* Define if the compiler uses zarch features. */ +/* #undef HAVE_AS_S390_ZARCH */ + +/* Define if your assembler and linker support unaligned PC relative relocs. + */ +/* #undef HAVE_AS_SPARC_UA_PCREL */ + +/* Define if your assembler supports unwind section type. */ +/* #undef HAVE_AS_X86_64_UNWIND_SECTION_TYPE */ + +/* Define if your assembler supports PC relative relocs. */ +/* #undef HAVE_AS_X86_PCREL */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if __attribute__((visibility("hidden"))) is supported. */ +/* #undef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the long double type and it is bigger than a double */ +/* #undef HAVE_LONG_DOUBLE */ + +/* Define if you support more than one size of the long double type */ +/* #undef HAVE_LONG_DOUBLE_VARIANT */ + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkostemp' function. */ +#define HAVE_MKOSTEMP 1 + +/* Define to 1 if you have the `mmap' function. */ +#define HAVE_MMAP 1 + +/* Define if mmap with MAP_ANON(YMOUS) works. */ +#define HAVE_MMAP_ANON 1 + +/* Define if mmap of /dev/zero works. */ +#define HAVE_MMAP_DEV_ZERO 1 + +/* Define if read-only mmap of a plain file works. */ +#define HAVE_MMAP_FILE 1 + +/* Define if .eh_frame sections should be read-only. */ +/* #undef HAVE_RO_EH_FRAME */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if GNU symbol versioning is used for libatomic. */ +/* #undef LIBFFI_GNU_SYMBOL_VERSIONING */ + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libffi" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "http://github.com/libffi/libffi/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libffi" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libffi 3.3-rc0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libffi" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.3-rc0" + +/* The size of `double', as computed by sizeof. */ +#define SIZEOF_DOUBLE 8 + +/* The size of `long double', as computed by sizeof. */ +#define SIZEOF_LONG_DOUBLE 8 + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T 4 + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +/* #undef STACK_DIRECTION */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if symbols are underscored. */ +/* #undef SYMBOL_UNDERSCORE */ + +/* Define this if you are using Purify and want to suppress spurious messages. + */ +/* #undef USING_PURIFY */ + +/* Version number of package */ +#define VERSION "3.3-rc0" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + + +#ifdef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE +#ifdef LIBFFI_ASM +#ifdef __APPLE__ +#define FFI_HIDDEN(name) .private_extern name +#else +#define FFI_HIDDEN(name) .hidden name +#endif +#else +#define FFI_HIDDEN __attribute__ ((visibility ("hidden"))) +#endif +#else +#ifdef LIBFFI_ASM +#define FFI_HIDDEN(name) +#else +#define FFI_HIDDEN +#endif +#endif + diff --git a/ports/wapy-wasm/mod/ffi/prep_cif.c b/ports/wapy-wasm/mod/ffi/prep_cif.c new file mode 100644 index 000000000..9e1197000 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/prep_cif.c @@ -0,0 +1,261 @@ +/* ----------------------------------------------------------------------- + prep_cif.c - Copyright (c) 2011, 2012 Anthony Green + Copyright (c) 1996, 1998, 2007 Red Hat, Inc. + + 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 + +/* Round up to FFI_SIZEOF_ARG. */ + +#define STACK_ARG_SIZE(x) FFI_ALIGN(x, FFI_SIZEOF_ARG) + +/* Perform machine independent initialization of aggregate type + specifications. */ + +static ffi_status initialize_aggregate(ffi_type *arg, size_t *offsets) +{ + ffi_type **ptr; + + if (UNLIKELY(arg == NULL || arg->elements == NULL)) + return FFI_BAD_TYPEDEF; + + arg->size = 0; + arg->alignment = 0; + + ptr = &(arg->elements[0]); + + if (UNLIKELY(ptr == 0)) + return FFI_BAD_TYPEDEF; + + while ((*ptr) != NULL) + { + if (UNLIKELY(((*ptr)->size == 0) + && (initialize_aggregate((*ptr), NULL) != FFI_OK))) + return FFI_BAD_TYPEDEF; + + /* Perform a sanity check on the argument type */ + FFI_ASSERT_VALID_TYPE(*ptr); + + arg->size = FFI_ALIGN(arg->size, (*ptr)->alignment); + if (offsets) + *offsets++ = arg->size; + arg->size += (*ptr)->size; + + arg->alignment = (arg->alignment > (*ptr)->alignment) ? + arg->alignment : (*ptr)->alignment; + + ptr++; + } + + /* Structure size includes tail padding. This is important for + structures that fit in one register on ABIs like the PowerPC64 + Linux ABI that right justify small structs in a register. + It's also needed for nested structure layout, for example + struct A { long a; char b; }; struct B { struct A x; char y; }; + should find y at an offset of 2*sizeof(long) and result in a + total size of 3*sizeof(long). */ + arg->size = FFI_ALIGN (arg->size, arg->alignment); + + /* On some targets, the ABI defines that structures have an additional + alignment beyond the "natural" one based on their elements. */ +#ifdef FFI_AGGREGATE_ALIGNMENT + if (FFI_AGGREGATE_ALIGNMENT > arg->alignment) + arg->alignment = FFI_AGGREGATE_ALIGNMENT; +#endif + + if (arg->size == 0) + return FFI_BAD_TYPEDEF; + else + return FFI_OK; +} + +#ifndef __CRIS__ +/* The CRIS ABI specifies structure elements to have byte + alignment only, so it completely overrides this functions, + which assumes "natural" alignment and padding. */ + +/* Perform machine independent ffi_cif preparation, then call + machine dependent routine. */ + +/* For non variadic functions isvariadic should be 0 and + nfixedargs==ntotalargs. + + For variadic calls, isvariadic should be 1 and nfixedargs + and ntotalargs set as appropriate. nfixedargs must always be >=1 */ + + +ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, ffi_type **atypes) +{ + unsigned bytes = 0; + unsigned int i; + ffi_type **ptr; + + FFI_ASSERT(cif != NULL); + FFI_ASSERT((!isvariadic) || (nfixedargs >= 1)); + FFI_ASSERT(nfixedargs <= ntotalargs); + + if (! (abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) + return FFI_BAD_ABI; + + cif->abi = abi; + cif->arg_types = atypes; + cif->nargs = ntotalargs; + cif->rtype = rtype; + + cif->flags = 0; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types (abi); +#endif + + /* Initialize the return type if necessary */ + if ((cif->rtype->size == 0) + && (initialize_aggregate(cif->rtype, NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if (rtype->type == FFI_TYPE_COMPLEX) + abort(); +#endif + /* Perform a sanity check on the return type */ + FFI_ASSERT_VALID_TYPE(cif->rtype); + + /* x86, x86-64 and s390 stack space allocation is handled in prep_machdep. */ +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + /* Make space for the return structure pointer */ + if (cif->rtype->type == FFI_TYPE_STRUCT +#ifdef TILE + && (cif->rtype->size > 10 * FFI_SIZEOF_ARG) +#endif +#ifdef XTENSA + && (cif->rtype->size > 16) +#endif +#ifdef NIOS2 + && (cif->rtype->size > 8) +#endif + ) + bytes = STACK_ARG_SIZE(sizeof(void*)); +#endif + + for (ptr = cif->arg_types, i = cif->nargs; i > 0; i--, ptr++) + { + + /* Initialize any uninitialized aggregate type definitions */ + if (((*ptr)->size == 0) + && (initialize_aggregate((*ptr), NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if ((*ptr)->type == FFI_TYPE_COMPLEX) + abort(); +#endif + /* Perform a sanity check on the argument type, do this + check after the initialization. */ + FFI_ASSERT_VALID_TYPE(*ptr); + +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + { + /* Add any padding if necessary */ + if (((*ptr)->alignment - 1) & bytes) + bytes = (unsigned)FFI_ALIGN(bytes, (*ptr)->alignment); + +#ifdef TILE + if (bytes < 10 * FFI_SIZEOF_ARG && + bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG) + { + /* An argument is never split between the 10 parameter + registers and the stack. */ + bytes = 10 * FFI_SIZEOF_ARG; + } +#endif +#ifdef XTENSA + if (bytes <= 6*4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6*4) + bytes = 6*4; +#endif + + bytes += STACK_ARG_SIZE((*ptr)->size); + } +#endif + } + + cif->bytes = bytes; + + /* Perform machine dependent cif processing */ +#ifdef FFI_TARGET_SPECIFIC_VARIADIC + if (isvariadic) + return ffi_prep_cif_machdep_var(cif, nfixedargs, ntotalargs); +#endif + + return ffi_prep_cif_machdep(cif); +} +#endif /* not __CRIS__ */ + +ffi_status ffi_prep_cif(ffi_cif *cif, ffi_abi abi, unsigned int nargs, + ffi_type *rtype, ffi_type **atypes) +{ + return ffi_prep_cif_core(cif, abi, 0, nargs, nargs, rtype, atypes); +} + +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes) +{ + return ffi_prep_cif_core(cif, abi, 1, nfixedargs, ntotalargs, rtype, atypes); +} + +#if FFI_CLOSURES + +ffi_status +ffi_prep_closure (ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data) +{ + return ffi_prep_closure_loc (closure, cif, fun, user_data, closure); +} + +#endif + +ffi_status +ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets) +{ + if (! (abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) + return FFI_BAD_ABI; + if (struct_type->type != FFI_TYPE_STRUCT) + return FFI_BAD_TYPEDEF; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types (abi); +#endif + + return initialize_aggregate(struct_type, offsets); +} diff --git a/ports/wapy-wasm/mod/ffi/types.c b/ports/wapy-wasm/mod/ffi/types.c new file mode 100644 index 000000000..c86c73574 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/types.c @@ -0,0 +1,108 @@ +/* ----------------------------------------------------------------------- + types.c - Copyright (c) 1996, 1998 Red Hat, Inc. + + Predefined ffi_types needed by libffi. + + 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. + ----------------------------------------------------------------------- */ + +/* Hide the basic type definitions from the header file, so that we + can redefine them here as "const". */ +#define LIBFFI_HIDE_BASIC_TYPES + +#include "ffi.h" +#include "ffi_common.h" + +/* Type definitions */ + +#define FFI_TYPEDEF(name, type, id, maybe_const)\ +struct struct_align_##name { \ + char c; \ + type x; \ +}; \ +FFI_EXTERN \ +maybe_const ffi_type ffi_type_##name = { \ + sizeof(type), \ + offsetof(struct struct_align_##name, x), \ + id, NULL \ +} + +#define FFI_COMPLEX_TYPEDEF(name, type, maybe_const) \ +static ffi_type *ffi_elements_complex_##name [2] = { \ + (ffi_type *)(&ffi_type_##name), NULL \ +}; \ +struct struct_align_complex_##name { \ + char c; \ + _Complex type x; \ +}; \ +FFI_EXTERN \ +maybe_const ffi_type ffi_type_complex_##name = { \ + sizeof(_Complex type), \ + offsetof(struct struct_align_complex_##name, x), \ + FFI_TYPE_COMPLEX, \ + (ffi_type **)ffi_elements_complex_##name \ +} + +/* Size and alignment are fake here. They must not be 0. */ +FFI_EXTERN const ffi_type ffi_type_void = { + 1, 1, FFI_TYPE_VOID, NULL +}; + +FFI_TYPEDEF(uint8, UINT8, FFI_TYPE_UINT8, const); +FFI_TYPEDEF(sint8, SINT8, FFI_TYPE_SINT8, const); +FFI_TYPEDEF(uint16, UINT16, FFI_TYPE_UINT16, const); +FFI_TYPEDEF(sint16, SINT16, FFI_TYPE_SINT16, const); +FFI_TYPEDEF(uint32, UINT32, FFI_TYPE_UINT32, const); +FFI_TYPEDEF(sint32, SINT32, FFI_TYPE_SINT32, const); +FFI_TYPEDEF(uint64, UINT64, FFI_TYPE_UINT64, const); +FFI_TYPEDEF(sint64, SINT64, FFI_TYPE_SINT64, const); + +FFI_TYPEDEF(pointer, void*, FFI_TYPE_POINTER, const); + +FFI_TYPEDEF(float, float, FFI_TYPE_FLOAT, const); +FFI_TYPEDEF(double, double, FFI_TYPE_DOUBLE, const); + +#if !defined HAVE_LONG_DOUBLE_VARIANT || defined __alpha__ +#define FFI_LDBL_CONST const +#else +#define FFI_LDBL_CONST +#endif + +#ifdef __alpha__ +/* Even if we're not configured to default to 128-bit long double, + maintain binary compatibility, as -mlong-double-128 can be used + at any time. */ +/* Validate the hard-coded number below. */ +# if defined(__LONG_DOUBLE_128__) && FFI_TYPE_LONGDOUBLE != 4 +# error FFI_TYPE_LONGDOUBLE out of date +# endif +const ffi_type ffi_type_longdouble = { 16, 16, 4, NULL }; +#elif FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_TYPEDEF(longdouble, long double, FFI_TYPE_LONGDOUBLE, FFI_LDBL_CONST); +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_COMPLEX_TYPEDEF(float, float, const); +FFI_COMPLEX_TYPEDEF(double, double, const); +#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_COMPLEX_TYPEDEF(longdouble, long double, FFI_LDBL_CONST); +#endif +#endif diff --git a/ports/wapy-wasm/mod/modffi.c b/ports/wapy-wasm/mod/modffi.c new file mode 100644 index 000000000..59f03557d --- /dev/null +++ b/ports/wapy-wasm/mod/modffi.c @@ -0,0 +1,515 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 + +#if __EMSCRIPTEN__ + #include +#else + #include "ffi.h" +#endif + +#include + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/mperrno.h" + +/* + * modffi uses character codes to encode a value type, based on "struct" + * module type codes, with some extensions and overridings. + * + * Extra/overridden typecodes: + * v - void, can be used only as return type + * P - const void*, pointer to read-only memory + * p - void*, meaning pointer to a writable memory (note that this + * clashes with struct's "p" as "Pascal string"). + * s - as argument, the same as "p", as return value, causes string + * to be allocated and returned, instead of pointer value. + * O - mp_obj_t, passed as is (mostly useful as a callback param) + * + * TODO: + * C - callback function + * + * Note: all constraint specified by typecode can be not enforced at this time, + * but may be later. + */ + +typedef struct _mp_obj_opaque_t { + mp_obj_base_t base; + void *val; +} mp_obj_opaque_t; + +typedef struct _mp_obj_ffimod_t { + mp_obj_base_t base; + void *handle; +} mp_obj_ffimod_t; + +typedef struct _mp_obj_ffivar_t { + mp_obj_base_t base; + void *var; + char type; +// ffi_type *type; +} mp_obj_ffivar_t; + +typedef struct _mp_obj_ffifunc_t { + mp_obj_base_t base; + void *func; + char rettype; + const char *argtypes; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_ffifunc_t; + +typedef struct _mp_obj_fficallback_t { + mp_obj_base_t base; + void *func; + ffi_closure *clo; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_fficallback_t; + +//STATIC const mp_obj_type_t opaque_type; +STATIC const mp_obj_type_t ffimod_type; +STATIC const mp_obj_type_t ffifunc_type; +STATIC const mp_obj_type_t fficallback_type; +STATIC const mp_obj_type_t ffivar_type; + +STATIC ffi_type *char2ffi_type(char c) +{ + switch (c) { + case 'b': return &ffi_type_schar; + case 'B': return &ffi_type_uchar; + case 'h': return &ffi_type_sshort; + case 'H': return &ffi_type_ushort; + case 'i': return &ffi_type_sint; + case 'I': return &ffi_type_uint; + case 'l': return &ffi_type_slong; + case 'L': return &ffi_type_ulong; + case 'q': return &ffi_type_sint64; + case 'Q': return &ffi_type_uint64; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': return &ffi_type_float; + case 'd': return &ffi_type_double; + #endif + case 'O': // mp_obj_t + case 'C': // (*)() + case 'P': // const void* + case 'p': // void* + case 's': return &ffi_type_pointer; + case 'v': return &ffi_type_void; + default: return NULL; + } +} + +STATIC ffi_type *get_ffi_type(mp_obj_t o_in) +{ + if (MP_OBJ_IS_STR(o_in)) { + const char *s = mp_obj_str_get_str(o_in); + ffi_type *t = char2ffi_type(*s); + if (t != NULL) { + return t; + } + } + // TODO: Support actual libffi type objects + + mp_raise_TypeError("Unknown type"); +} + +STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) +{ + switch (type) { + case 's': { + const char *s = (const char *)(intptr_t)val; + if (!s) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); + } + case 'v': + return mp_const_none; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': { + union { ffi_arg ffi; float flt; } val_union = { .ffi = val }; + return mp_obj_new_float(val_union.flt); + } + case 'd': { + double *p = (double*)&val; + return mp_obj_new_float(*p); + } + #endif + case 'O': + return (mp_obj_t)(intptr_t)val; + default: + return mp_obj_new_int(val); + } +} + +// FFI module + +STATIC void ffimod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->handle); +} + +STATIC mp_obj_t ffimod_close(mp_obj_t self_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + dlclose(self->handle); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ffimod_close_obj, ffimod_close); + +STATIC mp_obj_t make_func(mp_obj_t rettype_in, void *func, mp_obj_t argtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + const char *argtypes = mp_obj_str_get_str(argtypes_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(argtypes_in)); + mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type*, nparams); + o->base.type = &ffifunc_type; + + o->func = func; + o->rettype = *rettype; + o->argtypes = argtypes; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(argtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + mp_raise_ValueError("Error in ffi_prep_cif"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t ffimod_func(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(args[0]); + const char *symname = mp_obj_str_get_str(args[2]); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + } + return make_func(args[1], sym, args[3]); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ffimod_func_obj, 4, 4, ffimod_func); + +STATIC mp_obj_t mod_ffi_func(mp_obj_t rettype, mp_obj_t addr_in, mp_obj_t argtypes) { + void *addr = (void*)MP_OBJ_TO_PTR(mp_obj_int_get_truncated(addr_in)); + return make_func(rettype, addr, argtypes); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_func_obj, mod_ffi_func); + + +STATIC void call_py_func(ffi_cif *cif, void *ret, void** args, void *func) { + mp_obj_t pyargs[cif->nargs]; + for (uint i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(mp_int_t*)args[i]); + } + mp_obj_t res = mp_call_function_n_kw(MP_OBJ_FROM_PTR(func), cif->nargs, 0, pyargs); + + if (res != mp_const_none) { + *(ffi_arg*)ret = mp_obj_int_get_truncated(res); + } +} + + +STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); + mp_obj_fficallback_t *o = m_new_obj_var(mp_obj_fficallback_t, ffi_type*, nparams); + o->base.type = &fficallback_type; + + o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); + + o->rettype = *rettype; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(paramtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + mp_raise_ValueError("Error in ffi_prep_cif"); + } +/* #FIXME: PMPP */ + 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("ffi_prep_closure_loc"); + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, mod_ffi_callback); + +STATIC mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *rettype = mp_obj_str_get_str(vartype_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + return MP_OBJ_NULL; + } + mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t); + o->base.type = &ffivar_type; + + o->var = sym; + o->type = *rettype; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(ffimod_var_obj, ffimod_var); + +STATIC mp_obj_t ffimod_addr(mp_obj_t self_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + } + return mp_obj_new_int((uintptr_t)sym); +} +MP_DEFINE_CONST_FUN_OBJ_2(ffimod_addr_obj, ffimod_addr); + +STATIC mp_obj_t ffimod_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_args; + (void)n_kw; + + const char *fname = NULL; + if (args[0] != mp_const_none) { + fname = mp_obj_str_get_str(args[0]); + } + void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL); + + if (mod == NULL) { + mp_raise_OSError(errno); + } + mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t); + o->base.type = type; + o->handle = mod; + return MP_OBJ_FROM_PTR(o); +} + +STATIC const mp_rom_map_elem_t ffimod_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&ffimod_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_var), MP_ROM_PTR(&ffimod_var_obj) }, + { MP_ROM_QSTR(MP_QSTR_addr), MP_ROM_PTR(&ffimod_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&ffimod_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffimod_locals_dict, ffimod_locals_dict_table); + +STATIC const mp_obj_type_t ffimod_type = { + { &mp_type_type }, + .name = MP_QSTR_ffimod, + .print = ffimod_print, + .make_new = ffimod_make_new, + .locals_dict = (mp_obj_dict_t*)&ffimod_locals_dict, +}; + +// FFI function + +STATIC void ffifunc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + assert(n_kw == 0); + assert(n_args == self->cif.nargs); + + ffi_arg values[n_args]; + void *valueptrs[n_args]; + const char *argtype = self->argtypes; + for (uint i = 0; i < n_args; i++, argtype++) { + mp_obj_t a = args[i]; + if (*argtype == 'O') { + values[i] = (ffi_arg)(intptr_t)a; + #if MICROPY_PY_BUILTINS_FLOAT + } else if (*argtype == 'f') { + float *p = (float*)&values[i]; + *p = mp_obj_get_float(a); + } else if (*argtype == 'd') { + double *p = (double*)&values[i]; + *p = mp_obj_get_float(a); + #endif + } else if (a == mp_const_none) { + values[i] = 0; + } else if (MP_OBJ_IS_INT(a)) { + values[i] = mp_obj_int_get_truncated(a); + } else if (MP_OBJ_IS_STR(a)) { + const char *s = mp_obj_str_get_str(a); + values[i] = (ffi_arg)(intptr_t)s; + } else if (((mp_obj_base_t*)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { + mp_obj_base_t *o = (mp_obj_base_t*)MP_OBJ_TO_PTR(a); + mp_buffer_info_t bufinfo; + int ret = o->type->buffer_p.get_buffer(MP_OBJ_FROM_PTR(o), &bufinfo, MP_BUFFER_READ); // TODO: MP_BUFFER_READ? + if (ret != 0) { + goto error; + } + values[i] = (ffi_arg)(intptr_t)bufinfo.buf; + } else if (MP_OBJ_IS_TYPE(a, &fficallback_type)) { + mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); + values[i] = (ffi_arg)(intptr_t)p->func; + } else { + goto error; + } + valueptrs[i] = &values[i]; + } + + // If ffi_arg is not big enough to hold a double, then we must pass along a + // pointer to a memory location of the correct size. + // TODO check if this needs to be done for other types which don't fit into + // ffi_arg. + #if MICROPY_PY_BUILTINS_FLOAT + if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { + double retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return mp_obj_new_float(retval); + } else + #endif + { + ffi_arg retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(retval, self->rettype); + } + +error: + mp_raise_TypeError("Don't know how to pass object to native function"); +} + +STATIC const mp_obj_type_t ffifunc_type = { + { &mp_type_type }, + .name = MP_QSTR_ffifunc, + .print = ffifunc_print, + .call = ffifunc_call, +}; + +// FFI callback for Python function + +STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fficallback_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC const mp_obj_type_t fficallback_type = { + { &mp_type_type }, + .name = MP_QSTR_fficallback, + .print = fficallback_print, +}; + +// FFI variable + +STATIC void ffivar_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", self->var, *(int*)self->var); +} + +STATIC mp_obj_t ffivar_get(mp_obj_t self_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + return mp_binary_get_val_array(self->type, self->var, 0); +} +MP_DEFINE_CONST_FUN_OBJ_1(ffivar_get_obj, ffivar_get); + +STATIC mp_obj_t ffivar_set(mp_obj_t self_in, mp_obj_t val_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + mp_binary_set_val_array(self->type, self->var, 0, val_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(ffivar_set_obj, ffivar_set); + +STATIC const mp_rom_map_elem_t ffivar_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffivar_locals_dict, ffivar_locals_dict_table); + +STATIC const mp_obj_type_t ffivar_type = { + { &mp_type_type }, + .name = MP_QSTR_ffivar, + .print = ffivar_print, + .locals_dict = (mp_obj_dict_t*)&ffivar_locals_dict, +}; + +// Generic opaque storage object (unused) + +/* +STATIC const mp_obj_type_t opaque_type = { + { &mp_type_type }, + .name = MP_QSTR_opaqueval, +// .print = opaque_print, +}; +*/ + +STATIC mp_obj_t mod_ffi_open(size_t n_args, const mp_obj_t *args) { + return ffimod_make_new(&ffimod_type, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ffi_open_obj, 1, 2, mod_ffi_open); + +STATIC mp_obj_t mod_ffi_as_bytearray(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get_truncated(size), (void*)(uintptr_t)mp_obj_int_get_truncated(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_ffi_as_bytearray_obj, mod_ffi_as_bytearray); + +STATIC const mp_rom_map_elem_t mp_module_ffi_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ffi) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_ffi_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&mod_ffi_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&mod_ffi_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_as_bytearray), MP_ROM_PTR(&mod_ffi_as_bytearray_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ffi_globals, mp_module_ffi_globals_table); + +const mp_obj_module_t mp_module_ffi = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ffi_globals, +}; diff --git a/ports/wapy-wasm/mod/modos.c b/ports/wapy-wasm/mod/modos.c new file mode 100644 index 000000000..268138f13 --- /dev/null +++ b/ports/wapy-wasm/mod/modos.c @@ -0,0 +1,254 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 "py/mpconfig.h" + +#include "py/runtime.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "extmod/vfs.h" +#include "extmod/misc.h" + +#ifdef __ANDROID__ +#define USE_STATFS 1 +#endif + +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); + + 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_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); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); + +#if MICROPY_PY_OS_STATVFS + +#if USE_STATFS +#include +#define STRUCT_STATVFS struct statfs +#define STATVFS statfs +#define F_FAVAIL sb.f_ffree +#define F_NAMEMAX sb.f_namelen +#define F_FLAG sb.f_flags +#else +#include +#define STRUCT_STATVFS struct statvfs +#define STATVFS statvfs +#define F_FAVAIL sb.f_favail +#define F_NAMEMAX sb.f_namemax +#define F_FLAG sb.f_flag +#endif + +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); + + 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); + t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.f_blocks); + t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.f_bfree); + t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.f_bavail); + t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.f_files); + t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.f_ffree); + t->items[7] = MP_OBJ_NEW_SMALL_INT(F_FAVAIL); + t->items[8] = MP_OBJ_NEW_SMALL_INT(F_FLAG); + t->items[9] = MP_OBJ_NEW_SMALL_INT(F_NAMEMAX); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_statvfs_obj, mod_os_statvfs); +#endif + +STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + // Note that POSIX requires remove() to be able to delete a directory + // too (act as rmdir()). This is POSIX extenstion to ANSI C semantics + // 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. + int r = unlink(path); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_remove_obj, mod_os_remove); + +STATIC mp_obj_t mod_os_system(mp_obj_t cmd_in) { + const char *cmd = mp_obj_str_get_str(cmd_in); +#if __EMSCRIPTEN__ || __WASM__ + int r = 1; +#else + int r = system(cmd); +#endif + RAISE_ERRNO(r, errno); + + return MP_OBJ_NEW_SMALL_INT(r); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_system_obj, mod_os_system); + +STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) { + const char *s = getenv(mp_obj_str_get_str(var_in)); + if (s == NULL) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_getenv_obj, mod_os_getenv); + +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); + #ifdef _WIN32 + int r = mkdir(path); + #else + int r = mkdir(path, 0777); + #endif + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_mkdir_obj, mod_os_mkdir); + +typedef struct _mp_obj_listdir_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + DIR *dir; +} mp_obj_listdir_t; + +STATIC mp_obj_t listdir_next(mp_obj_t self_in) { + mp_obj_listdir_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->dir == NULL) { + goto done; + } + struct dirent *dirent = readdir(self->dir); + if (dirent == NULL) { + closedir(self->dir); + self->dir = NULL; + done: + return MP_OBJ_STOP_ITERATION; + } + + 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)); + + #ifdef _DIRENT_HAVE_D_TYPE + #ifdef DTTOIF + t->items[1] = MP_OBJ_NEW_SMALL_INT(DTTOIF(dirent->d_type)); + #else + if (dirent->d_type == DT_DIR) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); + } else if (dirent->d_type == DT_REG) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG); + } else { + t->items[1] = MP_OBJ_NEW_SMALL_INT(dirent->d_type); + } + #endif + #else + // DT_UNKNOWN should have 0 value on any reasonable system + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); + #endif + + #ifdef _DIRENT_HAVE_D_INO + t->items[2] = MP_OBJ_NEW_SMALL_INT(dirent->d_ino); + #else + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); + #endif + return MP_OBJ_FROM_PTR(t); +} + +STATIC mp_obj_t mod_os_ilistdir(size_t n_args, const mp_obj_t *args) { + const char *path = "."; + if (n_args > 0) { + path = mp_obj_str_get_str(args[0]); + } + mp_obj_listdir_t *o = m_new_obj(mp_obj_listdir_t); + o->base.type = &mp_type_polymorph_iter; + o->dir = opendir(path); + o->iternext = listdir_next; + return MP_OBJ_FROM_PTR(o); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_ilistdir_obj, 0, 1, mod_os_ilistdir); + +STATIC mp_obj_t mod_os_errno(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return MP_OBJ_NEW_SMALL_INT(errno); + } + + errno = mp_obj_get_int(args[0]); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj, 0, 1, mod_os_errno); + +STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mod_os_stat_obj) }, + #if MICROPY_PY_OS_STATVFS + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mod_os_statvfs_obj) }, + #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_getenv), MP_ROM_PTR(&mod_os_getenv_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) }, + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_os_globals, mp_module_os_globals_table); + +const mp_obj_module_t mp_module_os = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_os_globals, +}; diff --git a/ports/wapy-wasm/mod/modtime.c b/ports/wapy-wasm/mod/modtime.c new file mode 100644 index 000000000..9514b70f4 --- /dev/null +++ b/ports/wapy-wasm/mod/modtime.c @@ -0,0 +1,182 @@ +/* + * 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 "py/mpconfig.h" + + +#include +#include +#include +#include +#include +#include + +#include "py/runtime.h" +#include "py/smallint.h" +#include "py/mphal.h" +#include "extmod/utime_mphal.h" + +#include + + + +// mingw32 defines CLOCKS_PER_SEC as ((clock_t)) but preprocessor does not handle casts +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) +#define MP_REMOVE_BRACKETSA(x) +#define MP_REMOVE_BRACKETSB(x) MP_REMOVE_BRACKETSA x +#define MP_REMOVE_BRACKETSC(x) MP_REMOVE_BRACKETSB x +#define MP_CLOCKS_PER_SEC MP_REMOVE_BRACKETSC(CLOCKS_PER_SEC) +#else +#define MP_CLOCKS_PER_SEC CLOCKS_PER_SEC +#endif + +#if defined(MP_CLOCKS_PER_SEC) +#define CLOCK_DIV (MP_CLOCKS_PER_SEC / 1000.0F) +#else +#error Unsupported clock() implementation +#endif + + +// Note: this is deprecated since CPy3.3, but pystone still uses it. +STATIC mp_obj_t +mod_time_clock(void) { +#if MICROPY_PY_BUILTINS_FLOAT + // 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((float)(clock() / 1000) / CLOCK_DIV); +#else + return mp_obj_new_int((mp_int_t)clock()); +#endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_clock_obj, mod_time_clock); + + +STATIC mp_obj_t mod_time_time(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + +#if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = tv.tv_sec + (mp_float_t)tv.tv_usec / 1000000; + return mp_obj_new_float(val); +#else + return mp_obj_new_int_from_uint( tv.tv_sec ); +#endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_obj, mod_time_time); + +STATIC mp_obj_t mod_time_time_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return mp_obj_new_int_from_uint( ts.tv_nsec ); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_ns_obj, mod_time_time_ns); + +STATIC +mp_obj_t mod_time_sleep(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_obj, mod_time_sleep); + + +STATIC +mp_obj_t mod_time_sleep_ms(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep_ms\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_ms_obj, mod_time_sleep_ms); + +STATIC +mp_obj_t mod_time_sleep_us(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep_us\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_us_obj, mod_time_sleep_us); + + + +STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { + time_t t; + if (n_args == 0) { + t = time(NULL); + } else { + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(args[0]); + t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val); + #else + t = mp_obj_get_int(args[0]); + #endif + } + struct tm *tm = localtime(&t); + + mp_obj_t ret = mp_obj_new_tuple(9, NULL); + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(ret); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(tm->tm_year + 1900); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(tm->tm_mon + 1); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(tm->tm_mday); + tuple->items[3] = MP_OBJ_NEW_SMALL_INT(tm->tm_hour); + tuple->items[4] = MP_OBJ_NEW_SMALL_INT(tm->tm_min); + tuple->items[5] = MP_OBJ_NEW_SMALL_INT(tm->tm_sec); + int wday = tm->tm_wday - 1; + if (wday < 0) { + wday = 6; + } + tuple->items[6] = MP_OBJ_NEW_SMALL_INT(wday); + tuple->items[7] = MP_OBJ_NEW_SMALL_INT(tm->tm_yday + 1); + tuple->items[8] = MP_OBJ_NEW_SMALL_INT(tm->tm_isdst); + + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_localtime_obj, 0, 1, mod_time_localtime); + +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) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mod_time_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mod_time_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mod_time_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mod_time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mod_time_time_ns_obj) }, + { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, + { 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) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_time_globals, mp_module_time_globals_table); + +const mp_obj_module_t mp_module_time = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_time_globals, +}; + diff --git a/ports/wapy-wasm/mod/modules.h b/ports/wapy-wasm/mod/modules.h new file mode 100644 index 000000000..8b7362eba --- /dev/null +++ b/ports/wapy-wasm/mod/modules.h @@ -0,0 +1,13 @@ +#ifndef INCLUDE_MODULES_H +#define INCLUDE_MODULES_H 1 + +//extern const struct _mp_obj_module_t mp_module_embed; + + +#define MODULES_LINKS +// { MP_OBJ_NEW_QSTR(MP_QSTR_embed), MP_ROM_PTR(&mp_module_embed) }, + + +#endif + +//EOF diff --git a/ports/wapy-wasm/mod/moduos_vfs.c b/ports/wapy-wasm/mod/moduos_vfs.c new file mode 100644 index 000000000..e9ac8e1f8 --- /dev/null +++ b/ports/wapy-wasm/mod/moduos_vfs.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "extmod/vfs_fat.h" + +#if MICROPY_VFS + +// 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_system_obj); + +STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos_vfs) }, + { MP_ROM_QSTR(MP_QSTR_sep), MP_ROM_QSTR(MP_QSTR__slash_) }, + + { 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_system), MP_ROM_PTR(&mod_os_system_obj) }, + + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, + + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename),MP_ROM_PTR(&mp_vfs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove + + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif + + #if MICROPY_VFS_POSIX + { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, + #endif + #if MICROPY_VFS_FAT + { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(uos_vfs_module_globals, uos_vfs_module_globals_table); + +const mp_obj_module_t mp_module_uos_vfs = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&uos_vfs_module_globals, +}; + +#endif // MICROPY_VFS diff --git a/ports/wapy-wasm/mpconfigport.h b/ports/wapy-wasm/mpconfigport.h new file mode 100644 index 000000000..eea7a0f2c --- /dev/null +++ b/ports/wapy-wasm/mpconfigport.h @@ -0,0 +1,496 @@ +#define MODULES_LINKS + +#include "dev_conf.h" + +#if __DEV__ +#define MICROPY_VM_HOOK_BC 1 + +#ifdef STATIC + #undef STATIC +#endif + +#define STATIC // mpconfig.h:1402 => bad pointer cast and bad linking +#endif + + +#define MICROPY_ROM_TEXT_COMPRESSION (0) +#define MICROPY_PY_FSTRING (1) + +#undef NO_NLR +#define NO_NLR (1) +#define WAPY (1) + + +#define False false +#define True true + + +#define mp_hal_ticks_cpu() 0 + +#ifdef MICROPY_OBJ_IMMEDIATE_OBJS +//https://github.com/pfalcon/pycopy/commit/2a362af5bcdeaadd94ddac7f90ea33092a7ac7be +#undef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (0) +#endif + +// options to control how Micro Python is built +#define MICROPY_QSTR_BYTES_IN_HASH (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +//just in case of long urls and a local cache ? +#define MICROPY_ALLOC_PATH_MAX (1024) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) + +#if 0 + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) +#else + //#pragma message "build/frozen_mpy.c:7:2: error: 'incompatible MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE'" + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (0) + +#define MICROPY_MODULE_GETATTR (1) +#define MICROPY_MODULE_SPECIAL_METHODS (1) + + +#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool + +#define MICROPY_PY_BUILTINS_FROZENSET (1) + +#define MICROPY_LONGINT_IMPL_MPZ (2) +#if 1 + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#else + #pragma message "build/frozen_mpy.c:12:2: error: 'incompatible MICROPY_LONGINT_IMPL'" + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) +#endif + +#define MPZ_DIG_SIZE (16) + +#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_SYS_EXC_INFO (0) // <============================ NON STANDARD PY2 + +#define MICROPY_PERSISTENT_CODE (1) +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_PERSISTENT_CODE_SAVE (1) +//#define MICROPY_EMIT_MACHINE_CODE (1) + + +#define MICROPY_HAS_FILE_READER (1) +#define MICROPY_HELPER_LEXER_UNIX (1) + +#define MICROPY_READER_POSIX (0) +#define MICROPY_VFS_POSIX (0) + +#define HAVE_mp_raw_code_save_file (1) + +#define MICROPY_EMIT_WASM (1) + +//MICROPY_EMIT_INLINE_ASM ((MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) +//#define MICROPY_EMIT_NATIVE + +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) + + +#if 1 +// 0 0 0 0 ! RuntimeError: memory access out of bounds +// 0 0 0 1 ! silent crash +// 0 0 1 0 = 11.15 Kpy/s + #define MICROPY_STACKLESS (0) + #define MICROPY_STACKLESS_STRICT (0) + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#else +// 1 0 1 1 = 10.6 Kpy/s +// 1 0 1 0 = 10.8 K +// 1 1 1 0 = 10.6 +// 1 1 1 1 = 10.6 + #define MICROPY_STACKLESS (1) + #define MICROPY_STACKLESS_STRICT (1) // <=============== 1! or too much collect + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#endif + +// nlr.h MICROPY_NLR_* must match a supported arch +// define or autodetect will fail to select WASM + +#if __WASM__ +#else +#define MICROPY_NLR_SETJMP (1) +#endif +#define MICROPY_DYNAMIC_COMPILER (0) + +#if MICROPY_NLR_SETJMP + // can't have MICROPY_NLR_SETJMP==MICROPY_DYNAMIC_COMPILER==0 + #define MICROPY_NLR_OS_WINDOWS (0) + #define MICROPY_NLR_X86 (0) + #define MICROPY_NLR_X64 (0) +#endif + +// mpconfig +#define MICROPY_PY_GENERATOR_PEND_THROW (1) // def 1 +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) // def 0 + + +// MEMORY +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GCREGS_SETJMP (1) +#define MICROPY_GC_ALLOC_THRESHOLD (1) + + +//like ESP/UNIX +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) + + +#if MICROPY_PY_LVGL +#define MICROPY_MEM_STATS (0) +#else +#define MICROPY_MEM_STATS (1) +#endif + +#if MICROPY_MEM_STATS +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#endif + + +#define MICROPY_KBD_EXCEPTION (1) + + +//?? +#define MICROPY_USE_INTERNAL_ERRNO (0) + +#define MICROPY_USE_READLINE (0) +#define MICROPY_PY_BUILTINS_INPUT (1) + + +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#define MICROPY_PY_MATH_FACTORIAL (1) + + +#define MICROPY_COMP_MODULE_CONST (1) + +#if 0 + #define MICROPY_COMP_CONST (0) + #define MICROPY_PY_SYS_SETTRACE (1) +#else + #define MICROPY_COMP_CONST (1) + #define MICROPY_PY_SYS_SETTRACE (0) +#endif +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) + +#define MICROPY_ENABLE_SOURCE_LINE (1) + +#define MICROPY_HELPER_REPL (1) + +#define MICROPY_PY___FILE__ (1) + +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) + +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ASYNC_AWAIT (1) +#define MICROPY_PY_ATTRTUPLE (1) + +#define MICROPY_PY_BTREE (0) + +#define MICROPY_PY_BUILTINS_COMPLEX (1) // required +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_EXECFILE (1) // <============================ NON STANDARD PY2 +#define MICROPY_PY_BUILTINS_FILTER (1) +//#define MICROPY_PY_BUILTINS_FLOAT (1) because MICROPY_FLOAT_IMPL_DOUBLE +#define MICROPY_PY_BUILTINS_HELP_MODULES (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) // <=============== next2 make next() compat with cpython +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + //#error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) + +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_CMATH (1) + +//? TEST THAT THING ! +#ifndef MICROPY_PY_FFI +#define MICROPY_PY_FFI (1) // <========================== NON STANDARD NOT CPY +#endif + +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_MODULES (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_PLATFORM "wasm" + +// IO is cooked and multiplexed via mp_hal_stdout_tx_strn in file.c +// so do not modify those +// https://github.com/python/cpython/blob/v3.7.3/Python/bltinmodule.c#L1849-L1932 + + +#define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_PY_IO (1) // <=============== only 1 for wasm port +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_IO_BYTESIO (1) +#define MICROPY_PY_IO_BUFFEREDWRITER (1) + +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_SYS_STDFILES (1) + +#if __EMSCRIPTEN__ + #define MICROPY_PY_OS_DUPTERM (1) // <==== or js console will get C stdout too ( C-OUT [spam] ) +#else + #if __ANDROID__ + #define MICROPY_PY_OS_DUPTERM (0) + #else + #define MICROPY_PY_OS_DUPTERM (1) + #endif +#endif + + +#define MICROPY_PY_THREAD (0) +#define MICROPY_PY_THREAD_GIL (0) +#define MICROPY_PY_TIME (0) + +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UERRNO_ERRORCODE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UHASHLIB (1) + +//F +#define MICROPY_PY_UHASHLIB_SHA1 (0) + +#define MICROPY_PY_UHASHLIB_SHA256 (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_UOS (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_SUB (1) + + +//TODO: need #define MICROPY_EVENT_POLL_HOOK + select_select on io demultiplexer. +//F +#define MICROPY_PY_USELECT (0) + + +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UZLIB (1) + +#define MICROPY_VFS (0) + +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_MPY_CODE_SAVE (1) +#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_ENABLE_DOC_STRING (1) + + +//asyncio implemented +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_SCHEDULER_DEPTH (4) + + +// type definitions for the specific machine + +#define BYTES_PER_WORD (4) + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void*)((mp_uint_t)(p) | 1)) + +// This port is intended to be 32-bit, but unfortunately, int32_t for +// different targets may be defined in different ways - either as int +// or as long. This requires different printf formatting specifiers +// to print such value. So, we avoid int32_t and use int directly. +#define UINT_FMT "%u" +#define INT_FMT "%d" +typedef int mp_int_t; // must be pointer size +typedef unsigned mp_uint_t; // must be pointer size + +typedef long mp_off_t; + +#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +//#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn(str, len) + + +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_io; +extern const struct _mp_obj_module_t mp_module_os; + +#if MICROPY_PY_UOS_VFS + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), (mp_obj_t)&mp_module_os }, +#endif + +#if MICROPY_PY_FFI + extern const struct _mp_obj_module_t mp_module_ffi; + #define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else + #define MICROPY_PY_FFI_DEF +#endif + +#if MICROPY_PY_JNI + #define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else + #define MICROPY_PY_JNI_DEF +#endif + +#if MICROPY_PY_TERMIOS + #define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else + #define MICROPY_PY_TERMIOS_DEF +#endif + +#if MICROPY_PY_SOCKET + #define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else + #define MICROPY_PY_SOCKET_DEF +#endif + +#if MICROPY_PY_USELECT_POSIX + #define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else + #define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PY_MACHINE_DEF + + +#if MICROPY_PY_LVGL + extern const struct _mp_obj_module_t mp_module_utime; + extern const struct _mp_obj_module_t mp_module_lvgl; + extern const struct _mp_obj_module_t mp_module_lvindev; + extern const struct _mp_obj_module_t mp_module_SDL; + + #include "lib/lv_bindings/lvgl/src/lv_misc/lv_gc.h" + + #define MICROPY_PY_LVGL_DEF \ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvgl), (mp_obj_t)&mp_module_lvgl },\ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvindev), (mp_obj_t)&mp_module_lvindev},\ + { MP_OBJ_NEW_QSTR(MP_QSTR_SDL), (mp_obj_t)&mp_module_SDL }, + + #define MPR_void_mp_lv_user_data void *mp_lv_user_data; + #define MPR_LVGL LV_GC_ROOTS(LV_NO_PREFIX) +#else + #define MICROPY_PY_LVGL_DEF + #define MPR_void_mp_lv_user_data + #define MPR_LVGL +#endif + + +// { MP _ ROM_QSTR(MP_QSTR_umachine), MP _ ROM_PTR(&mp_module_machine) }, + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_SOCKET_DEF \ + MICROPY_PY_MACHINE_DEF \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + MICROPY_PY_LVGL_DEF \ + MODULES_LINKS + + + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, \ + + + +#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_binascii), (mp_obj_t)&mp_module_ubinascii }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_errno), (mp_obj_t)&mp_module_uerrno }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_hashlib), (mp_obj_t)&mp_module_uhashlib }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_heapq), (mp_obj_t)&mp_module_uheapq }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_json), (mp_obj_t)&mp_module_ujson }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&mp_module_os }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&mp_module_urandom }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_re), (mp_obj_t)&mp_module_ure }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_select), (mp_obj_t)&mp_module_uselect }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&mp_module_usocket }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_ssl), (mp_obj_t)&mp_module_ussl }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_struct), (mp_obj_t)&mp_module_ustruct }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_zlib), (mp_obj_t)&mp_module_uzlib }, \ + + + +// We need to provide a declaration/definition of alloca() +#include + +#define MICROPY_HW_BOARD_NAME "emscripten" +#define MICROPY_HW_MCU_NAME "wasm" + +#ifdef __linux__ +#define MICROPY_MIN_USE_STDOUT (1) +#endif + +#ifdef __thumb__ +#define MICROPY_MIN_USE_CORTEX_CPU (1) +#define MICROPY_MIN_USE_STM32_MCU (1) +#endif + +#define MP_STATE_PORT MP_STATE_VM + +#define MPR_const_char_readline_hist const char *readline_hist[16]; + +#define MICROPY_PORT_ROOT_POINTERS \ + MPR_LVGL \ + MPR_void_mp_lv_user_data \ + MPR_const_char_readline_hist \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + + +#define FFCONF_H "lib/oofatfs/ffconf.h" + + + + + + + + +// diff --git a/ports/wapy-wasm/mphalport.h b/ports/wapy-wasm/mphalport.h new file mode 100644 index 000000000..95de747d7 --- /dev/null +++ b/ports/wapy-wasm/mphalport.h @@ -0,0 +1,31 @@ +#include +//#include "py/nlr.h" +//#include "py/compile.h" +#include "py/runtime.h" +//#include "py/repl.h" +//#include "py/gc.h" +//#include "lib/utils/pyexec.h" + +#ifndef CHAR_CTRL_C +#define CHAR_CTRL_C (3) +#endif + + +static inline void mp_hal_set_interrupt_char(char c) {} + +// TODO: wasm does not sleep +/* +static inline void mp_hal_delay_ms(mp_uint_t ms) { usleep((ms) * 1000); } +static inline void mp_hal_delay_us(mp_uint_t us) { usleep(us); } +#define mp_hal_ticks_cpu() 0 +*/ + +mp_uint_t mp_hal_ticks_ms(void); + + + +#define RAISE_ERRNO(err_flag, error_val) \ + { if (err_flag == -1) \ + { mp_raise_OSError(error_val); } } + + diff --git a/ports/wapy-wasm/qstr/modbuiltins.c b/ports/wapy-wasm/qstr/modbuiltins.c new file mode 100644 index 000000000..0b61780d3 --- /dev/null +++ b/ports/wapy-wasm/qstr/modbuiltins.c @@ -0,0 +1,777 @@ +/* + * 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 + +#include "py/smallint.h" +#include "py/objint.h" +#include "py/objstr.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stream.h" + +#if MICROPY_PY_BUILTINS_FLOAT +#include +#endif + +#if MICROPY_PY_IO +extern struct _mp_dummy_t mp_sys_stdout_obj; // type is irrelevant, just need pointer +#endif + +// args[0] is function from class body +// args[1] is class name +// args[2:] are base objects +STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) { + assert(2 <= n_args); + + // set the new classes __locals__ object + mp_obj_dict_t *old_locals = mp_locals_get(); + mp_obj_t class_locals = mp_obj_new_dict(0); + mp_locals_set(MP_OBJ_TO_PTR(class_locals)); + + // call the class code + mp_obj_t cell = mp_call_function_0(args[0]); + + // restore old __locals__ object + mp_locals_set(old_locals); + + // get the class type (meta object) from the base objects + mp_obj_t meta; + if (n_args == 2) { + // no explicit bases, so use 'type' + meta = MP_OBJ_FROM_PTR(&mp_type_type); + } else { + // use type of first base object + meta = MP_OBJ_FROM_PTR(mp_obj_get_type(args[2])); + } + + // TODO do proper metaclass resolution for multiple base objects + + // create the new class using a call to the meta object + mp_obj_t meta_args[3]; + meta_args[0] = args[1]; // class name + meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases + meta_args[2] = class_locals; // dict of members + mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); + + // store into cell if neede + if (cell != mp_const_none) { + mp_obj_cell_set(cell, new_class); + } + + return new_class; +} +MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj, 2, mp_builtin___build_class__); + +STATIC mp_obj_t mp_builtin_abs(mp_obj_t o_in) { + return mp_unary_op(MP_UNARY_OP_ABS, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_abs_obj, mp_builtin_abs); + +STATIC mp_obj_t mp_builtin_all(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (!mp_obj_is_true(item)) { + return mp_const_false; + } + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_all_obj, mp_builtin_all); + +STATIC mp_obj_t mp_builtin_any(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (mp_obj_is_true(item)) { + return mp_const_true; + } + } + return mp_const_false; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_any_obj, mp_builtin_any); + +STATIC mp_obj_t mp_builtin_bin(mp_obj_t o_in) { + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_b_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_bin_obj, mp_builtin_bin); + +STATIC mp_obj_t mp_builtin_callable(mp_obj_t o_in) { + if (mp_obj_is_callable(o_in)) { + return mp_const_true; + } else { + return mp_const_false; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_callable_obj, mp_builtin_callable); + +STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_UNICODE + mp_uint_t c = mp_obj_get_int(o_in); + uint8_t str[4]; + int len = 0; + if (c < 0x80) { + *str = c; len = 1; + } else if (c < 0x800) { + str[0] = (c >> 6) | 0xC0; + str[1] = (c & 0x3F) | 0x80; + len = 2; + } else if (c < 0x10000) { + str[0] = (c >> 12) | 0xE0; + str[1] = ((c >> 6) & 0x3F) | 0x80; + str[2] = (c & 0x3F) | 0x80; + len = 3; + } else if (c < 0x110000) { + str[0] = (c >> 18) | 0xF0; + str[1] = ((c >> 12) & 0x3F) | 0x80; + str[2] = ((c >> 6) & 0x3F) | 0x80; + str[3] = (c & 0x3F) | 0x80; + len = 4; + } else { + mp_raise_ValueError("chr() arg not in range(0x110000)"); + } + return mp_obj_new_str_via_qstr((char*)str, len); + #else + mp_int_t ord = mp_obj_get_int(o_in); + if (0 <= ord && ord <= 0xff) { + uint8_t str[1] = {ord}; + return mp_obj_new_str_via_qstr((char*)str, 1); + } else { + mp_raise_ValueError("chr() arg not in range(256)"); + } + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); + +STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { + mp_obj_t dir = mp_obj_new_list(0, NULL); + if (n_args == 0) { + // Make a list of names in the local namespace + mp_obj_dict_t *dict = mp_locals_get(); + for (size_t i = 0; i < dict->map.alloc; i++) { + if (mp_map_slot_is_filled(&dict->map, i)) { + mp_obj_list_append(dir, dict->map.table[i].key); + } + } + } else { // n_args == 1 + // Make a list of names in the given object + // Implemented by probing all possible qstrs with mp_load_method_maybe + size_t nqstr = QSTR_TOTAL(); + for (size_t i = MP_QSTR_ + 1; i < nqstr; ++i) { + mp_obj_t dest[2]; + mp_load_method_protected(args[0], i, dest, false); + if (dest[0] != MP_OBJ_NULL) { + #if MICROPY_PY_ALL_SPECIAL_METHODS + // Support for __dir__: see if we can dispatch to this special method + // This relies on MP_QSTR__dir__ being first after MP_QSTR_ + if (i == MP_QSTR___dir__ && dest[1] != MP_OBJ_NULL) { + return mp_call_method_n_kw(0, 0, dest); + } + #endif + mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i)); + } + } + } + return dir; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); + +STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { + return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_divmod_obj, mp_builtin_divmod); + +STATIC mp_obj_t mp_builtin_hash(mp_obj_t o_in) { + // result is guaranteed to be a (small) int + return mp_unary_op(MP_UNARY_OP_HASH, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hash_obj, mp_builtin_hash); + +STATIC mp_obj_t mp_builtin_hex(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_x), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_x_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hex_obj, mp_builtin_hex); + +#if MICROPY_PY_BUILTINS_INPUT + +#include "py/mphal.h" +#include "lib/mp-readline/readline.h" + +// A port can define mp_hal_readline if they want to use a custom function here +#ifndef mp_hal_readline +#define mp_hal_readline readline +#endif + +STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_print(args[0], PRINT_STR); + } + vstr_t line; + vstr_init(&line, 16); + int ret = mp_hal_readline(&line, ""); + if (ret == CHAR_CTRL_C) { + nlr_raise(mp_obj_new_exception(&mp_type_KeyboardInterrupt)); + } + if (line.len == 0 && ret == CHAR_CTRL_D) { + nlr_raise(mp_obj_new_exception(&mp_type_EOFError)); + } + return mp_obj_new_str_from_vstr(&mp_type_str, &line); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj, 0, 1, mp_builtin_input); + +#endif + +STATIC mp_obj_t mp_builtin_iter(mp_obj_t o_in) { + return mp_getiter(o_in, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter); + +#if MICROPY_PY_BUILTINS_MIN_MAX + +STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs, mp_uint_t op) { + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_key), MP_MAP_LOOKUP); + mp_map_elem_t *default_elem; + mp_obj_t key_fn = key_elem == NULL ? MP_OBJ_NULL : key_elem->value; + if (n_args == 1) { + // given an iterable + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? item : mp_call_function_1(key_fn, item); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = item; + } + } + if (best_obj == MP_OBJ_NULL) { + default_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_default), MP_MAP_LOOKUP); + if (default_elem != NULL) { + best_obj = default_elem->value; + } else { + mp_raise_ValueError("arg is an empty sequence"); + } + } + return best_obj; + } else { + // given many args + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + for (size_t i = 0; i < n_args; i++) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? args[i] : mp_call_function_1(key_fn, args[i]); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = args[i]; + } + } + return best_obj; + } +} + +STATIC mp_obj_t mp_builtin_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_MORE); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_max_obj, 1, mp_builtin_max); + +STATIC mp_obj_t mp_builtin_min(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_LESS); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_min_obj, 1, mp_builtin_min); + +#endif + +#if MICROPY_PY_BUILTINS_NEXT2 +STATIC mp_obj_t mp_builtin_next(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_t ret = mpsl_iternext_allow_raise(args[0]); + if (ret == MP_OBJ_STOP_ITERATION) { + nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + } else { + return ret; + } + } else { + mp_obj_t ret = mp_iternext(args[0]); + return ret == MP_OBJ_STOP_ITERATION ? args[1] : ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); +#else +STATIC mp_obj_t mp_builtin_next(mp_obj_t o) { + mp_obj_t ret = mpsl_iternext_allow_raise(o); + if (ret == MP_OBJ_STOP_ITERATION) { + nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + } else { + return ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_next_obj, mp_builtin_next); +#endif + +STATIC mp_obj_t mp_builtin_oct(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_o), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_o_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_oct_obj, mp_builtin_oct); + +STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { + size_t len; + const byte *str = (const byte*)mp_obj_str_get_data(o_in, &len); + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (mp_obj_is_str(o_in)) { + len = utf8_charlen(str, len); + if (len == 1) { + return mp_obj_new_int(utf8_get_char(str)); + } + } else + #endif + { + // a bytes object, or a str without unicode support (don't sign extend the char) + if (len == 1) { + return MP_OBJ_NEW_SMALL_INT(str[0]); + } + } + + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("ord expects a character"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "ord() expected a character, but string of length %d found", (int)len)); + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); + +STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { + switch (n_args) { + case 2: return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); + default: +#if !MICROPY_PY_BUILTINS_POW3 + mp_raise_msg(&mp_type_NotImplementedError, "3-arg pow() not supported"); +#elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); +#else + return mp_obj_int_pow3(args[0], args[1], args[2]); +#endif + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); + +STATIC mp_obj_t mp_builtin_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sep, ARG_end, ARG_file }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} }, + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + { MP_QSTR_file, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_sys_stdout_obj)} }, + #endif + }; + + // parse args (a union is used to reduce the amount of C stack that is needed) + union { + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + size_t len[2]; + } u; + mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, u.args); + + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_get_stream_raise(u.args[ARG_file].u_obj, MP_STREAM_OP_WRITE); + mp_print_t print = {MP_OBJ_TO_PTR(u.args[ARG_file].u_obj), mp_stream_write_adaptor}; + #endif + + // extract the objects first because we are going to use the other part of the union + mp_obj_t sep = u.args[ARG_sep].u_obj; + mp_obj_t end = u.args[ARG_end].u_obj; + const char *sep_data = mp_obj_str_get_data(sep, &u.len[0]); + const char *end_data = mp_obj_str_get_data(end, &u.len[1]); + + for (size_t i = 0; i < n_args; i++) { + if (i > 0) { + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, sep_data, u.len[0]); + #else + mp_print_strn(&mp_plat_print, sep_data, u.len[0], 0, 0, 0); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_obj_print_helper(&print, pos_args[i], PRINT_STR); + #else + mp_obj_print_helper(&mp_plat_print, pos_args[i], PRINT_STR); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, end_data, u.len[1]); + #else + mp_print_strn(&mp_plat_print, end_data, u.len[1], 0, 0, 0); + #endif + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_print_obj, 0, mp_builtin_print); + +STATIC mp_obj_t mp_builtin___repl_print__(mp_obj_t o) { + if (o != mp_const_none) { + mp_obj_print_helper(MP_PYTHON_PRINTER, o, PRINT_REPR); + mp_print_str(MP_PYTHON_PRINTER, "\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Set "_" special variable + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, o}; + mp_type_module.attr(MP_OBJ_FROM_PTR(&mp_module_builtins), MP_QSTR__, dest); + #endif + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin___repl_print___obj, mp_builtin___repl_print__); + +STATIC mp_obj_t mp_builtin_repr(mp_obj_t o_in) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, o_in, PRINT_REPR); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_repr_obj, mp_builtin_repr); + +STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { + mp_obj_t o_in = args[0]; + if (mp_obj_is_int(o_in)) { + if (n_args <= 1) { + return o_in; + } + + #if !MICROPY_PY_BUILTINS_ROUND_INT + mp_raise_NotImplementedError(NULL); + #else + mp_int_t num_dig = mp_obj_get_int(args[1]); + if (num_dig >= 0) { + return o_in; + } + + mp_obj_t mult = mp_binary_op(MP_BINARY_OP_POWER, MP_OBJ_NEW_SMALL_INT(10), MP_OBJ_NEW_SMALL_INT(-num_dig)); + mp_obj_t half_mult = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, mult, MP_OBJ_NEW_SMALL_INT(2)); + mp_obj_t modulo = mp_binary_op(MP_BINARY_OP_MODULO, o_in, mult); + mp_obj_t rounded = mp_binary_op(MP_BINARY_OP_SUBTRACT, o_in, modulo); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, half_mult, modulo))) { + return rounded; + } else if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, modulo, half_mult))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + // round to even number + mp_obj_t floor = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, o_in, mult); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_AND, floor, MP_OBJ_NEW_SMALL_INT(1)))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + return rounded; + } + } + #endif + } +#if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(o_in); + if (n_args > 1) { + mp_int_t num_dig = mp_obj_get_int(args[1]); + mp_float_t mult = MICROPY_FLOAT_C_FUN(pow)(10, num_dig); + // TODO may lead to overflow + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val * mult) / mult; + return mp_obj_new_float(rounded); + } + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val); + return mp_obj_new_int_from_float(rounded); +#else + mp_int_t r = mp_obj_get_int(o_in); + return mp_obj_new_int(r); +#endif +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj, 1, 2, mp_builtin_round); + +STATIC mp_obj_t mp_builtin_sum(size_t n_args, const mp_obj_t *args) { + mp_obj_t value; + switch (n_args) { + case 1: value = MP_OBJ_NEW_SMALL_INT(0); break; + default: value = args[1]; break; + } + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_binary_op(MP_BINARY_OP_ADD, value, item); + } + return value; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum); + +STATIC mp_obj_t mp_builtin_sorted(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args > 1) { + mp_raise_TypeError("must use keyword argument for key function"); + } + mp_obj_t self = mp_type_list.make_new(&mp_type_list, 1, 0, args); + mp_obj_list_sort(1, &self, kwargs); + + return self; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); + +// See mp_load_attr() if making any changes +static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { + mp_obj_t dest[2]; + // use load_method, raising or not raising exception + ((defval == MP_OBJ_NULL) ? mp_load_method : mp_load_method_maybe)(base, attr, dest); + if (dest[0] == MP_OBJ_NULL) { + return defval; + } else if (dest[1] == MP_OBJ_NULL) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +STATIC mp_obj_t mp_builtin_getattr(size_t n_args, const mp_obj_t *args) { + mp_obj_t defval = MP_OBJ_NULL; + if (n_args > 2) { + defval = args[2]; + } + return mp_load_attr_default(args[0], mp_obj_str_get_qstr(args[1]), defval); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr); + +STATIC mp_obj_t mp_builtin_setattr(mp_obj_t base, mp_obj_t attr, mp_obj_t value) { + mp_store_attr(base, mp_obj_str_get_qstr(attr), value); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(mp_builtin_setattr_obj, mp_builtin_setattr); + +#if MICROPY_CPYTHON_COMPAT +STATIC mp_obj_t mp_builtin_delattr(mp_obj_t base, mp_obj_t attr) { + return mp_builtin_setattr(base, attr, MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_delattr_obj, mp_builtin_delattr); +#endif + +STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) { + qstr attr = mp_obj_str_get_qstr(attr_in); + mp_obj_t dest[2]; + mp_load_method_protected(object_in, attr, dest, false); + return mp_obj_new_bool(dest[0] != MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr); + +STATIC mp_obj_t mp_builtin_globals(void) { + return MP_OBJ_FROM_PTR(mp_globals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_builtin_globals); + +STATIC mp_obj_t mp_builtin_locals(void) { + return MP_OBJ_FROM_PTR(mp_locals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_builtin_locals); + +// These are defined in terms of MicroPython API functions right away +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_id_obj, mp_obj_id); +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_len_obj, mp_obj_len); + +STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_builtins) }, + + // built-in core functions + { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, + { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, + { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + + // built-in types + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, + { MP_ROM_QSTR(MP_QSTR_bytes), MP_ROM_PTR(&mp_type_bytes) }, + #if MICROPY_PY_BUILTINS_BYTEARRAY + { MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&mp_type_bytearray) }, + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_PTR(&mp_type_complex) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&mp_type_dict) }, + #if MICROPY_PY_BUILTINS_ENUMERATE + { MP_ROM_QSTR(MP_QSTR_enumerate), MP_ROM_PTR(&mp_type_enumerate) }, + #endif + #if MICROPY_PY_BUILTINS_FILTER + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&mp_type_filter) }, + #endif + #if MICROPY_PY_BUILTINS_FLOAT + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_PTR(&mp_type_float) }, + #endif + #if MICROPY_PY_BUILTINS_SET && MICROPY_PY_BUILTINS_FROZENSET + { MP_ROM_QSTR(MP_QSTR_frozenset), MP_ROM_PTR(&mp_type_frozenset) }, + #endif + { MP_ROM_QSTR(MP_QSTR_int), MP_ROM_PTR(&mp_type_int) }, + { MP_ROM_QSTR(MP_QSTR_list), MP_ROM_PTR(&mp_type_list) }, + { MP_ROM_QSTR(MP_QSTR_map), MP_ROM_PTR(&mp_type_map) }, + #if MICROPY_PY_BUILTINS_MEMORYVIEW + { MP_ROM_QSTR(MP_QSTR_memoryview), MP_ROM_PTR(&mp_type_memoryview) }, + #endif + { MP_ROM_QSTR(MP_QSTR_object), MP_ROM_PTR(&mp_type_object) }, + #if MICROPY_PY_BUILTINS_PROPERTY + { MP_ROM_QSTR(MP_QSTR_property), MP_ROM_PTR(&mp_type_property) }, + #endif + { MP_ROM_QSTR(MP_QSTR_range), MP_ROM_PTR(&mp_type_range) }, + #if MICROPY_PY_BUILTINS_REVERSED + { MP_ROM_QSTR(MP_QSTR_reversed), MP_ROM_PTR(&mp_type_reversed) }, + #endif + #if MICROPY_PY_BUILTINS_SET + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mp_type_set) }, + #endif + #if MICROPY_PY_BUILTINS_SLICE + { MP_ROM_QSTR(MP_QSTR_slice), MP_ROM_PTR(&mp_type_slice) }, + #endif + { MP_ROM_QSTR(MP_QSTR_str), MP_ROM_PTR(&mp_type_str) }, + { MP_ROM_QSTR(MP_QSTR_super), MP_ROM_PTR(&mp_type_super) }, + { MP_ROM_QSTR(MP_QSTR_tuple), MP_ROM_PTR(&mp_type_tuple) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&mp_type_type) }, + { MP_ROM_QSTR(MP_QSTR_zip), MP_ROM_PTR(&mp_type_zip) }, + + { MP_ROM_QSTR(MP_QSTR_classmethod), MP_ROM_PTR(&mp_type_classmethod) }, + { MP_ROM_QSTR(MP_QSTR_staticmethod), MP_ROM_PTR(&mp_type_staticmethod) }, + + // built-in objects + { MP_ROM_QSTR(MP_QSTR_Ellipsis), MP_ROM_PTR(&mp_const_ellipsis_obj) }, + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + { MP_ROM_QSTR(MP_QSTR_NotImplemented), MP_ROM_PTR(&mp_const_notimplemented_obj) }, + #endif + + // built-in user functions + { MP_ROM_QSTR(MP_QSTR_abs), MP_ROM_PTR(&mp_builtin_abs_obj) }, + { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&mp_builtin_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&mp_builtin_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_bin), MP_ROM_PTR(&mp_builtin_bin_obj) }, + { MP_ROM_QSTR(MP_QSTR_callable), MP_ROM_PTR(&mp_builtin_callable_obj) }, + #if MICROPY_PY_BUILTINS_COMPILE + { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mp_builtin_compile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_chr), MP_ROM_PTR(&mp_builtin_chr_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, + #if MICROPY_PY_BUILTINS_EVAL_EXEC + { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&mp_builtin_exec_obj) }, + #endif + #if MICROPY_PY_BUILTINS_EXECFILE + { MP_ROM_QSTR(MP_QSTR_execfile), MP_ROM_PTR(&mp_builtin_execfile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_getattr), MP_ROM_PTR(&mp_builtin_getattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setattr), MP_ROM_PTR(&mp_builtin_setattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_globals), MP_ROM_PTR(&mp_builtin_globals_obj) }, + { MP_ROM_QSTR(MP_QSTR_hasattr), MP_ROM_PTR(&mp_builtin_hasattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mp_builtin_hash_obj) }, + #if MICROPY_PY_BUILTINS_HELP + { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&mp_builtin_help_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_hex), MP_ROM_PTR(&mp_builtin_hex_obj) }, + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&mp_builtin_id_obj) }, + #if MICROPY_PY_BUILTINS_INPUT + { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mp_builtin_input_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_isinstance), MP_ROM_PTR(&mp_builtin_isinstance_obj) }, + { MP_ROM_QSTR(MP_QSTR_issubclass), MP_ROM_PTR(&mp_builtin_issubclass_obj) }, + { MP_ROM_QSTR(MP_QSTR_iter), MP_ROM_PTR(&mp_builtin_iter_obj) }, + { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&mp_builtin_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_locals), MP_ROM_PTR(&mp_builtin_locals_obj) }, + #if MICROPY_PY_BUILTINS_MIN_MAX + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&mp_builtin_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&mp_builtin_min_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, + { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, + { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, + { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, + { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, + { MP_ROM_QSTR(MP_QSTR_repr), MP_ROM_PTR(&mp_builtin_repr_obj) }, + { MP_ROM_QSTR(MP_QSTR_round), MP_ROM_PTR(&mp_builtin_round_obj) }, + { MP_ROM_QSTR(MP_QSTR_sorted), MP_ROM_PTR(&mp_builtin_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&mp_builtin_sum_obj) }, + + // built-in exceptions + { MP_ROM_QSTR(MP_QSTR_BaseException), MP_ROM_PTR(&mp_type_BaseException) }, + { MP_ROM_QSTR(MP_QSTR_ArithmeticError), MP_ROM_PTR(&mp_type_ArithmeticError) }, + { MP_ROM_QSTR(MP_QSTR_AssertionError), MP_ROM_PTR(&mp_type_AssertionError) }, + { MP_ROM_QSTR(MP_QSTR_AttributeError), MP_ROM_PTR(&mp_type_AttributeError) }, + { MP_ROM_QSTR(MP_QSTR_EOFError), MP_ROM_PTR(&mp_type_EOFError) }, + { MP_ROM_QSTR(MP_QSTR_Exception), MP_ROM_PTR(&mp_type_Exception) }, + { MP_ROM_QSTR(MP_QSTR_GeneratorExit), MP_ROM_PTR(&mp_type_GeneratorExit) }, + { MP_ROM_QSTR(MP_QSTR_ImportError), MP_ROM_PTR(&mp_type_ImportError) }, + { MP_ROM_QSTR(MP_QSTR_IndentationError), MP_ROM_PTR(&mp_type_IndentationError) }, + { MP_ROM_QSTR(MP_QSTR_IndexError), MP_ROM_PTR(&mp_type_IndexError) }, + { MP_ROM_QSTR(MP_QSTR_KeyboardInterrupt), MP_ROM_PTR(&mp_type_KeyboardInterrupt) }, + { MP_ROM_QSTR(MP_QSTR_KeyError), MP_ROM_PTR(&mp_type_KeyError) }, + { MP_ROM_QSTR(MP_QSTR_LookupError), MP_ROM_PTR(&mp_type_LookupError) }, + { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_MemoryError) }, + { MP_ROM_QSTR(MP_QSTR_NameError), MP_ROM_PTR(&mp_type_NameError) }, + { MP_ROM_QSTR(MP_QSTR_NotImplementedError), MP_ROM_PTR(&mp_type_NotImplementedError) }, + { MP_ROM_QSTR(MP_QSTR_OSError), MP_ROM_PTR(&mp_type_OSError) }, + { MP_ROM_QSTR(MP_QSTR_OverflowError), MP_ROM_PTR(&mp_type_OverflowError) }, + { MP_ROM_QSTR(MP_QSTR_RuntimeError), MP_ROM_PTR(&mp_type_RuntimeError) }, + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR_StopAsyncIteration), MP_ROM_PTR(&mp_type_StopAsyncIteration) }, + #endif + { MP_ROM_QSTR(MP_QSTR_StopIteration), MP_ROM_PTR(&mp_type_StopIteration) }, + { MP_ROM_QSTR(MP_QSTR_SyntaxError), MP_ROM_PTR(&mp_type_SyntaxError) }, + { MP_ROM_QSTR(MP_QSTR_SystemExit), MP_ROM_PTR(&mp_type_SystemExit) }, + { MP_ROM_QSTR(MP_QSTR_TypeError), MP_ROM_PTR(&mp_type_TypeError) }, + #if MICROPY_PY_BUILTINS_STR_UNICODE + { MP_ROM_QSTR(MP_QSTR_UnicodeError), MP_ROM_PTR(&mp_type_UnicodeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ValueError), MP_ROM_PTR(&mp_type_ValueError) }, + #if MICROPY_EMIT_NATIVE + { MP_ROM_QSTR(MP_QSTR_ViperTypeError), MP_ROM_PTR(&mp_type_ViperTypeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ZeroDivisionError), MP_ROM_PTR(&mp_type_ZeroDivisionError) }, + // 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 +}; + +MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); + +const mp_obj_module_t mp_module_builtins = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_builtins_globals, +}; diff --git a/ports/wapy-wasm/qstr/objtype.c b/ports/wapy-wasm/qstr/objtype.c new file mode 100644 index 000000000..3e65a32f0 --- /dev/null +++ b/ports/wapy-wasm/qstr/objtype.c @@ -0,0 +1,1405 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2018 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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 "py/objtype.h" +#include "py/runtime.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +#define ENABLE_SPECIAL_ACCESSORS \ + (MICROPY_PY_DESCRIPTORS || MICROPY_PY_DELATTR_SETATTR || MICROPY_PY_BUILTINS_PROPERTY) + +#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, size_t n_kw, const mp_obj_t *args); + +/******************************************************************************/ +// instance object + +STATIC int instance_count_native_bases(const mp_obj_type_t *type, const mp_obj_type_t **last_native_base) { + int count = 0; + for (;;) { + if (type == &mp_type_object) { + // Not a "real" type, end search here. + return count; + } else if (mp_obj_is_native_type(type)) { + // Native types don't have parents (at least not from our perspective) so end. + *last_native_base = type; + return count + 1; + } else if (type->parent == NULL) { + // No parents so end search here. + return count; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + // Multiple parents, search through them all recursively. + const mp_obj_tuple_t *parent_tuple = type->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len; + for (; item < top; ++item) { + assert(mp_obj_is_type(*item, &mp_type_type)); + const mp_obj_type_t *bt = (const mp_obj_type_t *)MP_OBJ_TO_PTR(*item); + count += instance_count_native_bases(bt, last_native_base); + } + return count; + #endif + } else { + // A single parent, use iteration to continue the search. + type = type->parent; + } + } +} + +// This wrapper function is allows a subclass of a native type to call the +// __init__() method (corresponding to type->make_new) of the native type. +STATIC mp_obj_t native_base_init_wrapper(size_t n_args, const mp_obj_t *args) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(args[0]); + const mp_obj_type_t *native_base = NULL; + instance_count_native_bases(self->base.type, &native_base); + self->subobj[0] = native_base->make_new(native_base, n_args - 1, 0, args + 1); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(native_base_init_wrapper_obj, 1, MP_OBJ_FUN_ARGS_MAX, native_base_init_wrapper); + +#if !MICROPY_CPYTHON_COMPAT +STATIC +#endif +mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *class, const mp_obj_type_t **native_base) { + size_t num_native_bases = instance_count_native_bases(class, native_base); + assert(num_native_bases < 2); + mp_obj_instance_t *o = m_new_obj_var(mp_obj_instance_t, mp_obj_t, num_native_bases); + o->base.type = class; + mp_map_init(&o->members, 0); + // Initialise the native base-class slot (should be 1 at most) with a valid + // object. It doesn't matter which object, so long as it can be uniquely + // distinguished from a native class that is initialised. + if (num_native_bases != 0) { + o->subobj[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); + } + return o; +} + +// TODO +// This implements depth-first left-to-right MRO, which is not compliant with Python3 MRO +// http://python-history.blogspot.com/2010/06/method-resolution-order.html +// https://www.python.org/download/releases/2.3/mro/ +// +// will keep lookup->dest[0]'s value (should be MP_OBJ_NULL on invocation) if attribute +// is not found +// will set lookup->dest[0] to MP_OBJ_SENTINEL if special method was found in a native +// type base via slot id (as specified by lookup->meth_offset). As there can be only one +// native base, it's known that it applies to instance->subobj[0]. In most cases, we also +// don't need to know which type it was - because instance->subobj[0] is of that type. +// The only exception is when object is not yet constructed, then we need to know base +// native type to construct its instance->subobj[0] from. But this case is handled via +// instance_count_native_bases(), which returns a native base which it saw. +struct class_lookup_data { + mp_obj_instance_t *obj; + qstr attr; + size_t meth_offset; + mp_obj_t *dest; + bool is_type; +}; + +STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_type_t *type) { + assert(lookup->dest[0] == MP_OBJ_NULL); + assert(lookup->dest[1] == MP_OBJ_NULL); + for (;;) { + DEBUG_printf("mp_obj_class_lookup: Looking up %s in %s\n", qstr_str(lookup->attr), qstr_str(type->name)); + // Optimize special method lookup for native types + // This avoids extra method_name => slot lookup. On the other hand, + // this should not be applied to class types, as will result in extra + // lookup either. + if (lookup->meth_offset != 0 && mp_obj_is_native_type(type)) { + if (*(void**)((char*)type + lookup->meth_offset) != NULL) { + DEBUG_printf("mp_obj_class_lookup: Matched special meth slot (off=%d) for %s\n", + lookup->meth_offset, qstr_str(lookup->attr)); + lookup->dest[0] = MP_OBJ_SENTINEL; + return; + } + } + + if (type->locals_dict != NULL) { + // search locals_dict (the set of methods/attributes) + assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &type->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(lookup->attr), MP_MAP_LOOKUP); + if (elem != NULL) { + if (lookup->is_type) { + // If we look up a class method, we need to return original type for which we + // do a lookup, not a (base) type in which we found the class method. + const mp_obj_type_t *org_type = (const mp_obj_type_t*)lookup->obj; + mp_convert_member_lookup(MP_OBJ_NULL, org_type, elem->value, lookup->dest); + } else { + mp_obj_instance_t *obj = lookup->obj; + mp_obj_t obj_obj; + if (obj != NULL && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { + // If we're dealing with native base class, then it applies to native sub-object + obj_obj = obj->subobj[0]; + } else { + obj_obj = MP_OBJ_FROM_PTR(obj); + } + mp_convert_member_lookup(obj_obj, type, elem->value, lookup->dest); + } +#if DEBUG_PRINT + DEBUG_printf("mp_obj_class_lookup: Returning: "); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, lookup->dest[0], PRINT_REPR); + if (lookup->dest[1] != MP_OBJ_NULL) { + // Don't try to repr() lookup->dest[1], as we can be called recursively + DEBUG_printf(" <%s @%p>", mp_obj_get_type_str(lookup->dest[1]), MP_OBJ_TO_PTR(lookup->dest[1])); + } + DEBUG_printf("\n"); +#endif + return; + } + } + + // Previous code block takes care about attributes defined in .locals_dict, + // but some attributes of native types may be handled using .load_attr method, + // so make sure we try to lookup those too. + if (lookup->obj != NULL && !lookup->is_type && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { + mp_load_method_maybe(lookup->obj->subobj[0], lookup->attr, lookup->dest); + if (lookup->dest[0] != MP_OBJ_NULL) { + return; + } + } + + // attribute not found, keep searching base classes + + if (type->parent == NULL) { + DEBUG_printf("mp_obj_class_lookup: No more parents\n"); + return; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + const mp_obj_tuple_t *parent_tuple = type->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len - 1; + for (; item < top; ++item) { + assert(mp_obj_is_type(*item, &mp_type_type)); + mp_obj_type_t *bt = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item); + if (bt == &mp_type_object) { + // Not a "real" type + continue; + } + mp_obj_class_lookup(lookup, bt); + if (lookup->dest[0] != MP_OBJ_NULL) { + return; + } + } + + // search last base (simple tail recursion elimination) + assert(mp_obj_is_type(*item, &mp_type_type)); + type = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item); + #endif + } else { + type = type->parent; + } + if (type == &mp_type_object) { + // Not a "real" type + return; + } + } +} + +STATIC void instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + qstr meth = (kind == PRINT_STR) ? MP_QSTR___str__ : MP_QSTR___repr__; + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = meth, + .meth_offset = offsetof(mp_obj_type_t, print), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL && kind == PRINT_STR) { + // If there's no __str__, fall back to __repr__ + lookup.attr = MP_QSTR___repr__; + lookup.meth_offset = 0; + mp_obj_class_lookup(&lookup, self->base.type); + } + + if (member[0] == MP_OBJ_SENTINEL) { + // Handle Exception subclasses specially + if (mp_obj_is_native_exception_instance(self->subobj[0])) { + if (kind != PRINT_STR) { + mp_print_str(print, qstr_str(self->base.type->name)); + } + mp_obj_print_helper(print, self->subobj[0], kind | PRINT_EXC_SUBCLASS); + } else { + mp_obj_print_helper(print, self->subobj[0], kind); + } + return; + } + + if (member[0] != MP_OBJ_NULL) { + mp_obj_t r = mp_call_function_1(member[0], self_in); + mp_obj_print_helper(print, r, PRINT_STR); + return; + } + + // TODO: CPython prints fully-qualified type name + mp_printf(print, "<%s object at %p>", mp_obj_get_type_str(self_in), self); +} + +mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_instance_type(self)); + + // look for __new__ function + mp_obj_t init_fn[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = NULL, + .attr = MP_QSTR___new__, + .meth_offset = offsetof(mp_obj_type_t, make_new), + .dest = init_fn, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self); + + const mp_obj_type_t *native_base = NULL; + mp_obj_instance_t *o; + if (init_fn[0] == MP_OBJ_NULL || init_fn[0] == MP_OBJ_SENTINEL) { + // Either there is no __new__() method defined or there is a native + // constructor. In both cases create a blank instance. + o = mp_obj_new_instance(self, &native_base); + + // Since type->make_new() implements both __new__() and __init__() in + // one go, of which the latter may be overridden by the Python subclass, + // we defer (see the end of this function) the call of the native + // constructor to give a chance for the Python __init__() method to call + // said native constructor. + + } else { + // Call Python class __new__ function with all args to create an instance + mp_obj_t new_ret; + if (n_args == 0 && n_kw == 0) { + mp_obj_t args2[1] = {MP_OBJ_FROM_PTR(self)}; + new_ret = mp_call_function_n_kw(init_fn[0], 1, 0, args2); + } else { + mp_obj_t *args2 = m_new(mp_obj_t, 1 + n_args + 2 * n_kw); + args2[0] = MP_OBJ_FROM_PTR(self); + memcpy(args2 + 1, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); + new_ret = mp_call_function_n_kw(init_fn[0], n_args + 1, n_kw, args2); + m_del(mp_obj_t, args2, 1 + n_args + 2 * n_kw); + } + + // https://docs.python.org/3.4/reference/datamodel.html#object.__new__ + // "If __new__() does not return an instance of cls, then the new + // instance's __init__() method will not be invoked." + if (mp_obj_get_type(new_ret) != self) { + return new_ret; + } + + // The instance returned by __new__() becomes the new object + o = MP_OBJ_TO_PTR(new_ret); + } + + // now call Python class __init__ function with all args + // This method has a chance to call super().__init__() to construct a + // possible native base class. + init_fn[0] = init_fn[1] = MP_OBJ_NULL; + lookup.obj = o; + lookup.attr = MP_QSTR___init__; + lookup.meth_offset = 0; + mp_obj_class_lookup(&lookup, self); + if (init_fn[0] != MP_OBJ_NULL) { + mp_obj_t init_ret; + if (n_args == 0 && n_kw == 0) { + init_ret = mp_call_method_n_kw(0, 0, init_fn); + } else { + mp_obj_t *args2 = m_new(mp_obj_t, 2 + n_args + 2 * n_kw); + args2[0] = init_fn[0]; + args2[1] = init_fn[1]; + memcpy(args2 + 2, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); + init_ret = mp_call_method_n_kw(n_args, n_kw, args2); + m_del(mp_obj_t, args2, 2 + n_args + 2 * n_kw); + } + if (init_ret != mp_const_none) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("__init__() should return None"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret))); + } + } + + } + + // If the type had a native base that was not explicitly initialised + // (constructed) by the Python __init__() method then construct it now. + if (native_base != NULL && o->subobj[0] == MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj)) { + o->subobj[0] = native_base->make_new(native_base, n_args, n_kw, args); + } + + return MP_OBJ_FROM_PTR(o); +} + +// Qstrs for special methods are guaranteed to have a small value, so we use byte +// type to represent them. +const byte mp_unary_op_method_name[MP_UNARY_OP_NUM_RUNTIME] = { + [MP_UNARY_OP_BOOL] = MP_QSTR___bool__, + [MP_UNARY_OP_LEN] = MP_QSTR___len__, + [MP_UNARY_OP_HASH] = MP_QSTR___hash__, + [MP_UNARY_OP_INT] = MP_QSTR___int__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_UNARY_OP_POSITIVE] = MP_QSTR___pos__, + [MP_UNARY_OP_NEGATIVE] = MP_QSTR___neg__, + [MP_UNARY_OP_INVERT] = MP_QSTR___invert__, + [MP_UNARY_OP_ABS] = MP_QSTR___abs__, + #endif + #if MICROPY_PY_SYS_GETSIZEOF + [MP_UNARY_OP_SIZEOF] = MP_QSTR___sizeof__, + #endif +}; + +STATIC mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + #if MICROPY_PY_SYS_GETSIZEOF + if (MP_UNLIKELY(op == MP_UNARY_OP_SIZEOF)) { + // TODO: This doesn't count inherited objects (self->subobj) + const mp_obj_type_t *native_base; + size_t num_native_bases = instance_count_native_bases(mp_obj_get_type(self_in), &native_base); + + size_t sz = sizeof(*self) + sizeof(*self->subobj) * num_native_bases + + sizeof(*self->members.table) * self->members.alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + + qstr op_name = mp_unary_op_method_name[op]; + /* Still try to lookup native slot + if (op_name == 0) { + return MP_OBJ_NULL; + } + */ + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = op_name, + .meth_offset = offsetof(mp_obj_type_t, unary_op), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + return mp_unary_op(op, self->subobj[0]); + } else if (member[0] != MP_OBJ_NULL) { + mp_obj_t val = mp_call_function_1(member[0], self_in); + + switch (op) { + case MP_UNARY_OP_HASH: + // __hash__ must return a small int + val = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int_truncated(val)); + break; + case MP_UNARY_OP_INT: + // Must return int + if (!mp_obj_is_int(val)) { + mp_raise_TypeError(NULL); + } + break; + default: + // No need to do anything + ; + } + return val; + } else { + if (op == MP_UNARY_OP_HASH) { + lookup.attr = MP_QSTR___eq__; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL) { + // https://docs.python.org/3/reference/datamodel.html#object.__hash__ + // "User-defined classes have __eq__() and __hash__() methods by default; + // with them, all objects compare unequal (except with themselves) and + // x.__hash__() returns an appropriate value such that x == y implies + // both that x is y and hash(x) == hash(y)." + return MP_OBJ_NEW_SMALL_INT((mp_uint_t)self_in); + } + // "A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. + // When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError" + } + + return MP_OBJ_NULL; // op not supported + } +} + +// Binary-op enum values not listed here will have the default value of 0 in the +// table, corresponding to MP_QSTR_NULL, and are therefore unsupported (a lookup will +// fail). They can be added at the expense of code size for the qstr. +// Qstrs for special methods are guaranteed to have a small value, so we use byte +// type to represent them. +const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = { + [MP_BINARY_OP_LESS] = MP_QSTR___lt__, + [MP_BINARY_OP_MORE] = MP_QSTR___gt__, + [MP_BINARY_OP_EQUAL] = MP_QSTR___eq__, + [MP_BINARY_OP_LESS_EQUAL] = MP_QSTR___le__, + [MP_BINARY_OP_MORE_EQUAL] = MP_QSTR___ge__, + // MP_BINARY_OP_NOT_EQUAL, // a != b calls a == b and inverts result + [MP_BINARY_OP_CONTAINS] = MP_QSTR___contains__, + + // If an inplace method is not found a normal method will be used as a fallback + [MP_BINARY_OP_INPLACE_ADD] = MP_QSTR___iadd__, + [MP_BINARY_OP_INPLACE_SUBTRACT] = MP_QSTR___isub__, + #if MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS + [MP_BINARY_OP_INPLACE_MULTIPLY] = MP_QSTR___imul__, + [MP_BINARY_OP_INPLACE_FLOOR_DIVIDE] = MP_QSTR___ifloordiv__, + [MP_BINARY_OP_INPLACE_TRUE_DIVIDE] = MP_QSTR___itruediv__, + [MP_BINARY_OP_INPLACE_MODULO] = MP_QSTR___imod__, + [MP_BINARY_OP_INPLACE_POWER] = MP_QSTR___ipow__, + [MP_BINARY_OP_INPLACE_OR] = MP_QSTR___ior__, + [MP_BINARY_OP_INPLACE_XOR] = MP_QSTR___ixor__, + [MP_BINARY_OP_INPLACE_AND] = MP_QSTR___iand__, + [MP_BINARY_OP_INPLACE_LSHIFT] = MP_QSTR___ilshift__, + [MP_BINARY_OP_INPLACE_RSHIFT] = MP_QSTR___irshift__, + #endif + + [MP_BINARY_OP_ADD] = MP_QSTR___add__, + [MP_BINARY_OP_SUBTRACT] = MP_QSTR___sub__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_BINARY_OP_MULTIPLY] = MP_QSTR___mul__, + [MP_BINARY_OP_FLOOR_DIVIDE] = MP_QSTR___floordiv__, + [MP_BINARY_OP_TRUE_DIVIDE] = MP_QSTR___truediv__, + [MP_BINARY_OP_MODULO] = MP_QSTR___mod__, + [MP_BINARY_OP_DIVMOD] = MP_QSTR___divmod__, + [MP_BINARY_OP_POWER] = MP_QSTR___pow__, + [MP_BINARY_OP_OR] = MP_QSTR___or__, + [MP_BINARY_OP_XOR] = MP_QSTR___xor__, + [MP_BINARY_OP_AND] = MP_QSTR___and__, + [MP_BINARY_OP_LSHIFT] = MP_QSTR___lshift__, + [MP_BINARY_OP_RSHIFT] = MP_QSTR___rshift__, + #endif + + #if MICROPY_PY_REVERSE_SPECIAL_METHODS + [MP_BINARY_OP_REVERSE_ADD] = MP_QSTR___radd__, + [MP_BINARY_OP_REVERSE_SUBTRACT] = MP_QSTR___rsub__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_BINARY_OP_REVERSE_MULTIPLY] = MP_QSTR___rmul__, + [MP_BINARY_OP_REVERSE_FLOOR_DIVIDE] = MP_QSTR___rfloordiv__, + [MP_BINARY_OP_REVERSE_TRUE_DIVIDE] = MP_QSTR___rtruediv__, + [MP_BINARY_OP_REVERSE_MODULO] = MP_QSTR___rmod__, + [MP_BINARY_OP_REVERSE_POWER] = MP_QSTR___rpow__, + [MP_BINARY_OP_REVERSE_OR] = MP_QSTR___ror__, + [MP_BINARY_OP_REVERSE_XOR] = MP_QSTR___rxor__, + [MP_BINARY_OP_REVERSE_AND] = MP_QSTR___rand__, + [MP_BINARY_OP_REVERSE_LSHIFT] = MP_QSTR___rlshift__, + [MP_BINARY_OP_REVERSE_RSHIFT] = MP_QSTR___rrshift__, + #endif + #endif +}; + +STATIC mp_obj_t instance_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // Note: For ducktyping, CPython does not look in the instance members or use + // __getattr__ or __getattribute__. It only looks in the class dictionary. + mp_obj_instance_t *lhs = MP_OBJ_TO_PTR(lhs_in); +retry:; + qstr op_name = mp_binary_op_method_name[op]; + /* Still try to lookup native slot + if (op_name == 0) { + return MP_OBJ_NULL; + } + */ + mp_obj_t dest[3] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = lhs, + .attr = op_name, + .meth_offset = offsetof(mp_obj_type_t, binary_op), + .dest = dest, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, lhs->base.type); + + mp_obj_t res; + if (dest[0] == MP_OBJ_SENTINEL) { + res = mp_binary_op(op, lhs->subobj[0], rhs_in); + } else if (dest[0] != MP_OBJ_NULL) { + dest[2] = rhs_in; + res = mp_call_method_n_kw(1, 0, dest); + } else { + // If this was an inplace method, fallback to normal method + // https://docs.python.org/3/reference/datamodel.html#object.__iadd__ : + // "If a specific method is not defined, the augmented assignment + // falls back to the normal methods." + if (op >= MP_BINARY_OP_INPLACE_OR && op <= MP_BINARY_OP_INPLACE_POWER) { + op -= MP_BINARY_OP_INPLACE_OR - MP_BINARY_OP_OR; + goto retry; + } + return MP_OBJ_NULL; // op not supported + } + + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + // NotImplemented means "try other fallbacks (like calling __rop__ + // instead of __op__) and if nothing works, raise TypeError". As + // MicroPython doesn't implement any fallbacks, signal to raise + // TypeError right away. + if (res == mp_const_notimplemented) { + return MP_OBJ_NULL; // op not supported + } + #endif + + return res; +} + +STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + // logic: look in instance members then class locals + assert(mp_obj_is_instance_type(mp_obj_get_type(self_in))); + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + // object member, always treated as a value + dest[0] = elem->value; + return; + } +#if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___dict__) { + // Create a new dict with a copy of the instance's map items. + // This creates, unlike CPython, a 'read-only' __dict__: 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; + return; + } +#endif + struct class_lookup_data lookup = { + .obj = self, + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + mp_obj_t member = dest[0]; + if (member != MP_OBJ_NULL) { + if (!(self->base.type->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + // Class doesn't have any special accessors to check so return straightaway + return; + } + + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(member, &mp_type_property)) { + // object member is a property; delegate the load to the property + // Note: This is an optimisation for code size and execution time. + // The proper way to do it is have the functionality just below + // in a __get__ method of the property object, and then it would + // be called by the descriptor code down below. But that way + // requires overhead for the nested mp_call's and overhead for + // the code. + const mp_obj_t *proxy = mp_obj_property_get(member); + if (proxy[0] == mp_const_none) { + mp_raise_msg(&mp_type_AttributeError, "unreadable attribute"); + } else { + dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); + } + return; + } + #endif + + #if MICROPY_PY_DESCRIPTORS + // found a class attribute; if it has a __get__ method then call it with the + // class instance and class as arguments and return the result + // Note that this is functionally correct but very slow: each load_attr + // requires an extra mp_load_method_maybe to check for the __get__. + mp_obj_t attr_get_method[4]; + mp_load_method_maybe(member, MP_QSTR___get__, attr_get_method); + if (attr_get_method[0] != MP_OBJ_NULL) { + attr_get_method[2] = self_in; + attr_get_method[3] = MP_OBJ_FROM_PTR(mp_obj_get_type(self_in)); + dest[0] = mp_call_method_n_kw(2, 0, attr_get_method); + } + #endif + return; + } + + // try __getattr__ + if (attr != MP_QSTR___getattr__) { + #if MICROPY_PY_DELATTR_SETATTR + // If the requested attr is __setattr__/__delattr__ then don't delegate the lookup + // to __getattr__. If we followed CPython's behaviour then __setattr__/__delattr__ + // would have already been found in the "object" base class. + if (attr == MP_QSTR___setattr__ || attr == MP_QSTR___delattr__) { + return; + } + #endif + + mp_obj_t dest2[3]; + mp_load_method_maybe(self_in, MP_QSTR___getattr__, dest2); + if (dest2[0] != MP_OBJ_NULL) { + // __getattr__ exists, call it and return its result + dest2[2] = MP_OBJ_NEW_QSTR(attr); + dest[0] = mp_call_method_n_kw(1, 0, dest2); + return; + } + } +} + +STATIC bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + if (!(self->base.type->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + // Class doesn't have any special accessors so skip their checks + goto skip_special_accessors; + } + + #if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS + // With property and/or descriptors enabled we need to do a lookup + // first in the class dict for the attribute to see if the store should + // be delegated. + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = attr, + .meth_offset = 0, + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + + if (member[0] != MP_OBJ_NULL) { + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(member[0], &mp_type_property)) { + // attribute exists and is a property; delegate the store/delete + // Note: This is an optimisation for code size and execution time. + // The proper way to do it is have the functionality just below in + // a __set__/__delete__ method of the property object, and then it + // would be called by the descriptor code down below. But that way + // requires overhead for the nested mp_call's and overhead for + // the code. + const mp_obj_t *proxy = mp_obj_property_get(member[0]); + mp_obj_t dest[2] = {self_in, value}; + if (value == MP_OBJ_NULL) { + // delete attribute + if (proxy[2] == mp_const_none) { + // TODO better error message? + return false; + } else { + mp_call_function_n_kw(proxy[2], 1, 0, dest); + return true; + } + } else { + // store attribute + if (proxy[1] == mp_const_none) { + // TODO better error message? + return false; + } else { + mp_call_function_n_kw(proxy[1], 2, 0, dest); + return true; + } + } + } + #endif + + #if MICROPY_PY_DESCRIPTORS + // found a class attribute; if it has a __set__/__delete__ method then + // call it with the class instance (and value) as arguments + if (value == MP_OBJ_NULL) { + // delete attribute + mp_obj_t attr_delete_method[3]; + mp_load_method_maybe(member[0], MP_QSTR___delete__, attr_delete_method); + if (attr_delete_method[0] != MP_OBJ_NULL) { + attr_delete_method[2] = self_in; + mp_call_method_n_kw(1, 0, attr_delete_method); + return true; + } + } else { + // store attribute + mp_obj_t attr_set_method[4]; + mp_load_method_maybe(member[0], MP_QSTR___set__, attr_set_method); + if (attr_set_method[0] != MP_OBJ_NULL) { + attr_set_method[2] = self_in; + attr_set_method[3] = value; + mp_call_method_n_kw(2, 0, attr_set_method); + return true; + } + } + #endif + } + #endif + + #if MICROPY_PY_DELATTR_SETATTR + if (value == MP_OBJ_NULL) { + // delete attribute + // try __delattr__ first + mp_obj_t attr_delattr_method[3]; + mp_load_method_maybe(self_in, MP_QSTR___delattr__, attr_delattr_method); + if (attr_delattr_method[0] != MP_OBJ_NULL) { + // __delattr__ exists, so call it + attr_delattr_method[2] = MP_OBJ_NEW_QSTR(attr); + mp_call_method_n_kw(1, 0, attr_delattr_method); + return true; + } + } else { + // store attribute + // try __setattr__ first + mp_obj_t attr_setattr_method[4]; + mp_load_method_maybe(self_in, MP_QSTR___setattr__, attr_setattr_method); + if (attr_setattr_method[0] != MP_OBJ_NULL) { + // __setattr__ exists, so call it + attr_setattr_method[2] = MP_OBJ_NEW_QSTR(attr); + attr_setattr_method[3] = value; + mp_call_method_n_kw(2, 0, attr_setattr_method); + return true; + } + } + #endif + +skip_special_accessors: + + if (value == MP_OBJ_NULL) { + // delete attribute + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); + return elem != NULL; + } else { + // store attribute + mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + return true; + } +} + +STATIC void mp_obj_instance_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + mp_obj_instance_load_attr(self_in, attr, dest); + } else { + if (mp_obj_instance_store_attr(self_in, attr, dest[1])) { + dest[0] = MP_OBJ_NULL; // indicate success + } + } +} + +STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[4] = {MP_OBJ_NULL, MP_OBJ_NULL, index, value}; + struct class_lookup_data lookup = { + .obj = self, + .meth_offset = offsetof(mp_obj_type_t, subscr), + .dest = member, + .is_type = false, + }; + if (value == MP_OBJ_NULL) { + // delete item + lookup.attr = MP_QSTR___delitem__; + } else if (value == MP_OBJ_SENTINEL) { + // load item + lookup.attr = MP_QSTR___getitem__; + } else { + // store item + lookup.attr = MP_QSTR___setitem__; + } + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + return mp_obj_subscr(self->subobj[0], index, value); + } else if (member[0] != MP_OBJ_NULL) { + size_t n_args = value == MP_OBJ_NULL || value == MP_OBJ_SENTINEL ? 1 : 2; + mp_obj_t ret = mp_call_method_n_kw(n_args, 0, member); + if (value == MP_OBJ_SENTINEL) { + return ret; + } else { + return mp_const_none; + } + } else { + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t mp_obj_instance_get_call(mp_obj_t self_in, mp_obj_t *member) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR___call__, + .meth_offset = offsetof(mp_obj_type_t, call), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + return member[0]; +} + +bool mp_obj_instance_is_callable(mp_obj_t self_in) { + mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; + return mp_obj_instance_get_call(self_in, member) != MP_OBJ_NULL; +} + +mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; + mp_obj_t call = mp_obj_instance_get_call(self_in, member); + if (call == MP_OBJ_NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("object not callable"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't callable", mp_obj_get_type_str(self_in))); + } + } + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (call == MP_OBJ_SENTINEL) { + return mp_call_function_n_kw(self->subobj[0], n_args, n_kw, args); + } + + return mp_call_method_self_n_kw(member[0], member[1], n_args, n_kw, args); +} + +STATIC mp_obj_t instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR___iter__, + .meth_offset = offsetof(mp_obj_type_t, getiter), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } else if (member[0] == MP_OBJ_SENTINEL) { + mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + return type->getiter(self->subobj[0], iter_buf); + } else { + return mp_call_method_n_kw(0, 0, member); + } +} + +STATIC mp_int_t instance_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR_, // don't actually look for a method + .meth_offset = offsetof(mp_obj_type_t, buffer_p.get_buffer), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + return type->buffer_p.get_buffer(self->subobj[0], bufinfo, flags); + } else { + return 1; // object does not support buffer protocol + } +} + +/******************************************************************************/ +// type object +// - the struct is mp_obj_type_t and is defined in obj.h so const types can be made +// - there is a constant mp_obj_type_t (called mp_type_type) for the 'type' object +// - creating a new class (a new type) creates a new mp_obj_type_t + +#if ENABLE_SPECIAL_ACCESSORS +STATIC bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { + #if MICROPY_PY_DELATTR_SETATTR + if (key == MP_OBJ_NEW_QSTR(MP_QSTR___setattr__) || key == MP_OBJ_NEW_QSTR(MP_QSTR___delattr__)) { + return true; + } + #endif + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(value, &mp_type_property)) { + return true; + } + #endif + #if MICROPY_PY_DESCRIPTORS + static const uint8_t to_check[] = { + MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, + }; + for (size_t i = 0; i < MP_ARRAY_SIZE(to_check); ++i) { + mp_obj_t dest_temp[2]; + mp_load_method_protected(value, to_check[i], dest_temp, true); + if (dest_temp[0] != MP_OBJ_NULL) { + return true; + } + } + #endif + return false; +} +#endif + +STATIC void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->name); +} + +STATIC mp_obj_t type_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + mp_arg_check_num(n_args, n_kw, 1, 3, false); + + switch (n_args) { + case 1: + return MP_OBJ_FROM_PTR(mp_obj_get_type(args[0])); + + case 3: + // args[0] = name + // args[1] = bases tuple + // args[2] = locals dict + return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]); + + default: + mp_raise_TypeError("type takes 1 or 3 arguments"); + } +} + +STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // instantiate an instance of a class + + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->make_new == NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("cannot create instance"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "cannot create '%q' instances", self->name)); + } + } + + // make new instance + mp_obj_t o = self->make_new(self, n_args, n_kw, args); + + // return new instance + return o; +} + +STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + assert(mp_obj_is_type(self_in, &mp_type_type)); + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + + if (dest[0] == MP_OBJ_NULL) { + // load attribute + #if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___name__) { + dest[0] = MP_OBJ_NEW_QSTR(self->name); + return; + } + #endif + struct class_lookup_data lookup = { + .obj = (mp_obj_instance_t*)self, + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = true, + }; + mp_obj_class_lookup(&lookup, self); + } else { + // delete/store attribute + + if (self->locals_dict != NULL) { + assert(self->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &self->locals_dict->map; + if (locals_map->is_fixed) { + // can't apply delete/store to a fixed map + return; + } + if (dest[1] == MP_OBJ_NULL) { + // delete attribute + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); + if (elem != NULL) { + dest[0] = MP_OBJ_NULL; // indicate success + } + } else { + #if ENABLE_SPECIAL_ACCESSORS + // Check if we add any special accessor methods with this store + if (!(self->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { + if (self->flags & TYPE_FLAG_IS_SUBCLASSED) { + // This class is already subclassed so can't have special accessors added + mp_raise_msg(&mp_type_AttributeError, "can't add special method to already-subclassed class"); + } + self->flags |= TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + } + } + #endif + + // store attribute + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + elem->value = dest[1]; + dest[0] = MP_OBJ_NULL; // indicate success + } + } + } +} + +const mp_obj_type_t mp_type_type = { + { &mp_type_type }, + .name = MP_QSTR_type, + .print = type_print, + .make_new = type_make_new, + .call = type_call, + .unary_op = mp_generic_unary_op, + .attr = type_attr, +}; + +mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) { + // Verify input objects have expected type + if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { + mp_raise_TypeError(NULL); + } + if (!mp_obj_is_type(locals_dict, &mp_type_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; + size_t bases_len; + mp_obj_t *bases_items; + mp_obj_tuple_get(bases_tuple, &bases_len, &bases_items); + for (size_t i = 0; i < bases_len; i++) { + if (!mp_obj_is_type(bases_items[i], &mp_type_type)) { + mp_raise_TypeError(NULL); + } + mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); + // TODO: Verify with CPy, tested on function type + if (t->make_new == NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("type isn't an acceptable base type"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "type '%q' isn't an acceptable base type", t->name)); + } + } + #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; + } + #endif + } + + mp_obj_type_t *o = m_new0(mp_obj_type_t, 1); + o->base.type = &mp_type_type; + o->flags = base_flags; + o->name = name; + o->print = instance_print; + o->make_new = mp_obj_instance_make_new; + o->call = mp_obj_instance_call; + o->unary_op = instance_unary_op; + o->binary_op = instance_binary_op; + o->attr = mp_obj_instance_attr; + o->subscr = instance_subscr; + o->getiter = instance_getiter; + //o->iternext = ; not implemented + o->buffer_p.get_buffer = instance_get_buffer; + + if (bases_len > 0) { + // Inherit protocol from a base class. This allows to define an + // abstract base class which would translate C-level protocol to + // Python method calls, and any subclass inheriting from it will + // support this feature. + o->protocol = ((mp_obj_type_t*)MP_OBJ_TO_PTR(bases_items[0]))->protocol; + + if (bases_len >= 2) { + #if MICROPY_MULTIPLE_INHERITANCE + o->parent = MP_OBJ_TO_PTR(bases_tuple); + #else + mp_raise_NotImplementedError("multiple inheritance not supported"); + #endif + } else { + o->parent = MP_OBJ_TO_PTR(bases_items[0]); + } + } + + o->locals_dict = MP_OBJ_TO_PTR(locals_dict); + + #if ENABLE_SPECIAL_ACCESSORS + // Check if the class has any special accessor methods + if (!(o->flags & 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 |= 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("multiple bases have instance lay-out conflict"); + } + + mp_map_t *locals_map = &o->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(MP_QSTR___new__), MP_MAP_LOOKUP); + if (elem != NULL) { + // __new__ slot exists; check if it is a function + if (mp_obj_is_fun(elem->value)) { + // __new__ is a function, wrap it in a staticmethod decorator + elem->value = static_class_method_make_new(&mp_type_staticmethod, 1, 0, &elem->value); + } + } + + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +// super object + +typedef struct _mp_obj_super_t { + mp_obj_base_t base; + mp_obj_t type; + mp_obj_t obj; +} mp_obj_super_t; + +STATIC void super_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); + mp_print_str(print, "type, PRINT_STR); + mp_print_str(print, ", "); + mp_obj_print_helper(print, self->obj, PRINT_STR); + mp_print_str(print, ">"); +} + +STATIC mp_obj_t super_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + // 0 arguments are turned into 2 in the compiler + // 1 argument is not yet implemented + mp_arg_check_num(n_args, n_kw, 2, 2, false); + if (!mp_obj_is_type(args[0], &mp_type_type)) { + mp_raise_TypeError(NULL); + } + mp_obj_super_t *o = m_new_obj(mp_obj_super_t); + *o = (mp_obj_super_t){{type_in}, args[0], args[1]}; + return MP_OBJ_FROM_PTR(o); +} + +STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // not load attribute + return; + } + + assert(mp_obj_is_type(self_in, &mp_type_super)); + mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); + + assert(mp_obj_is_type(self->type, &mp_type_type)); + + mp_obj_type_t *type = MP_OBJ_TO_PTR(self->type); + + struct class_lookup_data lookup = { + .obj = MP_OBJ_TO_PTR(self->obj), + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = false, + }; + + // Allow a call super().__init__() to reach any native base classes + if (attr == MP_QSTR___init__) { + lookup.meth_offset = offsetof(mp_obj_type_t, make_new); + } + + if (type->parent == NULL) { + // no parents, do nothing + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + const mp_obj_tuple_t *parent_tuple = type->parent; + size_t len = parent_tuple->len; + const mp_obj_t *items = parent_tuple->items; + for (size_t i = 0; i < len; i++) { + assert(mp_obj_is_type(items[i], &mp_type_type)); + if (MP_OBJ_TO_PTR(items[i]) == &mp_type_object) { + // The "object" type will be searched at the end of this function, + // and we don't want to lookup native methods in object. + continue; + } + mp_obj_class_lookup(&lookup, (mp_obj_type_t*)MP_OBJ_TO_PTR(items[i])); + if (dest[0] != MP_OBJ_NULL) { + break; + } + } + #endif + } else if (type->parent != &mp_type_object) { + mp_obj_class_lookup(&lookup, type->parent); + } + + if (dest[0] != MP_OBJ_NULL) { + if (dest[0] == MP_OBJ_SENTINEL) { + // Looked up native __init__ so defer to it + dest[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); + dest[1] = self->obj; + } + return; + } + + // Reset meth_offset so we don't look up any native methods in object, + // because object never takes up the native base-class slot. + lookup.meth_offset = 0; + + mp_obj_class_lookup(&lookup, &mp_type_object); +} + +const mp_obj_type_t mp_type_super = { + { &mp_type_type }, + .name = MP_QSTR_super, + .print = super_print, + .make_new = super_make_new, + .attr = super_attr, +}; + +void mp_load_super_method(qstr attr, mp_obj_t *dest) { + mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; + mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); +} + +/******************************************************************************/ +// subclassing and built-ins specific to types + +// object and classinfo should be type objects +// (but the function will fail gracefully if they are not) +bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo) { + for (;;) { + if (object == classinfo) { + return true; + } + + // not equivalent classes, keep searching base classes + + // object should always be a type object, but just return false if it's not + if (!mp_obj_is_type(object, &mp_type_type)) { + return false; + } + + const mp_obj_type_t *self = MP_OBJ_TO_PTR(object); + + if (self->parent == NULL) { + // type has no parents + return false; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)self->parent)->type == &mp_type_tuple) { + // get the base objects (they should be type objects) + const mp_obj_tuple_t *parent_tuple = self->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len - 1; + + // iterate through the base objects + for (; item < top; ++item) { + if (mp_obj_is_subclass_fast(*item, classinfo)) { + return true; + } + } + + // search last base (simple tail recursion elimination) + object = *item; + #endif + } else { + // type has 1 parent + object = MP_OBJ_FROM_PTR(self->parent); + } + } +} + +STATIC mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { + size_t len; + mp_obj_t *items; + if (mp_obj_is_type(classinfo, &mp_type_type)) { + len = 1; + items = &classinfo; + } else if (mp_obj_is_type(classinfo, &mp_type_tuple)) { + mp_obj_tuple_get(classinfo, &len, &items); + } else { + mp_raise_TypeError("issubclass() arg 2 must be a class or a tuple of classes"); + } + + for (size_t i = 0; i < len; i++) { + // We explicitly check for 'object' here since no-one explicitly derives from it + if (items[i] == MP_OBJ_FROM_PTR(&mp_type_object) || mp_obj_is_subclass_fast(object, items[i])) { + return mp_const_true; + } + } + return mp_const_false; +} + +STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) { + if (!mp_obj_is_type(object, &mp_type_type)) { + mp_raise_TypeError("issubclass() arg 1 must be a class"); + } + return mp_obj_is_subclass(object, classinfo); +} + +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_issubclass_obj, mp_builtin_issubclass); + +STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) { + return mp_obj_is_subclass(MP_OBJ_FROM_PTR(mp_obj_get_type(object)), classinfo); +} + +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance); + +mp_obj_t mp_instance_cast_to_native_base(mp_const_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_instance_t *self = (mp_obj_instance_t*)MP_OBJ_TO_PTR(self_in); + return self->subobj[0]; +} + +/******************************************************************************/ +// staticmethod and classmethod types (probably should go in a different file) + +STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(self == &mp_type_staticmethod || self == &mp_type_classmethod); + + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_static_class_method_t *o = m_new_obj(mp_obj_static_class_method_t); + *o = (mp_obj_static_class_method_t){{self}, args[0]}; + return MP_OBJ_FROM_PTR(o); +} + +const mp_obj_type_t mp_type_staticmethod = { + { &mp_type_type }, + .name = MP_QSTR_staticmethod, + .make_new = static_class_method_make_new, +}; + +const mp_obj_type_t mp_type_classmethod = { + { &mp_type_type }, + .name = MP_QSTR_classmethod, + .make_new = static_class_method_make_new, +}; diff --git a/ports/wapy-wasm/qstrdefsport.h b/ports/wapy-wasm/qstrdefsport.h new file mode 100644 index 000000000..8326cb082 --- /dev/null +++ b/ports/wapy-wasm/qstrdefsport.h @@ -0,0 +1,50 @@ +// qstrs specific to this port +Q(\r\n) +Q(__aenter__) +Q(__aexit__) +Q(__nonzero__) +Q(bound_method) +Q(pend_throw) +Q(__bool__) +Q(__pos__) +Q(__invert_) +Q(__abs__) +Q(__sizeof__) +Q(__eq__) +Q(__neg__) +Q(__lt__) +Q(__gt__) +Q(__le__) +Q(__ge__) +Q(__iadd__) +Q(__isub__) +Q(file) +Q(mode) +Q(encoding) +Q(r) +Q(buffering) +Q(heap_lock) +Q(heap_unlock) +Q(ld) +Q(unref) +Q(ptr) +Q(use) +Q(srv) +Q(config) +Q(oref) +Q(µO|%s|%s) +Q(android) +Q(emscripten) +Q(window) +Q(document) +Q(canvas) +Q(width) +Q(height) +Q(title) +Q(crt) +Q(set_x) +Q(set_z) +Q(set_text) + + + diff --git a/ports/wapy-wasm/upython.c b/ports/wapy-wasm/upython.c new file mode 100644 index 000000000..5312303b4 --- /dev/null +++ b/ports/wapy-wasm/upython.c @@ -0,0 +1,280 @@ +// +// core/main.c +// core/vfs.c + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +#include "../wapy/upython.h" + +#include "../wapy/core/ringbuf_o.h" +#include "../wapy/core/ringbuf_b.h" + +//static +struct wPyInterpreterState i_main; +//static +struct wPyThreadState i_state; + +RBB_T(out_rbb, 16384); + + + +EMSCRIPTEN_KEEPALIVE int +show_os_loop(int state) { + int last = SHOW_OS_LOOP; + if (state >= 0) { + SHOW_OS_LOOP = state; + if (state > 0) { + fprintf(stdout, "------------- showing os loop / starting repl --------------\n"); + repl_started = 1; + } else { + if (last != state) + fprintf(stdout, "------------- hiding os loop --------------\n"); + } + } + return (last > 0); +} + +EMSCRIPTEN_KEEPALIVE int +state_os_loop(int state) { + static int last_state = -666; + + if (!state) { + last_state = SHOW_OS_LOOP; + SHOW_OS_LOOP = 0; + } else { + if (last_state != -666) + SHOW_OS_LOOP = last_state; + last_state = -666; + } + return last_state; +} + +// ---- + + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + +char * +shm_get_ptr(int major, int minor) { + // keyboards + if (major == IO_KBD) { + if (minor == 0) + return &i_main.shm_stdio[IO_KBD]; + } + return NULL; +} + + + +char * +Py_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + pyexec_event_repl_init(); + return shm_ptr(); +} + +void +Py_Initialize() { + int stack_dummy; + stack_top = (char *) &stack_dummy; + +#if MICROPY_ENABLE_GC + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); +#endif + +//#if MICROPY_ENABLE_PYSTACK + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); +//#endif + + mp_init(); + +#if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; +#else + //(void)emit_opt; +#endif + + + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + +// TODO py/stackctrl.c#L40 check stack way + + +#if !MICROPY_ENABLE_FINALISER +#error requires MICROPY_ENABLE_FINALISER (1) +#endif + + +#undef gc_collect + + + + +//extern void gc_collect_root(void **ptrs, size_t len); + +#include "py/gc.h" + +#undef gc_collect + + +// GC boundaries + + +// The address of the top of the stack when we first saw it +// This approximates a pointer to the bottom of the stack, but +// may not necessarily _be_ the exact bottom. This is set by +// the entry point of the application +static void *stack_initial = NULL; +// Pointer to the end of the stack +static uintptr_t stack_max = (uintptr_t) NULL; +// The current stack pointer +static uintptr_t stack_ptr_val = (uintptr_t) NULL; +// The amount of stack remaining +static ptrdiff_t stack_left = 0; + +// Maximum stack size of 248k +// This limit is arbitrary, but is what works for me under Node.js when compiled with emscripten +static size_t stack_limit = 1024 * 248 * 1; + + +void +gc_collect(void) { + + gc_dump_info(); + + stack_ptr_val = (uintptr_t) __builtin_frame_address(0); + + clog("gc_collect_start"); + + gc_collect_start(); + + size_t bottom = (uintptr_t) stack_ptr_val; + + void **ptrs = (void **) (void *) stack_ptr_val; + + size_t len = ((uintptr_t) stack_initial - bottom) / sizeof(uintptr_t); + + clog("gc_collect stack_initial=%p bottom=%zu len=%zu", stack_initial, bottom, len); + +//343 + + gc_collect_root(ptrs, len); +//343 + +#if MICROPY_PY_THREAD + mp_thread_gc_others(); +#endif +#if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); +#endif + + clog("gc_collect_end"); + gc_collect_end(); +} + +// #endif // gc + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stdout, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + 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; + mp_raise_o(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stdout, "150:NULL FUNCTION\n"); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +#if __EMSCRIPTEN__ + #include "../wapy/wasm_mphal.c" +#endif + +#if __ANDROID__ + #include "../wapy/aosp_mphal.c" +#endif + +int +PyRun_IO_CODE() { + return pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + + +EMSCRIPTEN_KEEPALIVE int +repl_run(int warmup) { + if (warmup == 1) + return MP_IO_SHM_SIZE; + //wPy_NewInterpreter(); + repl_started = MP_IO_SHM_SIZE; + return 1; +} diff --git a/ports/wapy-wasm/vmsl/unwrap.c b/ports/wapy-wasm/vmsl/unwrap.c new file mode 100644 index 000000000..bcfce7b4a --- /dev/null +++ b/ports/wapy-wasm/vmsl/unwrap.c @@ -0,0 +1,416 @@ +#define VMTRACE (0) +#define DEBUG_BC (0) + + +#if !MICROPY_ENABLE_PYSTACK + #error "need MICROPY_ENABLE_PYSTACK (1)" +#endif + +#if !MICROPY_PERSISTENT_CODE + #pragma message "VM_DECODE_QSTR/VM_DECODE_PTR/VM_DECODE_OBJ" +#endif + + +#if VMTRACE + #define VM_TRACE_QSTR(opc, opv) clog(" %i:%s=%i '%s'", __LINE__, opc, opv, qstr_str(CTX.qst)) + #define VM_TRACE(opc, opv) clog(" %i:%s=%i", __LINE__, opc, opv) + + #define RAISE(o) do { \ + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o);\ + clog("%i@%s:exit on ex!", __LINE__, __FILE__);\ + goto exception_handler; } while (0) + + #define RAISE_IF(arg) if (arg) { clog("%i@%s:exit on ex!", __LINE__, __FILE__); goto exception_handler; } + +#else + #define VM_TRACE_QSTR(opc, opv) + #define VM_TRACE(opc, opv) + + #define RAISE(o) do { \ + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o);\ + goto exception_handler; } while (0) + + #define RAISE_IF(arg) if (arg) { goto exception_handler; } + +#endif + +#define VM_return(vm_ret_kind) { FRAME_LEAVE();\ +CTX.vm_return_kind = vm_ret_kind;\ +CTX.switch_break_for = 1;\ +break; } + + +#define RAISE_IF_NULL(arg) RAISE_IF(arg == MP_OBJ_NULL) +//#define THE_EXC the_exc +mp_obj_base_t * the_exc ; +//----------------------------------------- + + +#define EX_DECODE_ULABEL size_t ulab = (exip[0] | (exip[1] << 8)); exip += 2 + +#define VM_PUSH_EXC_BLOCK(with_or_finally) do { \ + VM_DECODE_ULABEL; /* except labels are always forward */ \ + ++CTX.exc_sp; \ + CTX.exc_sp->handler = CTX.ip + CTX.ulab; \ + CTX.exc_sp->val_sp = MP_TAGPTR_MAKE(CTX.sp, ((with_or_finally) << 1)); \ + CTX.exc_sp->prev_exc = NULL; \ +} while (0) + +#define VM_POP_EXC_BLOCK() \ + CTX.exc_sp--; /* pop back to previous exception handler */ \ + CLEAR_SYS_EXC_INFO() /* just clear sys.exc_info(), not compliant, but it shouldn't be used in 1st place */ + +#define VM_PUSH(val) (*++CTX.sp = (val)) +#define VM_POP() (*CTX.sp--) +#define VM_TOP() (*CTX.sp) +#define VM_SET_TOP(val) (*CTX.sp = (val)) + +#define VM_DECODE_UINT \ + mp_uint_t unum = 0; \ + do { \ + unum = (unum << 7) + (*CTX.ip & 0x7f); \ + } while ((*CTX.ip++ & 0x80) != 0) + +#define VM_DECODE_ULABEL CTX.ulab = (CTX.ip[0] | (CTX.ip[1] << 8)); CTX.ip += 2 +#define VM_DECODE_SLABEL CTX.slab = (CTX.ip[0] | (CTX.ip[1] << 8)) - 0x8000; CTX.ip += 2 + +#define VM_DECODE_QSTR \ + CTX.qst = CTX.ip[0] | CTX.ip[1] << 8; \ + CTX.ip += 2; + +#define VM_DECODE_PTR \ + VM_DECODE_UINT; \ + CTX.ptr = (void*)(uintptr_t)CTX.code_state->fun_bc->const_table[unum] + +#define VM_DECODE_OBJ \ + VM_DECODE_UINT; \ + mp_obj_t obj = (mp_obj_t)CTX.code_state->fun_bc->const_table[unum] + +#define LOCAL_NAME_ERROR() RAISE(mp_obj_new_exception_msg(&mp_type_NameError, MP_ERROR_TEXT("local variable referenced before assignment"))) + + +#if MICROPY_PY_SYS_EXC_INFO +#define CLEAR_SYS_EXC_INFO() MP_STATE_VM(cur_exception) = NULL; +#else +#define CLEAR_SYS_EXC_INFO() +#endif + + +static mp_obj_t obj_shared; +static int GOTO_OUTER_VM_DISPATCH = 1; + +def_mp_execute_bytecode: { + + // MUST BE SET CTX.inject_exc = MP_OBJ_NULL ; + + #if VM_OPT_COMPUTED_GOTO + #error "no wasm support for VM_OPT_COMPUTED_GOTO" + #else + #define VM_ENTRY(op) case op + #endif +//247 +#if MICROPY_STACKLESS +run_code_state: ; +#endif + +//252 +FRAME_ENTER() +#if MICROPY_STACKLESS +run_code_state_from_return: ; +#endif +FRAME_SETUP(); clog("~vm.c:258 VM(%d)run_code_state", ctx_current); +//257 + // Pointers which are constant for particular invocation of mp_execute_bytecode() + //mp_obj_t * /*const*/ fastn; mp_exc_stack_t * /*const*/ exc_stack; + { + size_t n_state = mp_decode_uint_value(CTX.code_state->fun_bc->bytecode); + CTX.fastn = &CTX.code_state->state[n_state - 1]; + CTX.exc_stack = (mp_exc_stack_t*)(CTX.code_state->state + n_state); + } +//266 + // variables that are visible to the exception handler (declared volatile) + // CTX.exc_sp = MP_TAGPTR_PTR(CTX.code_state->exc_sp); // stack grows up, exc_sp points to top of stack + CTX.exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(CTX.exc_stack, CTX.code_state->exc_sp_idx); +VM_DISPATCH_loop: + + if ( CTX.vmloop_state == VM_RESUMING ) { + //? restore what ? + clog("127:unwrap.c VM(%d)restored", ctx_current); + //CTX.ip = CTX.code_state->ip; + //CTX.sp = CTX.code_state->sp; + //obj_shared = CTX.obj_shared ; + CTX.vmloop_state = VM_RUNNING; + } + + if (GOTO_OUTER_VM_DISPATCH) { + clog("134:unwrap.c VM(%d)OUTER_DISPATCH for(){ ip:sp }", ctx_current); // loop to execute byte code + GOTO_OUTER_VM_DISPATCH = 0; + // local variables [that were not visible to the exception handler] + //static const byte *ip; + CTX.ip = CTX.code_state->ip; + //static mp_obj_t *sp; + CTX.sp = CTX.code_state->sp; + } + +#if VMTRACE +if (MP_STATE_THREAD(active_exception) != NULL) { + clog("160:ERROR EXCEPTION ON ENTER[%d]", ctx_current); +} else { + clog("162:unwrap.c VM(%d)DISPATCH->for()", ctx_current); // loop to execute byte code +} +#endif + + for (;;) { + + MICROPY_VM_HOOK_INIT + + // If we have exception to inject, now that we finish setting up + // execution context, raise it. This works as if RAISE_VARARGS + // bytecode was executed. + // Injecting exc into yield from generator is a special case, + // handled by MP_BC_YIELD_FROM itself + if ( (CTX.inject_exc != MP_OBJ_NULL) && (*CTX.ip != MP_BC_YIELD_FROM) ) { +clog("157:unwrap.c pending_exception to inject"); + mp_obj_t exc = CTX.inject_exc; + CTX.inject_exc = MP_OBJ_NULL; + exc = mp_make_raise_obj(exc); + RAISE(exc); + } + + // stores ip pointing to last opcode + CTX.code_state->ip = CTX.ip; + +#if VMTRACE +if (SHOW_OS_LOOP>0) { + #include "vmsl/vmbc_trace.c" +} +#endif + switch (*CTX.ip++) { + // opcode table, can be optimized later with computed goto + // if compiler does not already. + + #include "vmsl/vmswitch.c" + + } // switch + +/* + if return was requested the switch is broken and : + + - return value type is set in CTX.vm_return_kind + + - if (vm_return_kind == MP_VM_RETURN_NORMAL) then result is in CTX.code_state->sp + + - CTX.switch_break_for may be set to break from the for loop from here + +*/ + + if (CTX.switch_break_for) { +#if VMTRACE +clog(" 195:switch_break_for") +#endif + + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { +#if VMTRACE +clog(" 200:switch_break_for/vm_return_kind return value is set") +#endif + RETVAL = *CTX.code_state->sp ; + + // an exception : returned exception is in state[0] + } else if (CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) { +#if VMTRACE +clog(" 208:switch_break_for/vm_return_kind exception value is set") +#endif + RETVAL = CTX.code_state->state[0]; + } else { + clog(" 212:switch_break_for/unrouted vm_return_kind"); + } + break; // go to end of } // for loop + } + +//1 463 +//pending_exception_check: + MICROPY_VM_HOOK_LOOP + + #if MICROPY_ENABLE_SCHEDULER + // This is an inlined variant of mp_handle_pending + if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + 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 + // This is an inlined variant of mp_handle_pending + 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; + RAISE(obj); + } + #endif + + #if MICROPY_PY_THREAD_GIL + #if MICROPY_PY_THREAD_GIL_VM_DIVISOR + if (--gil_divisor == 0) + #endif + { + #if MICROPY_PY_THREAD_GIL_VM_DIVISOR + gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; + #endif + #if MICROPY_ENABLE_SCHEDULER + // can only switch threads if the scheduler is unlocked + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) + #endif + { + MP_THREAD_GIL_EXIT(); + MP_THREAD_GIL_ENTER(); + } + } + #endif + + + continue; + + + exception_handler: + // exception occurred +#if VMTRACE + assert( (MP_STATE_THREAD(active_exception)) != NULL); + clog(" 508:loop[%i] exit on EX!", ctx_current ); +#endif + // clear exception because we caught it + the_exc = (mp_obj_base_t *)MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + + #if MICROPY_PY_SYS_EXC_INFO + MP_STATE_VM(cur_exception) = the_exc; + #endif + + #if SELECTIVE_EXC_IP + // with selective ip, we store the ip 1 byte past the opcode, so move ptr back + CTX.code_state->ip -= 1; + #endif + +//1 569 + #if MICROPY_PY_SYS_SETTRACE + // Exceptions are traced here + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)the_exc)->type), MP_OBJ_FROM_PTR(&mp_type_Exception))) { + TRACE_TICK(CTX.code_state->ip, CTX.code_state->sp, true /* yes, it's an exception */); + } + #endif + + #if MICROPY_STACKLESS + unwind_loop: + #endif + // Set traceback info (file and line number) where the exception occurred, but not for: + // - constant GeneratorExit object, because it's const + // - exceptions re-raised by END_FINALLY + // - exceptions re-raised explicitly by "raise" + + if ( + (the_exc != &mp_const_GeneratorExit_obj.base) + && (*CTX.code_state->ip != MP_BC_END_FINALLY) + && (*CTX.code_state->ip != MP_BC_RAISE_LAST) + ) { +// same as vmbc_trace + const byte *ipval = CTX.code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ipval); + MP_BC_PRELUDE_SIZE_DECODE(ipval); + const byte *bytecode_start = ipval + n_info + n_cell; + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + size_t bc = CTX.code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr block_name = ipval[0] | (ipval[1] << 8); + qstr source_file = ipval[2] | (ipval[3] << 8); + ipval += 4; + #else + qstr block_name = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + qstr source_file = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + #endif + size_t source_line = mp_bytecode_get_source_line(ipval, bc); +// /vmbc_trace + mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(the_exc), source_file, source_line, block_name); + } +//1 615 + while ((CTX.exc_sp >= CTX.exc_stack) && (CTX.exc_sp->handler <= CTX.code_state->ip)) { + + // nested exception + + assert(CTX.exc_sp >= CTX.exc_stack); + + // TODO make a proper message for nested exception + // at the moment we are just raising the very last exception (the one that caused the nested exception) + + clog("// move up to previous exception handler"); // move up to previous exception handler + VM_POP_EXC_BLOCK(); + } + + if (CTX.exc_sp >= CTX.exc_stack) { + // catch exception and pass to byte code + +//PROBLEM HERE, ip was not set. + CTX.ip = (CTX.code_state->ip = CTX.exc_sp->handler); + mp_obj_t *sptr = MP_TAGPTR_PTR(CTX.exc_sp->val_sp); + // save this exception in the stack so it can be used in a reraise, if needed + CTX.exc_sp->prev_exc = the_exc; + // push exception object so it can be handled by bytecode + VM_PUSH(MP_OBJ_FROM_PTR(the_exc)); + CTX.code_state->sp = sptr; +#if VMTRACE + clog(" 638: where's my EX handler ???"); +#endif + + continue; + +//PROBLEM IS HERE + #if MICROPY_STACKLESS + + } else if (CTX.code_state->prev != NULL) { + mp_globals_set(CTX.code_state->old_globals); + mp_code_state_t *new_code_state = CTX.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(CTX.code_state, sizeof(mp_code_state_t)); + #endif + CTX.code_state = new_code_state; + size_t n_state = CTX.code_state->n_state; + CTX.fastn = &CTX.code_state->state[n_state - 1]; + CTX.exc_stack = (mp_exc_stack_t*)(CTX.code_state->state + n_state); + // variables that are visible to the exception handler (declared volatile) + CTX.exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(CTX.exc_stack, CTX.code_state->exc_sp_idx); // stack grows up, exc_sp points to top of stack + goto unwind_loop; + + #endif + } else { + // propagate exception to higher level + // Note: ip and sp don't have usable values at this point + CTX.code_state->state[0] = MP_OBJ_FROM_PTR(the_exc); // put exception here because sp is invalid + RETVAL = MP_OBJ_FROM_PTR(the_exc); + FRAME_LEAVE(); + CTX.vm_return_kind = MP_VM_RETURN_EXCEPTION; + clog("667: return EX"); + break; + } + + } // for loop + + CTX.switch_break_for = 0 ; + RETURN; + + + +} // def_mp_execute_bytecode diff --git a/ports/wapy-wasm/vmsl/vmbc_call_function.c b/ports/wapy-wasm/vmsl/vmbc_call_function.c new file mode 100644 index 000000000..9af8927c6 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_function.c @@ -0,0 +1,151 @@ + + + + + + + + + + + +//1022 + VM_ENTRY(MP_BC_CALL_FUNCTION): { + VM_TRACE("MP_BC_CALL_FUNCTION", MP_BC_CALL_FUNCTION); + + FRAME_UPDATE(); + VM_DECODE_UINT; + + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe); + #if MICROPY_STACKLESS + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*CTX.sp, unum & 0xff, (unum >> 8) & 0xff, CTX.sp + 1); + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; +/* +deep_recursion_error: + mp_raise_recursion_depth(); + RAISE_IF(true); +*/ + #endif //MICROPY_STACKLESS_STRICT + } else + #endif //!MICROPY_ENABLE_PYSTACK + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } else + clog("35:MP_BC_CALL_FUNCTION not mp_type_fun_bc"); + #endif //MICROPY_STACKLESS + //1058 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + const char *fn; + + + + + ctx_get_next(CTX_NEW); + NEXT.self_in = *CTX.sp; + NEXT.sp = CTX.sp; + NEXT.n_args = unum & 0xff; + NEXT.n_kw = (unum >> 8) & 0xff; + NEXT.args = CTX.sp + 1; + + + + if (!*CTX.sp) { + ctx_abort(); // 39 + clog("47:MP_BC_CALL_FUNCTION NPE"); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "Invalid Function Name->Pointer mapping called"); + mp_raise_o(obj); + RAISE_IF_NULL(VM_SET_TOP(MP_OBJ_NULL)); + continue; + } + + fn = qstr_str(mp_obj_fun_get_name(NEXT.self_in)); + + if ( strlen(fn)>1 ) { +#if DEBUG_BC + clog(" 99:MP_BC_CALL_FUNCTION [%s %zu '%s']",mp_obj_get_type_str(*CTX.sp), mp_obj_fun_get_name(*CTX.sp), fn); +#endif + GOSUB(def_mp_call_function_n_kw, fn ); + } else { +#if DEBUG_BC + clog(" 102:MP_BC_CALL_FUNCTION [%s %zu]",mp_obj_get_type_str(*CTX.sp), mp_obj_fun_get_name(*CTX.sp)); +#endif + GOSUB(def_mp_call_function_n_kw, mp_obj_get_type_str(NEXT.self_in) ); + } + + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("120:MP_BC_CALL_FUNCTION[%s]/exit on EX!", fn); + } + + RAISE_IF_NULL(VM_SET_TOP(SUBVAL)); + + continue; + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c b/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c new file mode 100644 index 000000000..ba8ba61ee --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c @@ -0,0 +1,68 @@ +//1062 OK+ +VM_ENTRY(MP_BC_CALL_FUNCTION_VAR_KW): { + FRAME_UPDATE(); + VM_DECODE_UINT; + VM_TRACE("MP_BC_CALL_FUNCTION_VAR_KW", MP_BC_CALL_FUNCTION_VAR_KW); + + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + // We have following stack layout here: + // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2; + #if MICROPY_STACKLESS + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(false, unum, CTX.sp, &out_args); + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, + out_args.n_args, out_args.n_kw, out_args.args); + #if !MICROPY_ENABLE_PYSTACK + // Freeing args at this point does not follow a LIFO order so only do it if + // pystack is not enabled. For pystack, they are freed when code_state is. + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + #endif + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } + #endif + + ctx_get_next(CTX_NEW); + + static mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(false, unum, CTX.sp, &out_args); + + + NEXT.args = out_args.args; + NEXT.alloc = out_args.n_alloc * sizeof(mp_obj_t) ; + + NEXT.self_in = out_args.fun; + NEXT.n_args = out_args.n_args; + NEXT.n_kw = out_args.n_kw; + +// DO NOT USE LINE 57 (GOSUB from BC_CALL_METHOD) + + GOSUB(def_mp_call_function_n_kw, "BC_CALL_FUNCTION_VAR_KW"); + + if (CTX.sub_alloc) + mp_nonlocal_free(CTX.sub_args, CTX.sub_alloc); + + VM_SET_TOP(SUBVAL); + continue; +} diff --git a/ports/wapy-wasm/vmsl/vmbc_call_method.c b/ports/wapy-wasm/vmsl/vmbc_call_method.c new file mode 100644 index 000000000..9f16edd54 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_method.c @@ -0,0 +1,83 @@ +// OK+ + +VM_ENTRY(MP_BC_CALL_METHOD): { + VM_DECODE_UINT; + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1; + + #if MICROPY_STACKLESS + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + size_t n_args = unum & 0xff; + size_t n_kw = (unum >> 8) & 0xff; + int adjust = (CTX.sp[1] == MP_OBJ_NULL) ? 0 : 1; + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*CTX.sp, n_args + adjust, n_kw, CTX.sp + 2 - adjust); + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } else { + clog("38:MP_BC_CALL_METHOD native"); + native = 1; + } + + #endif + + ctx_get_next(CTX_NEW); + NEXT.args = CTX.sp ; + + NEXT.self_in = NEXT.args[0]; + NEXT.n_args = unum & 0xff; + NEXT.n_kw = (unum >> 8) & 0xff; + + { + int adjust = (NEXT.args[1] == MP_OBJ_NULL) ? 0 : 1; + NEXT.n_args += adjust; + NEXT.args += 2 - adjust; + } + + + + +//57: DO NOT USE LINE 57 FOR SUB + + + + + + + + + + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + qstr FUN_QSTR = mp_obj_fun_get_name( NEXT.self_in ); + if (FUN_QSTR != MP_QSTR_ ) { + GOSUB(def_mp_call_function_n_kw, qstr_str(FUN_QSTR) ); + } else { + GOSUB(def_mp_call_function_n_kw, "BC_CALL_METHOD(?name?)" ); + } + } else { + GOSUB(def_mp_call_function_n_kw, "BC_CALL_METHOD(native)" ); + } + + VM_SET_TOP(SUBVAL); + continue; +} + + diff --git a/ports/wapy-wasm/vmsl/vmbc_for_iter.c b/ports/wapy-wasm/vmsl/vmbc_for_iter.c new file mode 100644 index 000000000..ef0aaee1e --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_for_iter.c @@ -0,0 +1,86 @@ +VM_ENTRY(MP_BC_FOR_ITER): { + FRAME_UPDATE(); + VM_DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward + + CTX.code_state->sp = CTX.sp; + + ctx_get_next(CTX_COPY); + NEXT.return_value = MP_OBJ_NULL;; + NEXT.send_value = mp_const_none; + NEXT.throw_value = MP_OBJ_NULL; + + if (CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] == MP_OBJ_NULL) { + NEXT.self_in = CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 2]; + } else { + NEXT.self_in = MP_OBJ_FROM_PTR(&CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1]); + } +// ============= mp_obj_t value = mp_iternext_allow_raise(obj); =============== +// unwrap from runtime_no_nlr.c:1259 + const mp_obj_type_t *type = mp_obj_get_type(NEXT.self_in); + + if (type->iternext != NULL) { + + clog(">>> BC_FOR_ITER:gen_instance_iternext\n" ); + RETVAL = type->iternext(NEXT.self_in); + + if ( (void*)*type->iternext == &gen_instance_iternext ) { + clog(">>> BC_FOR_ITER:type->iternext\n" ); +#if 0 + static mp_vm_return_kind_t mpsl_obj_gen_resume; + + #include "vm/mpsl_obj_gen_resume.c" + + switch (mpsl_obj_gen_resume) { + case MP_VM_RETURN_NORMAL: + default: + // Optimize return w/o value in case generator is used in for loop + if (return_value == mp_const_none || return_value == MP_OBJ_STOP_ITERATION) { + return_value = MP_OBJ_STOP_ITERATION; + break; + } else { + nlr_raise(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &return_value)); + break; + } + + case MP_VM_RETURN_YIELD: + //return_value = return_value; + break; + + case MP_VM_RETURN_EXCEPTION: + nlr_raise(return_value); + } + + +#endif + clog("<<< BC_FOR_ITER:type->iternext_return\n"); + } + + clog("<<< BC_FOR_ITER:gen_instance_iternext_return\n" ); + + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(NEXT.self_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result +clog(">>> BC_FOR_ITER:mp_call_method_n_kw\n"); + RETVAL = mp_call_method_n_kw(0, 0, dest); +clog("<<< BC_FOR_ITER:mp_call_method_n_kw_return\n"); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't an iterator", mp_obj_get_type_str(NEXT.self_in))); + } + } +// runtime_no_nlr.c:1280 +//========================== end mp_iternext_allow_raise(mp_obj_t o_in) ================== + + if (RETVAL == MP_OBJ_STOP_ITERATION) { + CTX.sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator + CTX.ip += CTX.ulab; // jump to after for-block + } else { + VM_PUSH(RETVAL); // push the next iteration value + } + ctx_abort(); + continue; +} + diff --git a/ports/wapy-wasm/vmsl/vmbc_import.c b/ports/wapy-wasm/vmsl/vmbc_import.c new file mode 100644 index 000000000..8e12d0275 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_import.c @@ -0,0 +1,102 @@ +// OK+ + +#ifndef MICROPY_CAN_OVERRIDE_BUILTINS +#define MICROPY_CAN_OVERRIDE_BUILTINS 1 +#endif + +//1364 +VM_ENTRY(MP_BC_IMPORT_NAME): { + FRAME_UPDATE(); + VM_DECODE_QSTR; // qst => import [name] + +#if DEBUG_BC + clog("BC_IMPORT_NAME(%d:%d): [%s]\n", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); +#endif + + ctx_get_next(CTX_COPY); + //NEXT.qst = CTX.qst; + NEXT.n_args = 5 ; + NEXT.n_kw = 0; + NEXT.argv[0] = MP_OBJ_NEW_QSTR(CTX.qst) ; + NEXT.argv[1] = mp_const_none; // TODO should be globals + NEXT.argv[2] = mp_const_none; // TODO should be globals + NEXT.argv[3] = VM_POP(); // fromlist + NEXT.argv[4] = VM_TOP(); + + NEXT.args = &NEXT.argv[0] ; //implicit + + //if ( !strcmp(qstr_str(CTX.qst),"syscall") ) + // clog(" BC_IMPORT_NAME(%d:%d) import %s->pause", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); + + + #if MICROPY_CAN_OVERRIDE_BUILTINS + { + mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); + // lookup __import__ and call that instead of going straight to builtin implementation + if (bo_dict != NULL) { + mp_map_elem_t *cust_imp = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); + if (cust_imp != NULL) { + NEXT.self_in = cust_imp->value ; + GOSUB(def_mp_call_function_n_kw, qstr_str(CTX.qst)); + + goto RET_import_name; + } // no continuation + } + } + #endif + + SUBVAL = mp_builtin___import__(5, NEXT.argv); + + //ctx_release(); + + RET_import_name: + // not a sub, here we don't return + RETVAL = SUBVAL ; //VM_SET_TOP( CTX.sub_value ); + VM_SET_TOP( RETVAL ); + + RAISE_IF_NULL( RETVAL ); + // TODO:CTX + + if ( !strcmp(qstr_str(CTX.qst),"aio_suspend") ) { + //clog(" BC_IMPORT_NAME(%d:%d) import %s->pause", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); + BRANCH(VM_syscall, VMOP_PAUSE, VM_DISPATCH_loop, "BC_IMPORT_NAME(aio_suspend)"); + } + + continue; +} + +//1374 +VM_ENTRY(MP_BC_IMPORT_FROM): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_obj_t obj = mp_import_from(VM_TOP(), CTX.qst); + RAISE_IF_NULL(obj); + VM_PUSH(obj); + continue; +} + +//1386 +VM_ENTRY(MP_BC_IMPORT_STAR): { + mp_import_all(VM_POP()); + continue; +} + + + + + + + + + + + + + + + + + + + +// diff --git a/ports/wapy-wasm/vmsl/vmbc_raise.c b/ports/wapy-wasm/vmsl/vmbc_raise.c new file mode 100644 index 000000000..ce2ad46f4 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_raise.c @@ -0,0 +1,28 @@ + +//1266 + VM_ENTRY(MP_BC_RAISE_LAST): { + // search for the inner-most previous exception, to reraise it + mp_obj_t obj = MP_OBJ_NULL; + for (mp_exc_stack_t *e = CTX.exc_sp; e >= CTX.exc_stack; --e) { + if (e->prev_exc != NULL) { + obj = MP_OBJ_FROM_PTR(e->prev_exc); + break; + } + } + if (obj == MP_OBJ_NULL) { + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); + } + RAISE(obj); + } +//1282 + VM_ENTRY(MP_BC_RAISE_OBJ): { + mp_obj_t obj = mp_make_raise_obj(VM_TOP()); + RAISE(obj); + } +//1288 + VM_ENTRY(MP_BC_RAISE_FROM): { + mp_warning(NULL, "exception chaining not supported"); + CTX.sp--; // ignore (pop) "from" argument + mp_obj_t obj = mp_make_raise_obj(VM_TOP()); + RAISE(obj); + } diff --git a/ports/wapy-wasm/vmsl/vmbc_trace.c b/ports/wapy-wasm/vmsl/vmbc_trace.c new file mode 100644 index 000000000..a6e6917bf --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_trace.c @@ -0,0 +1,30 @@ + const byte *ipval = CTX.code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ipval); + MP_BC_PRELUDE_SIZE_DECODE(ipval); + const byte *bytecode_start = ipval + n_info + n_cell; + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + size_t bc = CTX.code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr source_file = ipval[2] | (ipval[3] << 8); + ipval += 4; + #else + ipval = mp_decode_uint_skip(ipval); + qstr source_file = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + #endif + size_t source_line = mp_bytecode_get_source_line(ipval, bc); + static size_t last_line = 0; + + if (source_line!=last_line) { +#if TRACE_ON + cdbg("\nbc:%i ctx=%i %s:%zu", *CTX.ip , ctx_current,qstr_str(source_file), source_line); +#else + clog("\nbc:%i ctx=%i %s:%zu", *CTX.ip , ctx_current,qstr_str(source_file), source_line); +#endif + + last_line = source_line; + } + diff --git a/ports/wapy-wasm/vmsl/vmbclookup.c b/ports/wapy-wasm/vmsl/vmbclookup.c new file mode 100644 index 000000000..dce31ca11 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbclookup.c @@ -0,0 +1,78 @@ +//385 + VM_ENTRY(MP_BC_LOAD_NAME): { + VM_DECODE_QSTR; + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_locals_get()->map, CTX.qst, (uint8_t*)CTX.ip); + + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_name(CTX.qst); + RAISE_IF_NULL(obj); + } + VM_PUSH(obj); + CTX.ip++; + continue; + } + +//413 + VM_ENTRY(MP_BC_LOAD_GLOBAL): { + VM_DECODE_QSTR; + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_globals_get()->map, CTX.qst, (uint8_t*)CTX.ip); + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_global(CTX.qst); + RAISE_IF_NULL(obj); + } + VM_PUSH(obj); + CTX.ip++; + continue; + } + +//442 + VM_ENTRY(MP_BC_LOAD_ATTR): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_obj_t top = VM_TOP(); + 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, CTX.qst, (uint8_t*)CTX.ip); + } + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_attr(top, CTX.qst); + RAISE_IF_NULL(obj); + } + VM_SET_TOP(obj); + CTX.ip++; + continue; + } + + // This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or + // MICROPY_PY_DESCRIPTORS enabled because if the attr exists in + // 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. + VM_ENTRY(MP_BC_STORE_ATTR): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_map_elem_t *elem = NULL; + mp_obj_t top = VM_TOP(); + if (mp_obj_is_instance_type(mp_obj_get_type(top)) && CTX.sp[-1] != MP_OBJ_NULL) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); + elem = mp_map_cached_lookup(&self->members, CTX.qst, (uint8_t*)CTX.ip); + } + if (elem != NULL) { + elem->value = CTX.sp[-1]; + } else { + RAISE_IF_NULL(mp_store_attr(CTX.sp[0], CTX.qst, CTX.sp[-1])); + } + CTX.sp -= 2; + CTX.ip++; + continue; + } diff --git a/ports/wapy-wasm/vmsl/vmconf.h b/ports/wapy-wasm/vmsl/vmconf.h new file mode 100644 index 000000000..c2e2bf30d --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmconf.h @@ -0,0 +1,32 @@ +//TODO sys max recursion handling. +#define SYS_MAX_RECURSION 128 +#define MAX_BRANCHING 128 + +//order matters +#define VM_IDLE 0 +#define VM_WARMUP 1 +#define VM_RUNNING 2 +#define VM_RESUMING 3 +#define VM_AIO 4 +#define VM_PAUSED 5 +#define VM_SYSCALL 6 +#define VM_HCF 7 + +static int VMOP = -1; + +#define VMOP_NONE 0 +#define VMOP_INIT 1 +#define VMOP_WARMUP 2 +#define VMOP_CALL 3 +#define VMOP_CRASH 4 +#define VMOP_AIO 5 +#define VMOP_PAUSE 6 +#define VMOP_SYSCALL 7 + +static int sub_tracking = 0; + +static int ctx_current = 1; +static int ctx_next = 2; + + + diff --git a/ports/wapy-wasm/vmsl/vmreg.c b/ports/wapy-wasm/vmsl/vmreg.c new file mode 100644 index 000000000..d045dcbd5 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmreg.c @@ -0,0 +1,331 @@ +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define STRINGIFY2(a, b) a ## b +#define CONCAT(x, y) STRINGIFY2(x, y) + + + +#ifndef VM_REG_H + //TODO sys max recursion handling. + #define SYS_MAX_RECURSION 32 + #define MAX_BRANCHING 128 + + //order matters + #define VM_IDLE 0 + #define VM_WARMUP 1 + #define VM_RUNNING 2 + #define VM_RESUMING 3 + #define VM_PAUSED 4 + #define VM_SYSCALL 5 + #define VM_HCF 6 + + static int VMOP = -1; + + #define VMOP_NONE 0 + #define VMOP_INIT 1 + #define VMOP_WARMUP 2 + #define VMOP_CALL 3 + #define VMOP_CRASH 4 + #define VMOP_PAUSE 5 + #define VMOP_SYSCALL 6 + + static int sub_tracking = 0; + + static int ctx_current = 1; + static int ctx_next = 2; + + + + #define clog(...) { fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } + + struct mp_registers { + int sub_id ; + //who created us + int parent ; + //who did we create and was running last + int childcare ; + + // execution path + int pointer; + int switch_break_for; + int vmloop_state; + }; + + static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + + void mp_new_interpreter(void * mpi, int ctx, int parent, int childcare) { + + mpi_ctx[ctx].parent = parent; + mpi_ctx[ctx].childcare = childcare; + // + mpi_ctx[ctx].pointer = 0; + mpi_ctx[ctx].sub_id = sub_tracking++; + mpi_ctx[ctx].vmloop_state = VM_IDLE; + + } +#endif + + +#define JMP_NONE (void*)0 +#define TYPE_JUMP 0 +#define TYPE_SUB 1 + +#define JUMPED_IN reached_point[CTX.pointer] +#define JUMP_TYPE type_point[CTX.pointer] +#define ENTRY_POINT entry_point[CTX.pointer] +#define EXIT_POINT exit_point[CTX.pointer] + +static int point_ptr = 0; // index 0 is reserved for no branching + +static int come_from[MAX_BRANCHING]; +static int type_point[MAX_BRANCHING]; +static int reached_point[MAX_BRANCHING]; +static void* entry_point[MAX_BRANCHING]; +static void* exit_point[MAX_BRANCHING]; + + +//need global interrupt state marker ( @syscall / @awaited ) to choose which VM to enter +// this is different from ctx interrupts ctx_if ( if used ) + +int VMFLAGS_IF = 0; + +static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + +#define CTX mpi_ctx[ctx_current] +#define NEXT mpi_ctx[ctx_next] +#define PARENT mpi_ctx[CTX.parent] + +#define CTX_STATE CTX.vmloop_state +#define NEXT_STATE NEXT.vmloop_state + + +static void* crash_point = JMP_NONE; + +void *crash(const char *panic){ + clog("%s", panic); + VMOP = VMOP_CRASH; + return crash_point; + +} + +#define FATAL(msg) goto *crash(msg) + +#if VM_FULL + #define SUBVAL CTX.sub_value + #define RETVAL CTX.return_value +#endif + +#define RETURN goto *ctx_return() +#define COME_FROM goto *ctx_come_from() + + + +void ctx_free(){ + clog("CTX %d freed(free) for %d", ctx_current, CTX.parent); + CTX_STATE = VM_IDLE; +} + +void ctx_abort(){ + clog("CTX %d freed(abort) for %d", ctx_next, ctx_current); + NEXT_STATE = VM_IDLE; +} + + +static int come_from[MAX_BRANCHING]; + +#define deferred 1 + +void zigzag(void* jump_entry, void* jump_back, int jump_type){ + come_from[++point_ptr] = CTX.pointer; // when set to 0 its origin mark. + CTX.pointer = point_ptr; + JUMPED_IN = 0; + ENTRY_POINT = jump_entry; + EXIT_POINT = jump_back; + JUMP_TYPE = jump_type; +} + + + + +#define CTX_COPY 1 +#define CTX_NEW 0 + +// prepare a child context for a possibly recursive call, +// NEXT.* will then gives access to that ctx (inited from CTX if CTX_COPY ) until GOSUB() +// after GOSUB CTX is restored automaticallly to parent and return value is in CTX.sub_value + +#if VM_FULL +void ctx_get_next(int copy) { + //push + int ctx; + for (ctx=3; ctx 2) && (CTX.code_state == NULL)) + clog(" ======== no code_state for slot %i->%i ============", ctx_current, ctx_next); + + NEXT.self_in = CTX.self_in; + NEXT.self_fun = CTX.self_fun; + NEXT.ip = CTX.ip; + NEXT.sp = CTX.sp; + NEXT.exc_stack = CTX.exc_stack; + NEXT.n_args = CTX.n_args; + NEXT.n_kw = CTX.n_kw; + NEXT.args = CTX.args; +//? + NEXT.n_state = CTX.n_state; + NEXT.state_size = CTX.state_size; + NEXT.inject_exc = CTX.inject_exc; + } +} +#endif + + +void ctx_switch(){ + NEXT_STATE = VM_RUNNING; + ctx_current = ctx_next; + #if CTX_DEBUG + clog("CTX %d locked for %d",ctx_current,CTX.parent); + #endif +} + +void* ctx_branch(void* jump_entry,int vmop, void *jump_back, const char *jto, const char *jback, const char* context, int defer) { + VMOP = vmop; + zigzag(jump_entry, jump_back, TYPE_JUMP); + clog(" ZZ > %s(...) %s -> %s @%d",context, jto, jback, ctx_current); + if (!defer) + JUMPED_IN = 1 ; + return jump_entry; +} + +#define BRANCH(arg0, vmop, arg1, arg2) goto *ctx_branch(&&arg0, vmop, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2, !deferred) + +#define SYSCALL(arg0, vmop, arg1, arg2) \ +{\ + ctx_branch(&&arg0, vmop, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2, deferred);\ + goto VM_syscall;\ +} + + +void* ctx_call(void* jump_entry, void *jump_back, const char *jt_origin,const char *context, int defer) { + VMOP = VMOP_CALL; + zigzag(jump_entry, jump_back, TYPE_JUMP); + clog(" CC > %s->%s(...) @%d", context, jt_origin, ctx_current); + if (!defer) + JUMPED_IN = 1 ; + return jump_entry; +} + +#define CALL(arg0, caller) \ +{\ + ctx_call(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), caller, deferred);\ + goto VM_syscall;\ + CONCAT(call_,__LINE__):;\ +} + +#define JUMP(arg0, caller) \ +{\ + goto *ctx_call(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), caller, !deferred);\ + CONCAT(call_,__LINE__):;\ +} + +void* ctx_sub(void* jump_entry, void* jump_back, const char* jto, const char* jback, const char* context) { + // set the new context so zigzag @ are set + ctx_switch(); + + zigzag(jump_entry, jump_back, TYPE_SUB); + clog(" Begin[%d:%d] %s(...) %s -> %s %d->%d", ctx_current, CTX.sub_id, context, jto, jback, CTX.parent, ctx_current); + JUMPED_IN = 1 ; + return jump_entry; +} + +//#define GOSUB(arg0, arg1, arg2) goto *ctx_sub(&&arg0, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2 ) + +#define GOSUB(arg0, caller) \ +{\ + goto *ctx_sub(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), TOSTRING(CONCAT(call_,__LINE__)), caller ) ;\ + CONCAT(call_,__LINE__):;\ +} + + +void* ctx_come_from() { + // just going back from branching + int ptr_back = come_from[CTX.pointer]; + + void* return_point; + + if (JUMP_TYPE!=TYPE_JUMP) + return crash("BRANCH EXIT in SUB CONTEXT"); + + return_point = EXIT_POINT; + + if (point_ptr!=CTX.pointer) { + return crash("ERROR: jumping back from upper branch not allowed, maybe ctx_get_next missing for GOSUB ?"); + } else + point_ptr--; + clog("<<< ZZ[%d]",ctx_current); + // go up one level of branching ( or if 0 reach root ) + CTX.pointer = ptr_back; + return return_point; +} + + + +void* ctx_return(){ + void* return_point; + int ptr_back = come_from[CTX.pointer]; + +#if VM_FULL + PARENT.sub_vm_return_kind = CTX.vm_return_kind; + PARENT.sub_value = CTX.return_value; + PARENT.sub_alloc = CTX.alloc; + PARENT.sub_args = CTX.args ; +#endif + + // possibly return in upper branch ? + if (point_ptr!=CTX.pointer) { + clog("ERROR not on leaf branch"); + emscripten_cancel_main_loop(); + } else { + point_ptr -= 1; + } + return_point = exit_point[CTX.pointer]; + + + // not a zigzag branching free registers + if (!ptr_back) + ctx_free(); + + // not a zigzag go upper context + if ( JUMP_TYPE == TYPE_SUB) + ctx_current = CTX.parent; + else + return crash("239:ERROR: RETURN in non SUB BRANCH"); + + return return_point; +} + + + + + + diff --git a/ports/wapy-wasm/vmsl/vmreg.h b/ports/wapy-wasm/vmsl/vmreg.h new file mode 100644 index 000000000..6e86d8c3f --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmreg.h @@ -0,0 +1,217 @@ +#include "py/obj.h" +#include "py/runtime.h" + +//#include "lib/bipbuffer/bipbuffer.h" +//#include "lib/bipbuffer/bipbuffer.c" + + +typedef struct _mp_obj_closure_t { + mp_obj_base_t base; + mp_obj_t fun; + size_t n_closed; + mp_obj_t closed[]; +} mp_obj_closure_t; + + +typedef struct _mp_obj_gen_instance_t { + mp_obj_base_t base; + mp_obj_dict_t *globals; + mp_code_state_t code_state; +} mp_obj_gen_instance_t; + +#include "vmsl/vmconf.h" + +struct mp_registers { + // builtins override dict ? + // __main__ ? + // sys.(std*) ? + // sys.argv + + // cpu time load stats ? + + //who created us + int parent ; + + // + int sub_id ; + + //who did we create and was running last + int childcare ; + + int vmloop_state; + nlr_buf_t nlr; + + mp_lexer_t *lex; + mp_reader_t reader; + + qstr source_name; + mp_parse_tree_t parse_tree; + + mp_obj_t * /*const*/ fastn; + mp_exc_stack_t * /*const*/ exc_stack; + + size_t n_state; + size_t state_size; + +//bytecode switch case + + volatile mp_obj_t inject_exc; + mp_code_state_t *code_state; + mp_exc_stack_t *volatile exc_sp; + + +// execution path + int pointer; + int switch_break_for; + + +// parent return state + size_t ulab; + size_t slab; + mp_obj_t *sp; + const byte *ip; + mp_obj_t obj_shared; + void *ptr; + +// mp_import state + qstr qst; + mp_obj_t argv[5]; + + +// mp_call_function_n_kw, closure_call and iterators state + mp_obj_t self_in; + mp_obj_fun_builtin_var_t *self_fb; + mp_obj_fun_bc_t *self_fun; + mp_obj_closure_t *self_clo; + + int alloc; + + //mp_obj_type_t *type; + + size_t n_args; + size_t n_kw; + //const + mp_obj_t *args; + + mp_obj_gen_instance_t* generator; + mp_obj_t send_value, throw_value, return_value; + + mp_obj_t sub_value; // child ctx computed value. + mp_obj_t *sub_args ; // child allocated memory ptr + int sub_alloc ; // child allocated memory size + mp_vm_return_kind_t sub_vm_return_kind; // child result on mp_execute_bytecode() calls ( can be recursive ) + + mp_vm_return_kind_t vm_return_kind; + // last + //mp_obj_gen_instance_t self_gen; +}; + +static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + +void +mp_new_interpreter(void * mpi, int ctx, int parent, int childcare) { + mpi_ctx[ctx].vmloop_state = VM_IDLE; + mpi_ctx[ctx].parent = parent; + mpi_ctx[ctx].childcare = childcare; + mpi_ctx[ctx].pointer = 0; + mpi_ctx[ctx].code_state = NULL ; + //? + mpi_ctx[ctx].state_size = 0; + mpi_ctx[ctx].n_state = 0; + //? + mpi_ctx[ctx].n_args = 0; + mpi_ctx[ctx].n_kw = 0; + mpi_ctx[ctx].args = &mpi_ctx[ctx].argv[0]; + mpi_ctx[ctx].alloc = 0; + mpi_ctx[ctx].switch_break_for = 0; + mpi_ctx[ctx].sub_id = sub_tracking++; + +} + +#define VM_FULL 1 + + +struct mp_stack { + int gen_i; + int gen_n; + int has_default; + int default_value; + int __line__ ; + const char *name; + const char *value; // some in memory rpc struct +}; + + +// Duff's device thanks a lot to https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + + +#define Begin switch(generator_context->__line__) { case 0: + +#define yield(x)\ +do {\ + generator_context->__line__ = __LINE__;\ + return x; \ +case __LINE__:; } while (0) + +#define End }; + + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +// static int enumerator=0; +#define vars(generator_name) static struct mp_stack generator_context = { .gen_i=0, .gen_n = 1, .has_default = 0, .default_value = 666, .__line__ = 0, .name=TOSTRING(generator_name) } + +#define async_def(generator_name, gen_type, ...) \ + gen_type generator_name(struct mp_stack *generator_context) { \ + Begin; \ + __VA_ARGS__ \ + End;\ + generator_context->gen_n = 0;\ + return generator_context->default_value;\ + }\ + +#define async_iter(generator_name) \ + vars(generator_name);\ + if (generator_context.gen_n) generator_context.gen_i=generator_name(&generator_context); \ + if (generator_context.gen_n) + +#define async_next(generator_name, defval) \ + vars(generator_name); if (!generator_context.has_default) { generator_context.has_default = 1; generator_context.default_value = defval; }\ + int had_next = generator_context.gen_n; \ + if (had_next) generator_context.gen_i = generator_name(&generator_context); \ + if (had_next) + + +// -------------------------- attempt to be pythonic -------------------- +#define self (*generator_context) +#define iterator generator_context->gen_i +#define next generator_context->gen_n + + +async_def(gen1, int, { + while (next) { + printf("gen1 %s ",self.name); + if (iterator++ == 10) break; + yield(iterator); + } +}); + + +async_def(gen2, int, { + for (iterator = 0; iterator < 5; iterator++) { + printf("gen2 %s ",self.name); + yield(iterator); + } +}); + +#undef self +#undef iterator +#undef next + +#define iterator generator_context.gen_i + +// ------------------------------------------------------------------------ + + +#define VM_REG_H 1 diff --git a/ports/wapy-wasm/vmsl/vmswitch.c b/ports/wapy-wasm/vmsl/vmswitch.c new file mode 100644 index 000000000..86c2cc2ce --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmswitch.c @@ -0,0 +1,979 @@ +//311 + + VM_ENTRY(MP_BC_LOAD_CONST_FALSE): { + VM_PUSH(mp_const_false); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_NONE): { + VM_PUSH(mp_const_none); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_TRUE): { + VM_PUSH(mp_const_true); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_SMALL_INT): { + mp_int_t num = 0; + if ((CTX.ip[0] & 0x40) != 0) { + // Number is negative + num--; + } + do { num = (num << 7) | (*CTX.ip & 0x7f); } while ((*CTX.ip++ & 0x80) != 0); + VM_PUSH(MP_OBJ_NEW_SMALL_INT(num)); + continue; + } +//339 + VM_ENTRY(MP_BC_LOAD_CONST_STRING): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_CONST_STRING", MP_BC_LOAD_CONST_STRING); + + VM_PUSH(MP_OBJ_NEW_QSTR(CTX.qst)); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_OBJ): { + VM_DECODE_OBJ; + VM_TRACE("MP_BC_LOAD_CONST_OBJ", MP_BC_LOAD_CONST_OBJ); + + VM_PUSH(obj); + continue; + } + + VM_ENTRY(MP_BC_LOAD_NULL): { + VM_PUSH(MP_OBJ_NULL); + continue; + } + + VM_ENTRY(MP_BC_LOAD_FAST_N): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_LOAD_FAST_N", MP_BC_LOAD_FAST_N); + + obj_shared = CTX.fastn[-unum]; + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + // no continuation + + VM_PUSH(obj_shared); + continue; + } + + VM_ENTRY(MP_BC_LOAD_DEREF): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_LOAD_DEREF", MP_BC_LOAD_DEREF); + + obj_shared = mp_obj_cell_get(CTX.fastn[-unum]); + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + // no continuation + + VM_PUSH(obj_shared); + continue; + } + + #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +//378 + VM_ENTRY(MP_BC_LOAD_NAME): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_NAME", MP_BC_LOAD_NAME); + VM_PUSH(mp_load_name(CTX.qst)); + RAISE_IF_NULL(VM_TOP()); + continue; //? DISPATCH() ? + } +//406 + VM_ENTRY(MP_BC_LOAD_GLOBAL): { + VM_DECODE_QSTR; +if (MP_STATE_THREAD(active_exception) != NULL) clog("EXEXEXEXEXEXEXEXEXEXEXEXEXEXEXEXE 1"); + VM_TRACE_QSTR("MP_BC_LOAD_GLOBAL", MP_BC_LOAD_GLOBAL); + VM_PUSH(mp_load_global(CTX.qst)); +if (MP_STATE_THREAD(active_exception) != NULL) clog("EXEXEXEXEXEXEXEXEXEXEXEXEXEXEXEXE 2"); + RAISE_IF_NULL(VM_TOP()); + + continue; //? DISPATCH() ? + } +//433 + VM_ENTRY(MP_BC_LOAD_ATTR): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_ATTR", MP_BC_LOAD_ATTR); + + VM_SET_TOP(mp_load_attr(VM_TOP(), CTX.qst)); + RAISE_IF_NULL(VM_TOP()); + continue; //? DISPATCH() ? + } +//522 + VM_ENTRY(MP_BC_STORE_ATTR): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_ATTR",MP_BC_STORE_ATTR); + + RAISE_IF_NULL( mp_store_attr(CTX.sp[0], CTX.qst, CTX.sp[-1]) ); + CTX.sp -= 2; + continue; + } + + #else + #include "vmsl/vmbclookup.c" + #endif + +//467 + VM_ENTRY(MP_BC_LOAD_METHOD): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_METHOD", MP_BC_LOAD_METHOD); + + RAISE_IF_NULL( mp_load_method(*CTX.sp, CTX.qst, CTX.sp) ); + CTX.sp += 1; + continue; + } + + VM_ENTRY(MP_BC_LOAD_SUPER_METHOD): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_SUPER_METHOD", MP_BC_LOAD_SUPER_METHOD); + CTX.sp -= 1; + RAISE_IF_NULL( mp_load_super_method(CTX.qst, CTX.sp - 1) ); + continue; + } + + VM_ENTRY(MP_BC_LOAD_BUILD_CLASS): + VM_PUSH(mp_load_build_class()); + continue; + + VM_ENTRY(MP_BC_LOAD_SUBSCR): { + VM_TRACE("MP_BC_LOAD_SUBSCR", MP_BC_LOAD_SUBSCR); + mp_obj_t index = VM_POP(); + VM_SET_TOP(mp_obj_subscr(VM_TOP(), index, MP_OBJ_SENTINEL)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_FAST_N): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_STORE_FAST_N", MP_BC_STORE_FAST_N); + CTX.fastn[-unum] = VM_POP(); + continue; + } +//502 + VM_ENTRY(MP_BC_STORE_DEREF): { + VM_DECODE_UINT; + mp_obj_cell_set(CTX.fastn[-unum], VM_POP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_NAME): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_NAME", MP_BC_STORE_NAME); + mp_store_name(CTX.qst, VM_POP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_GLOBAL): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_GLOBAL", MP_BC_STORE_GLOBAL); + mp_store_global(CTX.qst, VM_POP()); + continue; + } + + +//558 + VM_ENTRY(MP_BC_STORE_SUBSCR): { + RAISE_IF_NULL( mp_obj_subscr(CTX.sp[-1], CTX.sp[0], CTX.sp[-2]) ); + CTX.sp -= 3; + continue; + } + +//564 + VM_ENTRY(MP_BC_DELETE_FAST): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_DELETE_FAST", MP_BC_DELETE_FAST); + if (CTX.fastn[-unum] == MP_OBJ_NULL) { + LOCAL_NAME_ERROR(); //goto local_name_error; + } + CTX.fastn[-unum] = MP_OBJ_NULL; + continue; + } +//574 + VM_ENTRY(MP_BC_DELETE_DEREF): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_DELETE_DEREF", MP_BC_DELETE_DEREF); + if (mp_obj_cell_get(CTX.fastn[-unum]) == MP_OBJ_NULL) { + LOCAL_NAME_ERROR(); //goto local_name_error; + } + mp_obj_cell_set(CTX.fastn[-unum], MP_OBJ_NULL); + continue; + } + + VM_ENTRY(MP_BC_DELETE_NAME): { + VM_DECODE_QSTR; + RAISE_IF_NULL( mp_delete_name(CTX.qst) ); + continue; + } +//591 + VM_ENTRY(MP_BC_DELETE_GLOBAL): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_DELETE_GLOBAL", MP_BC_DELETE_GLOBAL); + RAISE_IF_NULL( mp_delete_global(CTX.qst) ); + continue; + } + + VM_ENTRY(MP_BC_DUP_TOP): { + mp_obj_t top = VM_TOP(); + VM_PUSH(top); + continue; + } +//604 + VM_ENTRY(MP_BC_DUP_TOP_TWO): + CTX.sp += 2; + CTX.sp[0] = CTX.sp[-2]; + CTX.sp[-1] = CTX.sp[-3]; + continue; + + VM_ENTRY(MP_BC_POP_TOP): { + CTX.sp -= 1; + continue; } // master misses scoping + + VM_ENTRY(MP_BC_ROT_TWO): { + mp_obj_t top = CTX.sp[0]; + CTX.sp[0] = CTX.sp[-1]; + CTX.sp[-1] = top; + continue; + } +//621 + VM_ENTRY(MP_BC_ROT_THREE): { + mp_obj_t top = CTX.sp[0]; + CTX.sp[0] = CTX.sp[-1]; + CTX.sp[-1] = CTX.sp[-2]; + CTX.sp[-2] = top; + continue; + } + + VM_ENTRY(MP_BC_JUMP): { + VM_DECODE_SLABEL; + VM_TRACE("MP_BC_JUMP", MP_BC_JUMP); + CTX.ip += CTX.slab; + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_JUMP_IF_TRUE): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_POP())) { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_JUMP_IF_FALSE): { + VM_DECODE_SLABEL; + if (!mp_obj_is_true(VM_POP())) { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_TOP())) { + CTX.ip += CTX.slab; + } else { + CTX.sp--; + } + break; //goto pending_exception_check; + } +//661 + VM_ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_TOP())) { + CTX.sp--; + } else { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_EXCEPT_JUMP): { + assert(CTX.exc_sp >= CTX.exc_stack); + VM_POP_EXC_BLOCK(); + VM_DECODE_ULABEL; + CTX.ip += CTX.ulab; + break; //goto pending_exception_check; + } + + +//896 + VM_ENTRY(MP_BC_BUILD_TUPLE): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_TUPLE", MP_BC_BUILD_TUPLE); + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_tuple(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + +//905 + VM_ENTRY(MP_BC_BUILD_LIST): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_LIST", MP_BC_BUILD_LIST); + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_list(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + +//914 + VM_ENTRY(MP_BC_BUILD_MAP): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_MAP", MP_BC_BUILD_MAP); + RAISE_IF_NULL( VM_PUSH(mp_obj_new_dict(unum)) ); + continue; + } + + +//922 + VM_ENTRY(MP_BC_STORE_MAP): { + VM_TRACE("MP_BC_STORE_MAP", MP_BC_STORE_MAP); + CTX.sp -= 2; + RAISE_IF_NULL( mp_obj_dict_store(CTX.sp[0], CTX.sp[2], CTX.sp[1]) ); + continue; + } + + +//671 + VM_ENTRY(MP_BC_SETUP_WITH): { + VM_TRACE("MP_BC_SETUP_WITH", MP_BC_SETUP_WITH); + // stack: (..., ctx_mgr) + mp_obj_t obj = VM_TOP(); + mp_load_method(obj, MP_QSTR___exit__, CTX.sp); + mp_load_method(obj, MP_QSTR___enter__, CTX.sp + 2); + mp_obj_t ret = mp_call_method_n_kw(0, 0, CTX.sp + 2); + RAISE_IF_NULL(ret); + CTX.sp += 1; + VM_PUSH_EXC_BLOCK(1); + VM_PUSH(ret); + // stack: (..., __exit__, ctx_mgr, as_value) + continue; + } + + VM_ENTRY(MP_BC_WITH_CLEANUP): { + VM_TRACE("MP_BC_WITH_CLEANUP", MP_BC_WITH_CLEANUP); + + // Arriving here, there's "exception control block" on top of stack, + // and __exit__ method (with self) underneath it. Bytecode calls __exit__, + // and "deletes" it off stack, shifting "exception control block" + // to its place. + // The bytecode emitter ensures that there is enough space on the Python + // value stack to hold the __exit__ method plus an additional 4 entries. + if (VM_TOP() == mp_const_none) { + // stack: (..., __exit__, ctx_mgr, None) + CTX.sp[1] = mp_const_none; + CTX.sp[2] = mp_const_none; + CTX.sp -= 2; +#pragma message "740: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 740: mp_call_method_n_kw"); + RAISE_IF_NULL( mp_call_method_n_kw(3, 0, CTX.sp) ); + VM_SET_TOP(mp_const_none); + } else if (mp_obj_is_small_int(VM_TOP())) { + // Getting here there are two distinct cases: + // - unwind return, stack: (..., __exit__, ctx_mgr, ret_val, SMALL_INT(-1)) + // - unwind jump, stack: (..., __exit__, ctx_mgr, dest_ip, SMALL_INT(num_exc)) + // For both cases we do exactly the same thing. + mp_obj_t data = CTX.sp[-1]; + mp_obj_t cause = CTX.sp[0]; + CTX.sp[-1] = mp_const_none; + CTX.sp[0] = mp_const_none; + CTX.sp[1] = mp_const_none; +#pragma message "753: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 753: mp_call_method_n_kw"); + mp_call_method_n_kw(3, 0, CTX.sp - 3); + CTX.sp[-3] = data; + CTX.sp[-2] = cause; + CTX.sp -= 2; // we removed (__exit__, ctx_mgr) + } else { + assert(mp_obj_is_exception_instance(VM_TOP())); + // stack: (..., __exit__, ctx_mgr, exc_instance) + // Need to pass (exc_type, exc_instance, None) as arguments to __exit__. + CTX.sp[1] = CTX.sp[0]; + CTX.sp[0] = MP_OBJ_FROM_PTR(mp_obj_get_type(CTX.sp[0])); + CTX.sp[2] = mp_const_none; + CTX.sp -= 2; +#pragma message "766: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 766: mp_call_method_n_kw"); + mp_obj_t ret_value = mp_call_method_n_kw(3, 0, CTX.sp); + if (mp_obj_is_true(ret_value)) { + // We need to silence/swallow the exception. This is done + // by popping the exception and the __exit__ handler and + // replacing it with None, which signals END_FINALLY to just + // execute the finally handler normally. + VM_SET_TOP(mp_const_none); + } else { + // We need to re-raise the exception. We pop __exit__ handler + // by copying the exception instance down to the new top-of-stack. + CTX.sp[0] = CTX.sp[3]; + } + } + continue; + } +//741 ************************************************************************************* + VM_ENTRY(MP_BC_UNWIND_JUMP): { +#if VMTRACE + clog(" 399:MP_BC_UNWIND_JUMP=%i",MP_BC_UNWIND_JUMP); +#endif + VM_DECODE_SLABEL; + VM_PUSH((mp_obj_t)(mp_uint_t)(uintptr_t)(CTX.ip + CTX.slab)); // push destination ip for jump + VM_PUSH((mp_obj_t)(mp_uint_t)(*CTX.ip)); // push number of exception handlers to unwind (0x80 bit set if we also need to pop stack) +unwind_jump:; + mp_uint_t unum = (mp_uint_t)VM_POP(); // get number of exception handlers to unwind + while ((unum & 0x7f) > 0) { + unum -= 1; + assert(CTX.exc_sp >= CTX.exc_stack); + // FIXME () + if (MP_TAGPTR_TAG1(CTX.exc_sp->val_sp)) { + if (CTX.exc_sp->handler > CTX.ip) { + // Found a finally handler that isn't active; run it. + // Getting here the stack looks like: + // (..., X, dest_ip) + // where X is pointed to by exc_sp->val_sp and in the case + // of a "with" block contains the context manager info. + + assert(&CTX.sp[-1] == MP_TAGPTR_PTR(CTX.exc_sp->val_sp)); + + // We're going to run "finally" code as a coroutine + // (not calling it recursively). Set up a sentinel + // on the stack so it can return back to us when it is + // done (when WITH_CLEANUP or END_FINALLY reached). + // The sentinel is the number of exception handlers left to + // unwind, which is a non-negative integer. + VM_PUSH(MP_OBJ_NEW_SMALL_INT(unum)); + CTX.ip = CTX.exc_sp->handler; // get exception handler byte code address +//REMOVED CTX.exc_sp--; // pop exception handler + continue; // goto VM_DISPATCH_loop; // run the exception handler + } else { + // Found a finally handler that is already active; cancel it. + VM_CANCEL_ACTIVE_FINALLY(CTX.sp); + } + } + VM_POP_EXC_BLOCK(); + } + CTX.ip = (const byte*)MP_OBJ_TO_PTR(VM_POP()); // pop destination ip for jump + if (unum != 0) { + // pop the exhausted iterator + CTX.sp -= MP_OBJ_ITER_BUF_NSLOTS; + } + break; //DISPATCH_WITH_PEND_EXC_CHECK() : goto pending_exception_check; + } +//784 + VM_ENTRY(MP_BC_SETUP_EXCEPT): +clog(" 833:vmswitch.c EXCEPT"); + + VM_ENTRY(MP_BC_SETUP_FINALLY): { + +#if VMTRACE +clog(" 451:MP_BC_SETUP_FINALLY=%i",MP_BC_SETUP_FINALLY); +#endif + #if SELECTIVE_EXC_IP + VM_PUSH_EXC_BLOCK((CTX.code_state->ip[-1] == MP_BC_SETUP_FINALLY) ? 1 : 0); + #else + VM_PUSH_EXC_BLOCK((CTX.code_state->ip[0] == MP_BC_SETUP_FINALLY) ? 1 : 0); + #endif + continue; + } +//795 + VM_ENTRY(MP_BC_END_FINALLY): { +#if VMTRACE +clog(" 463:MP_BC_END_FINALLY=%i",MP_BC_END_FINALLY); +#endif + // if VM_TOP is None, just pops it and continues + // if VM_TOP is an integer, finishes coroutine and returns control to caller + // if VM_TOP is an exception, reraises the exception + if (VM_TOP() == mp_const_none) { + assert(CTX.exc_sp >= CTX.exc_stack); + VM_POP_EXC_BLOCK(); + CTX.sp--; + } else if (mp_obj_is_small_int(VM_TOP())) { + // We finished "finally" coroutine and now VM_DISPATCH back + // to our caller, based on TOS value + mp_int_t cause = MP_OBJ_SMALL_INT_VALUE(VM_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 + VM_PUSH((mp_obj_t)cause); + goto unwind_jump; + } + } else { + assert(mp_obj_is_exception_instance(VM_TOP())); + RAISE(VM_TOP()); + } + continue; + } + + +//823 + VM_ENTRY(MP_BC_GET_ITER): { + VM_SET_TOP(mp_getiter(VM_TOP(), NULL)); + continue; + } + + +//829 + // 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. + VM_ENTRY(MP_BC_GET_ITER_STACK): { + mp_obj_t obj = VM_TOP(); + mp_obj_iter_buf_t *iter_buf = (mp_obj_iter_buf_t*)CTX.sp; + CTX.sp += MP_OBJ_ITER_BUF_NSLOTS - 1; + obj = mp_getiter(obj, iter_buf); + RAISE_IF_NULL(obj); + if (obj != MP_OBJ_FROM_PTR(iter_buf)) { + // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. + CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; + CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 2] = obj; + } + continue; + } + + +//955 + VM_ENTRY(MP_BC_STORE_COMP): { + VM_DECODE_UINT; + mp_obj_t obj = CTX.sp[-(unum >> 2)]; + if ((unum & 3) == 0) { + mp_obj_list_append(obj, CTX.sp[0]); + CTX.sp--; + } else if (!MICROPY_PY_BUILTINS_SET || (unum & 3) == 1) { + mp_obj_dict_store(obj, CTX.sp[0], CTX.sp[-1]); + CTX.sp -= 2; + #if MICROPY_PY_BUILTINS_SET + } else { + mp_obj_set_store(obj, CTX.sp[0]); + CTX.sp--; + #endif + } + continue; + } + + +//974 + VM_ENTRY(MP_BC_UNPACK_SEQUENCE): { + VM_DECODE_UINT; + RAISE_IF_NULL( mp_unpack_sequence(CTX.sp[0], unum, CTX.sp) ); + CTX.sp += unum - 1; + continue; + } + + +//982 + VM_ENTRY(MP_BC_UNPACK_EX): { + VM_DECODE_UINT; + RAISE_IF_NULL( mp_unpack_ex(CTX.sp[0], unum, CTX.sp) ); + CTX.sp += (unum & 0xff) + ((unum >> 8) & 0xff); + continue; + } + + +//990 + VM_ENTRY(MP_BC_MAKE_FUNCTION): { + VM_DECODE_PTR; + VM_PUSH(mp_make_function_from_raw_code(CTX.ptr, MP_OBJ_NULL, MP_OBJ_NULL)); + continue; + } + + +//996 + VM_ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS): { + VM_DECODE_PTR; + // Stack layout: def_tuple def_dict <- TOS + mp_obj_t def_dict = VM_POP(); + VM_SET_TOP(mp_make_function_from_raw_code(CTX.ptr, VM_TOP(), def_dict)); + continue; + } + + +//1024 + VM_ENTRY(MP_BC_MAKE_CLOSURE): { + VM_DECODE_PTR; + size_t n_closed_over = *CTX.ip++; + // Stack layout: closed_overs <- TOS + CTX.sp -= n_closed_over - 1; + VM_SET_TOP(mp_make_closure_from_raw_code(CTX.ptr, n_closed_over, CTX.sp)); + continue; + } + + +//1013 + VM_ENTRY(MP_BC_MAKE_CLOSURE_DEFARGS): { + VM_DECODE_PTR; + size_t n_closed_over = *CTX.ip++; + // Stack layout: def_tuple def_dict closed_overs <- TOS + CTX.sp -= 2 + n_closed_over - 1; + VM_SET_TOP(mp_make_closure_from_raw_code(CTX.ptr, 0x100 | n_closed_over, CTX.sp)); + continue; + } + + +//1022 + /* + ENTRY(MP_BC_CALL_FUNCTION): { +included + }*/ + + +//1111 +// ENTRY(MP_BC_CALL_METHOD): { + #include "vmsl/vmbc_call_method.c" +//} + + +//1154 + VM_ENTRY(MP_BC_CALL_METHOD_VAR_KW): { + FRAME_UPDATE(); + + VM_DECODE_UINT; + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + // We have following stack layout here: + // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 3; + #if MICROPY_STACKLESS + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(true, unum, CTX.sp, &out_args); + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, + out_args.n_args, out_args.n_kw, out_args.args); + #if !MICROPY_ENABLE_PYSTACK + // Freeing args at this point does not follow a LIFO order so only do it if + // pystack is not enabled. For pystack, they are freed when code_state is. + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + #endif + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; +/* +deep_recursion_error: + mp_raise_recursion_depth(); + RAISE_IF(true); +*/ + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } + #endif + VM_SET_TOP(mp_call_method_n_kw_var(true, unum, CTX.sp)); + continue; + } +//1205 + VM_ENTRY(MP_BC_RETURN_VALUE): { + +unwind_return: +#if VMTRACE + clog(" 652:MP_BC_RETURN_VALUE=%i",MP_BC_RETURN_VALUE); +#endif + // Search for and execute finally handlers that aren't already active + while (CTX.exc_sp >= CTX.exc_stack) { + if (MP_TAGPTR_TAG1(CTX.exc_sp->val_sp)) { + if (CTX.exc_sp->handler > CTX.ip) { +#if VMTRACE + clog(" 682:Found a finally handler that isn't active."); +#endif + // 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(CTX.exc_sp->val_sp); + finally_sp[1] = CTX.sp[0]; + CTX.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). + VM_PUSH(MP_OBJ_NEW_SMALL_INT(-1)); + CTX.ip = CTX.exc_sp->handler; + + //BEWARE cannot "continue" or "break" we're inside the while@670 + goto VM_DISPATCH_loop; + + } else { +#if VMTRACE + clog(" 697:Found a finally handler that is already active; cancel it."); +#endif + VM_CANCEL_ACTIVE_FINALLY(CTX.sp); + } + } + VM_POP_EXC_BLOCK(); + } // end while + + CTX.code_state->sp = CTX.sp; + assert(CTX.exc_sp == CTX.exc_stack - 1); + + MICROPY_VM_HOOK_RETURN + + #if MICROPY_STACKLESS + if (CTX.code_state->prev != NULL) { + mp_obj_t res = *CTX.sp; + mp_globals_set(CTX.code_state->old_globals); + mp_code_state_t *new_code_state = CTX.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(CTX.code_state, sizeof(mp_code_state_t)); + #endif + CTX.code_state = new_code_state; + *CTX.code_state->sp = res; + goto run_code_state_from_return; + } + #endif + + VM_return(MP_VM_RETURN_NORMAL); + } + +//1266 +//VM_ENTRY(MP_BC_RAISE_LAST): + +//1282 +//VMENTRY(MP_BC_RAISE_OBJ): + +//1288 +//VM_ENTRY(MP_BC_RAISE_FROM): + + #include "vmsl/vmbc_raise.c" + + + + +//1295 +//yield: + VM_ENTRY(MP_BC_YIELD_VALUE): + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + VM_return(MP_VM_RETURN_YIELD); +//1304 no continuation + + + + + + + + VM_ENTRY(MP_BC_YIELD_FROM): { + +#define EXC_MATCH(exc, type) mp_obj_exception_match(exc, type) +#define GENERATOR_EXIT_IF_NEEDED(t) if (t != MP_OBJ_NULL && EXC_MATCH(t, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { mp_obj_t raise_t = mp_make_raise_obj(t); RAISE(raise_t); } + mp_vm_return_kind_t ret_kind; + mp_obj_t send_value = VM_POP(); + mp_obj_t t_exc = MP_OBJ_NULL; + mp_obj_t ret_value; + +//NO_NLR CTX.code_state->sp = CTX.sp; // Save sp because it's needed if mp_resume raises StopIteration + + if (CTX.inject_exc != MP_OBJ_NULL) { + t_exc = CTX.inject_exc; + CTX.inject_exc = MP_OBJ_NULL; +clog("1320:vmswitch.c mp_resume + exc"); + ret_kind = mp_resume(VM_TOP(), MP_OBJ_NULL, t_exc, &ret_value); + } else { +clog("1323:vmswitch.c mp_resume"); + ret_kind = mp_resume(VM_TOP(), send_value, MP_OBJ_NULL, &ret_value); + } + + if (ret_kind == MP_VM_RETURN_YIELD) { +clog("1178:vmswitch.c yield from\n"); + CTX.ip--; + VM_PUSH(ret_value); + //goto yield; +//inline 1296-1303 (MP_BC_YIELD_VALUE) +CTX.code_state->ip = CTX.ip; +CTX.code_state->sp = CTX.sp; +CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + +VM_return(MP_VM_RETURN_YIELD); + + } // no continuation + + if (ret_kind == MP_VM_RETURN_NORMAL) { + // Pop exhausted gen + CTX.sp--; + if (ret_value == MP_OBJ_STOP_ITERATION) { + // Optimize StopIteration + // TODO: get StopIteration's value + VM_PUSH(mp_const_none); + } else { + VM_PUSH(ret_value); + } + + // If we injected GeneratorExit downstream, then even + // if it was swallowed, we re-raise GeneratorExit + GENERATOR_EXIT_IF_NEEDED(t_exc); + continue; + } // no continuation + + + assert(ret_kind == MP_VM_RETURN_EXCEPTION); + // Pop exhausted gen + CTX.sp--; + if (EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + VM_PUSH(mp_obj_exception_get_value(ret_value)); + // If we injected GeneratorExit downstream, then even + // if it was swallowed, we re-raise GeneratorExit + GENERATOR_EXIT_IF_NEEDED(t_exc); + continue; + } // no continuation + + RAISE(ret_value); + } + +#include "vmsl/vmbc_import.c" // FAIL + +#if MICROPY_PY_BUILTINS_SET + VM_ENTRY(MP_BC_BUILD_SET): { + + VM_DECODE_UINT; + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_set(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } +#endif + +#if MICROPY_PY_BUILTINS_SLICE + VM_ENTRY(MP_BC_BUILD_SLICE): { + + mp_obj_t step = mp_const_none; + if (*CTX.ip++ == 3) { + // 3-argument slice includes step + step = VM_POP(); + } + mp_obj_t stop = VM_POP(); + mp_obj_t start = VM_TOP(); + VM_SET_TOP(mp_obj_new_slice(start, stop, step)); + RAISE_IF_NULL(VM_TOP()); + continue; + } +#endif + + #include "vmsl/vmbc_for_iter.c" + + #include "vmsl/vmbc_call_function_var_kw.c" + + + #include "vmsl/vmbc_call_function.c" // FAIL + + + + + + +//!MICROPY_OPT_COMPUTED_GOTO + + +/* + VM_ENTRY(MP_BC_RAISE_VARARGS): { +clog("mpsl:998 MP_BC_RAISE_VARARGS\n"); + + mp_uint_t unum = *CTX.ip; + mp_obj_t obj; + if (unum == 2) { + mp_warning(NULL, "exception chaining not supported"); + // ignore (pop) "from" argument + CTX.sp--; + } + if (unum == 0) { + // search for the inner-most previous exception, to reraise it + obj = MP_OBJ_NULL; + for (mp_exc_stack_t *e = CTX.exc_sp; e >= CTX.exc_stack; e--) { + if (e->prev_exc != NULL) { + obj = MP_OBJ_FROM_PTR(e->prev_exc); + break; + } + } + if (obj == MP_OBJ_NULL) { + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); + RAISE(obj); + } + } else { + obj = VM_TOP(); + } + obj = mp_make_raise_obj(obj); + RAISE(obj); + } +*/ + + + +//1421 +//ENTRY_DEFAULT: + default: { +#if VM_TRACE + clog(" 928:ENTRY_DEFAULT"); +#endif + //if (CTX.ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { + if (CTX.ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM) { + VM_PUSH(MP_OBJ_NEW_SMALL_INT((mp_int_t)CTX.ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16)); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { + obj_shared = CTX.fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)CTX.ip[-1]]; + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + VM_PUSH(obj_shared); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { + CTX.fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)CTX.ip[-1]] = VM_POP(); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { + VM_SET_TOP(mp_unary_op(CTX.ip[-1] - MP_BC_UNARY_OP_MULTI, VM_TOP())); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { + mp_obj_t rhs = VM_POP(); + mp_obj_t lhs = VM_TOP(); + VM_SET_TOP(mp_binary_op(CTX.ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + continue; + } // no continuation + + clog("1453:FATAL OPCODE N/I"); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, "opcode"); + CTX.code_state->state[0] = obj; + + VM_return(MP_VM_RETURN_EXCEPTION); + // no continuation + } + +//1460 + + + + + diff --git a/ports/wapy-wasm/vmsl/vmwarmup.c b/ports/wapy-wasm/vmsl/vmwarmup.c new file mode 100644 index 000000000..fe1418147 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmwarmup.c @@ -0,0 +1,59 @@ +if (VMOP < VMOP_INIT) { + puts("init"); + + Py_Init(); + + stack_initial = __builtin_frame_address(0); // could also use alloca(0) + stack_max = (uintptr_t)stack_initial - stack_limit; + + + VMOP = VMOP_INIT; + + entry_point[0]=JMP_NONE; + exit_point[0]=JMP_NONE; + come_from[0]=0; + type_point[0]=0; + + for (int i=0; i syscall + +if (VMOP==VMOP_INIT) { + puts("VMOP_INIT"); + + VMOP = VMOP_WARMUP; + show_os_loop(1); + + // could help fix lack of vars() but wapy does not need + // "__dict__ = globals();" + + PyRun_SimpleString( + "import sys;" + "import embed;" + "import builtins;builtins.__WAPY__ = True;" + "sys.path.append('/assets');" + "import wapy_wasm_site as site;" + "sys.path.append('/assets/packages');" + "#\n" + ); + emscripten_cancel_main_loop(); + emscripten_set_main_loop( main_loop_or_step, 0, 1); +} diff --git a/ports/wapy-wasm/wasm_mphal.c b/ports/wapy-wasm/wasm_mphal.c new file mode 100644 index 000000000..a8306f8b5 --- /dev/null +++ b/ports/wapy-wasm/wasm_mphal.c @@ -0,0 +1,157 @@ +#include +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#elif __CPP__ + #define EMSCRIPTEN_KEEPALIVE +#endif + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif +} + + + + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif + +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i +#include + +#include "py/runtime.h" + +#pragma message "TODO: maybe take the function name as an argument so we can print nicer error messages" +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { + // TODO maybe take the function name as an argument so we can print nicer error messages + + // The reverse of MP_OBJ_FUN_MAKE_SIG + bool takes_kw = sig & 1; + size_t n_args_min = sig >> 17; + size_t n_args_max = (sig >> 1) & 0xffff; + + if (n_kw && !takes_kw) { + + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_TypeError_o(MP_ERROR_TEXT("function doesn't take keyword arguments")); + #endif + return 1; + } + + if (n_args_min == n_args_max) { + if (n_args != n_args_min) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), + n_args_min, n_args)); + #endif + return 1; + + } + } else { + if (n_args < n_args_min) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing %d required positional arguments", + n_args_min - n_args)); + #endif + return 1; + } else if (n_args > n_args_max) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function expected at most %d arguments, got %d", + n_args_max, n_args)); + #endif + return 1; + } + } + + return 0; +} + + +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { + size_t pos_found = 0, kws_found = 0; + for (size_t i = 0; i < n_allowed; i++) { + mp_obj_t given_arg; + if (i < n_pos) { + if (allowed[i].flags & MP_ARG_KW_ONLY) { + goto extra_positional; + } + pos_found++; + given_arg = pos[i]; + } else { + mp_map_elem_t *kw = mp_map_lookup(kws, MP_OBJ_NEW_QSTR(allowed[i].qst), MP_MAP_LOOKUP); + if (kw == NULL) { + if (allowed[i].flags & MP_ARG_REQUIRED) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%q' argument required", allowed[i].qst)); + #endif + return 1; + } + out_vals[i] = allowed[i].defval; + continue; + } else { + kws_found++; + given_arg = kw->value; + } + } + if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_BOOL) { + out_vals[i].u_bool = mp_obj_is_true(given_arg); + } else if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_INT) { + out_vals[i].u_int = mp_obj_get_int(given_arg); + } else { + assert((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_OBJ); + out_vals[i].u_obj = given_arg; + } + } + if (pos_found < n_pos) { + extra_positional: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + // TODO better error message + mp_raise_TypeError_o("extra positional arguments given"); + return 1; + #endif + } + if (kws_found < kws->used) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_arg_error_terse_mismatch(); + } else { + // TODO better error message + mp_raise_TypeError_o("extra keyword arguments given"); + return 1; + } + } + return 0; +} + +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_pos); + return mp_arg_parse_all(n_pos, args, &kw_args, n_allowed, allowed, out_vals); +} + +void mp_arg_error_terse_mismatch(void) { + mp_raise_TypeError_o("argument num/types mismatch"); +} + +#if MICROPY_CPYTHON_COMPAT +void mp_arg_error_unimpl_kw(void) { + mp_raise_NotImplementedError_o("keyword argument(s) not yet implemented - use normal args instead"); +} +#endif diff --git a/ports/wapy/builtinimport_no_nlr.c b/ports/wapy/builtinimport_no_nlr.c new file mode 100644 index 000000000..5310e9a79 --- /dev/null +++ b/ports/wapy/builtinimport_no_nlr.c @@ -0,0 +1,524 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2019 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 "py/compile.h" +#include "py/objmodule.h" +#include "py/persistentcode.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/frozenmod.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +#if MICROPY_ENABLE_EXTERNAL_IMPORT + +#define PATH_SEP_CHAR '/' + +bool mp_obj_is_package(mp_obj_t module) { + mp_obj_t dest[2]; + mp_load_method_maybe(module, MP_QSTR___path__, dest); + return dest[0] != MP_OBJ_NULL; +} + +// Stat either frozen or normal module by a given path +// (whatever is available, if at all). +STATIC mp_import_stat_t mp_import_stat_any(const char *path) { + #if MICROPY_MODULE_FROZEN + mp_import_stat_t st = mp_frozen_stat(path); + if (st != MP_IMPORT_STAT_NO_EXIST) { + return st; + } + #endif + return mp_import_stat(path); +} + +STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { + mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + if (stat == MP_IMPORT_STAT_FILE) { + return stat; + } + + #if MICROPY_PERSISTENT_CODE_LOAD + vstr_ins_byte(path, path->len - 2, 'm'); + stat = mp_import_stat_any(vstr_null_terminated_str(path)); + if (stat == MP_IMPORT_STAT_FILE) { + return stat; + } + #endif + + return MP_IMPORT_STAT_NO_EXIST; +} + +STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { + mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); + if (stat == MP_IMPORT_STAT_DIR) { + return stat; + } + + // not a directory, add .py and try as a file + vstr_add_str(path, ".py"); + return stat_file_py_or_mpy(path); +} + +STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) { + #if MICROPY_PY_SYS + // extract the list of paths + size_t path_num; + mp_obj_t *path_items; + mp_obj_list_get(mp_sys_path, &path_num, &path_items); + + if (path_num != 0) { + // go through each path looking for a directory or file + for (size_t i = 0; i < path_num; i++) { + vstr_reset(dest); + size_t p_len; + const char *p = mp_obj_str_get_data(path_items[i], &p_len); + if (p_len > 0) { + vstr_add_strn(dest, p, p_len); + vstr_add_char(dest, PATH_SEP_CHAR); + } + vstr_add_strn(dest, file_str, file_len); + mp_import_stat_t stat = stat_dir_or_file(dest); + if (stat != MP_IMPORT_STAT_NO_EXIST) { + return stat; + } + } + + // could not find a directory or file + return MP_IMPORT_STAT_NO_EXIST; + } + #endif + + // mp_sys_path is empty, so just use the given file name + vstr_add_strn(dest, file_str, file_len); + return stat_dir_or_file(dest); +} + +#if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER +STATIC int do_load_from_lexer(mp_obj_t module_obj, mp_lexer_t *lex) { + #if MICROPY_PY___FILE__ + qstr source_name = lex->source_name; + mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + #endif + + // parse, compile and execute the module in its context + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mp_parse_compile_execute(lex, MP_PARSE_FILE_INPUT, mod_globals, mod_globals) == MP_OBJ_NULL; +} +#endif + +#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_MODULE_FROZEN_MPY +STATIC int do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, const char *source_name) { + (void)source_name; + + #if MICROPY_PY___FILE__ + mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); + #endif + + // execute the module in its context + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + + // save context + mp_obj_dict_t *volatile old_globals = mp_globals_get(); + mp_obj_dict_t *volatile old_locals = mp_locals_get(); + + // set new context + mp_globals_set(mod_globals); + mp_locals_set(mod_globals); + + mp_obj_t module_fun = mp_make_function_from_raw_code(raw_code, MP_OBJ_NULL, MP_OBJ_NULL); + module_fun = mp_call_function_0(module_fun); + + // restore context + mp_globals_set(old_globals); + mp_locals_set(old_locals); + + return module_fun == MP_OBJ_NULL; +} +#endif + +STATIC int do_load(mp_obj_t module_obj, vstr_t *file) { + #if MICROPY_MODULE_FROZEN || MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) + char *file_str = vstr_null_terminated_str(file); + #endif + + // If we support frozen modules (either as str or mpy) then try to find the + // requested filename in the list of frozen module filenames. + #if MICROPY_MODULE_FROZEN + void *modref; + int frozen_type = mp_find_frozen_module(file_str, file->len, &modref); + #endif + + // If we support frozen str modules and the compiler is enabled, and we + // found the filename in the list of frozen files, then load and execute it. + #if MICROPY_MODULE_FROZEN_STR + if (frozen_type == MP_FROZEN_STR) { + return do_load_from_lexer(module_obj, modref); + } + #endif + + // If we support frozen mpy modules and we found a corresponding file (and + // its data) in the list of frozen files, execute it. + #if MICROPY_MODULE_FROZEN_MPY + if (frozen_type == MP_FROZEN_MPY) { + return do_execute_raw_code(module_obj, modref, file_str); + } + #endif + + // If we support loading .mpy files then check if the file extension is of + // the correct format and, if so, load and execute the file. + #if MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD + if (file_str[file->len - 3] == 'm') { + mp_raw_code_t *raw_code = mp_raw_code_load_file(file_str); + if (raw_code == NULL) { + return 1; + } + return do_execute_raw_code(module_obj, raw_code, file_str); + } + #endif + + // If we can compile scripts then load the file and compile and execute it. + #if MICROPY_ENABLE_COMPILER + { + mp_lexer_t *lex = mp_lexer_new_from_file(file_str); + return do_load_from_lexer(module_obj, lex); + } + #else + // If we get here then the file was not frozen and we can't compile scripts. + mp_raise_msg_o(&mp_type_ImportError, "script compilation not supported"); + return 1; + #endif +} + +STATIC void chop_component(const char *start, const char **end) { + const char *p = *end; + while (p > start) { + if (*--p == '.') { + *end = p; + return; + } + } + *end = p; +} + +mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { + #if DEBUG_PRINT + DEBUG_printf("__import__:\n"); + for (size_t i = 0; i < n_args; i++) { + DEBUG_printf(" "); + mp_obj_print(args[i], PRINT_REPR); + DEBUG_printf("\n"); + } + #endif + + mp_obj_t module_name = args[0]; + mp_obj_t fromtuple = mp_const_none; + mp_int_t level = 0; + if (n_args >= 4) { + fromtuple = args[3]; + if (n_args >= 5) { + level = MP_OBJ_SMALL_INT_VALUE(args[4]); + if (level < 0) { + mp_raise_ValueError(NULL); + } + } + } + + size_t mod_len; + const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); + if (mod_str == NULL) { + return MP_OBJ_NULL; + } + + if (level != 0) { + // What we want to do here is to take name of current module, + // chop trailing components, and concatenate with passed-in + // module name, thus resolving relative import name into absolute. + // This even appears to be correct per + // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name + // "Relative imports use a module's __name__ attribute to determine that + // module's position in the package hierarchy." + level--; + mp_obj_t this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + if (this_name_q == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + assert(this_name_q != MP_OBJ_NULL); + #if MICROPY_CPYTHON_COMPAT + if (MP_OBJ_QSTR_VALUE(this_name_q) == MP_QSTR___main__) { + // This is a module run by -m command-line switch, get its real name from backup attribute + this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + if (this_name_q == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + #endif + mp_map_t *globals_map = &mp_globals_get()->map; + mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + bool is_pkg = (elem != NULL); + + #if DEBUG_PRINT + DEBUG_printf("Current module/package: "); + mp_obj_print(this_name_q, PRINT_REPR); + DEBUG_printf(", is_package: %d", is_pkg); + DEBUG_printf("\n"); + #endif + + size_t this_name_l; + const char *this_name = mp_obj_str_get_data(this_name_q, &this_name_l); + + const char *p = this_name + this_name_l; + if (!is_pkg) { + // We have module, but relative imports are anchored at package, so + // go there. + chop_component(this_name, &p); + } + + while (level--) { + chop_component(this_name, &p); + } + + // We must have some component left over to import from + if (p == this_name) { + return mp_raise_ValueError_o("cannot perform relative import"); + } + + uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); + char *new_mod = mp_local_alloc(new_mod_l); + memcpy(new_mod, this_name, p - this_name); + if (mod_len != 0) { + new_mod[p - this_name] = '.'; + memcpy(new_mod + (p - this_name) + 1, mod_str, mod_len); + } + + qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); + mp_local_free(new_mod); + DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); + module_name = MP_OBJ_NEW_QSTR(new_mod_q); + mod_str = qstr_str(new_mod_q); + mod_len = new_mod_l; + } + + if (mod_len == 0) { + mp_raise_ValueError(NULL); + } + + // check if module already exists + qstr module_name_qstr = mp_obj_str_get_qstr(module_name); + mp_obj_t module_obj = mp_module_get(module_name_qstr); + if (module_obj != MP_OBJ_NULL) { + DEBUG_printf("Module already loaded\n"); + // If it's not a package, return module right away + char *p = strchr(mod_str, '.'); + if (p == NULL) { + return module_obj; + } + // If fromlist is not empty, return leaf module + if (fromtuple != mp_const_none) { + return module_obj; + } + // Otherwise, we need to return top-level package + qstr pkg_name = qstr_from_strn(mod_str, p - mod_str); + return mp_module_get(pkg_name); + } + DEBUG_printf("Module not yet loaded\n"); + + uint last = 0; + VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) + module_obj = MP_OBJ_NULL; + mp_obj_t top_module_obj = MP_OBJ_NULL; + mp_obj_t outer_module_obj = MP_OBJ_NULL; + uint i; + for (i = 1; i <= mod_len; i++) { + if (i == mod_len || mod_str[i] == '.') { + // create a qstr for the module name up to this depth + qstr mod_name = qstr_from_strn(mod_str, i); + DEBUG_printf("Processing module: %s\n", qstr_str(mod_name)); + DEBUG_printf("Previous path: =%.*s=\n", vstr_len(&path), vstr_str(&path)); + + // find the file corresponding to the module name + mp_import_stat_t stat; + if (vstr_len(&path) == 0) { + // first module in the dotted-name; search for a directory or file + stat = find_file(mod_str, i, &path); + } else { + // latter module in the dotted-name; append to path + vstr_add_char(&path, PATH_SEP_CHAR); + vstr_add_strn(&path, mod_str + last, i - last); + stat = stat_dir_or_file(&path); + } + DEBUG_printf("Current path: %.*s\n", vstr_len(&path), vstr_str(&path)); + + if (stat == MP_IMPORT_STAT_NO_EXIST) { + module_obj = MP_OBJ_NULL; + #if MICROPY_MODULE_WEAK_LINKS + // check if there is a weak link to this module + if (i == mod_len) { + module_obj = mp_module_search_umodule(mod_str); + if (module_obj != MP_OBJ_NULL) { + // found weak linked module + mp_module_call_init(mod_name, module_obj); + } + } + #endif + if (module_obj == MP_OBJ_NULL) { + // couldn't find the file, so fail + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_ImportError, "module not found"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ImportError, + "no module named '%q'", mod_name)); + } + } + } else { + // found the file, so get the module + module_obj = mp_module_get(mod_name); + } + + if (module_obj == MP_OBJ_NULL) { + // module not already loaded, so load it! + + module_obj = mp_obj_new_module(mod_name); + + // if args[3] (fromtuple) has magic value False, set up + // this module for command-line "-m" option (set module's + // name to __main__ instead of real name). Do this only + // for *modules* however - packages never have their names + // replaced, instead they're -m'ed using a special __main__ + // submodule in them. (This all apparently is done to not + // touch package name itself, which is important for future + // imports). + if (i == mod_len && fromtuple == mp_const_false && stat != MP_IMPORT_STAT_DIR) { + mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + #if MICROPY_CPYTHON_COMPAT + // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). + mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); + // Store real name in "__main__" attribute. Chosen semi-randonly, to reuse existing qstr's. + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(mod_name)); + #endif + } + + if (stat == MP_IMPORT_STAT_DIR) { + DEBUG_printf("%.*s is dir\n", vstr_len(&path), vstr_str(&path)); + // https://docs.python.org/3/reference/import.html + // "Specifically, any module that contains a __path__ attribute is considered a package." + mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path))); + size_t orig_path_len = path.len; + vstr_add_char(&path, PATH_SEP_CHAR); + vstr_add_str(&path, "__init__.py"); + if (stat_file_py_or_mpy(&path) != MP_IMPORT_STAT_FILE) { + //mp_warning("%s is imported as namespace package", vstr_str(&path)); + } else { + if (do_load(module_obj, &path)) { + // exception + return MP_OBJ_NULL; + } + } + path.len = orig_path_len; + } else { // MP_IMPORT_STAT_FILE + if (do_load(module_obj, &path)) { + // exception + return MP_OBJ_NULL; + } + // This should be the last component in the import path. If there are + // remaining components then it's an ImportError because the current path + // (the module that was just loaded) is not a package. This will be caught + // on the next iteration because the file will not exist. + } + } + if (outer_module_obj != MP_OBJ_NULL) { + qstr s = qstr_from_strn(mod_str + last, i - last); + mp_store_attr(outer_module_obj, s, module_obj); + } + outer_module_obj = module_obj; + if (top_module_obj == MP_OBJ_NULL) { + top_module_obj = module_obj; + } + last = i + 1; + } + } + + // If fromlist is not empty, return leaf module + if (fromtuple != mp_const_none) { + return module_obj; + } + // Otherwise, we need to return top-level package + return top_module_obj; +} + +#else // MICROPY_ENABLE_EXTERNAL_IMPORT + +mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { + // Check that it's not a relative import + if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) { + return mp_raise_NotImplementedError_o("relative import"); + } + + // Check if module already exists, and return it if it does + qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); + if (module_name_qstr == MP_QSTR_NULL) { + return MP_OBJ_NULL; + } + mp_obj_t module_obj = mp_module_get(module_name_qstr); + if (module_obj != MP_OBJ_NULL) { + return module_obj; + } + + #if MICROPY_MODULE_WEAK_LINKS + // Check if there is a weak link to this module + module_obj = mp_module_search_umodule(qstr_str(module_name_qstr)); + if (module_obj != MP_OBJ_NULL) { + // Found weak-linked module + mp_module_call_init(module_name_qstr, module_obj); + return module_obj; + } + #endif + + // Couldn't find the module, so fail + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_ImportError, "module not found"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ImportError, + "no module named '%q'", module_name_qstr)); + } +} + +#endif // MICROPY_ENABLE_EXTERNAL_IMPORT + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin___import___obj, 1, 5, mp_builtin___import__); diff --git a/ports/wapy/cmod/common/_zipfile.pym b/ports/wapy/cmod/common/_zipfile.pym new file mode 100644 index 000000000..5d9371072 --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile.pym @@ -0,0 +1,66 @@ +#include "zip.h" + +class ZipFile(type): + hash : uint8_t = 0 + zbuf : void_p = NULL + zbuf_size : size_t = 0 + zip = "struct zip_t *" + path = ('char_p','NULL') + + def read(name: const_char_p="") -> bytes: + zip_entry_open(self.zip, name); + zip_entry_read(self.zip, &self.zbuf, &self.zbuf_size); + zip_entry_close(self.zip); + try: + if (self.zbuf): + return self.zbuf + finally: + free(self.zbuf) + + def open(path : const_char_p="") -> void: + self.path = strdup(path) + self.hash = strlen(path) + printf("ZIPFILE[%d;%s]self\n", self.hash, self.path) + self.zip = zip_open(self.path, 0, 'r'); + + def filename() -> bytes: + return self.path + + def close() -> void: + if self.path: + zip_close(self.zip); + free(self.path) + + async def pouet() -> int: + if 1: + printf(" pouet arg0 %zu\n", argc ); + return 42; + + async def pouet2() -> int: +/* + for (iterator = 0; iterator < 5; iterator++) { + printf("gen2 %s ",self.name); + yield(iterator); + } +*/ + if 1: + if 2: + return 43; + + + + +#>>> import zipfile; x= zipfile.ZipFile() +#>>> x.open("/data/cross/pydk-applications/org.beerware.wapy/app/build/outputs/apk/debug/app-debug.apk") +#>>> x.read("assets/python3/python3.py") +# x.read_from("assets/python3/python3.py",zp) + + + + + + + + + + diff --git a/ports/wapy/cmod/common/_zipfile/micropython.mk b/ports/wapy/cmod/common/_zipfile/micropython.mk new file mode 100644 index 000000000..46fb21c7e --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/micropython.mk @@ -0,0 +1,8 @@ + +_ZIPFILE_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(_ZIPFILE_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(_ZIPFILE_MOD_DIR) diff --git a/ports/wapy/cmod/common/_zipfile/miniz.h b/ports/wapy/cmod/common/_zipfile/miniz.h new file mode 100644 index 000000000..c4fcfb83e --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/miniz.h @@ -0,0 +1,6854 @@ +/* + miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP + reading/writing/appending, PNG writing See "unlicense" statement at the end + of this file. Rich Geldreich , last updated Oct. 13, + 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: + http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the + archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of + all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major + release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug + (thanks kahmyong.moon@hp.com) which could cause locate files to not find + files. This bug would only have occured in earlier versions if you explicitly + used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or + mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you + can't switch to v1.15 but want to fix this bug, just remove the uses of this + flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when + pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract + compressed data from directory entries, to account for weird zipfiles which + contain zero-size compressed data on dir entries. Hopefully this fix won't + cause any issues on weird zip archives, because it assumes the low 16-bits of + zip external attributes are DOS attributes (which I believe they always are + in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the + internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, + tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping + (super useful for OpenGL apps), and explicit control over the compression + level (so you can set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), + tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG + file. + - Modified example2 to help test the + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix + possible src file fclose() leak if alignment bytes+local header file write + faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the + wrong central dir header offset, appears harmless in this release, but it + became a problem in the zip64 branch 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 + compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix + mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and + re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze + (static analysis) option and fixed all warnings (except for the silly "Use of + the comma-operator in a tested expression.." analysis warning, which I + purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and + tested Linux executables. The codeblocks workspace is compatible with + Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app + derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on + ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level + functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the + MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which + are too large (a limitation of the examples, not miniz itself). 4/12/12 v1.12 + - More comments, added low-level example5.c, fixed a couple minor + level_and_flags issues in the archive API's. level_and_flags can now be set + to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson + for the feedback/bug report. 5/28/11 v1.11 - Added statement from + unlicense.org 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput + now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 + vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved + compression perf. issues on some file types. + - Refactored the compression code for better readability and + maintainability. + - Added level 10 compression level (L10 has slightly better ratio than + level 9, but could have a potentially large drop in throughput on some + files). 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, + and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, + and Huffman-only streams. It performs and compresses approximately as well as + zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is + implemented as a single function coroutine: see tinfl_decompress(). It + supports decompression into a 32KB (or larger power of 2) wrapping buffer, or + into a memory block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory + allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough + functionality present for it to be a drop-in zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly + routines. Supports raw deflate streams or standard zlib streams with adler-32 + checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or + zlib static dictionaries. I've tried to closely emulate zlib's various + flavors of stream flushing and return status codes, but there are no + guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, + originally written by Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in + mind, with just enough abstraction to get the job done with minimal fuss. + There are simple API's to retrieve file information, read files from existing + archives, create new archives, append new files to existing archives, or + clone archive data from one archive to another. It supports archives located + in memory or the heap, on disk (using stdio.h), or you can specify custom + file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a + disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const + char *pArchive_name, size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an + archive, the entire central directory is located and read as-is into memory, + and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a + loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one + example) can be used to identify multiple versions of the same file in an + archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using + mz_zip_reader_get_num_files()) and retrieve detailed info on each file by + calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer + immediately writes compressed file data to disk and builds an exact image of + the central directory in memory. The central directory image is written all + at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file + data to any power of 2 alignment, which can be useful when the archive will + be read from optical media. Also, the writer supports placing arbitrary data + blobs at the very beginning of ZIP archives. Archives written using either + feature are still readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is + to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, + const char *pArchive_name, const void *pBuf, size_t buf_size, const void + *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be + appended to. Note the appending is done in-place and is not an atomic + operation, so if something goes wrong during the operation it's possible the + archive could be left without a central directory (although the local file + headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, + cloning only those bits you want to preserve into a new archive using using + the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and + rename the newly written archive, and you're done. This is safe but requires + a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using + mz_zip_writer_init_from_reader(), append new files as needed, then finalize + the archive which will write an updated central directory to the original + archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() + does.) There's a possibility that the archive's central directory could be + lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle + unencrypted, stored or deflated files. Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, + either cut and paste the below header, or create miniz.h, #define + MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your + target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define + MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before + including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), + stat64(), etc. Otherwise you won't be able to process large files (i.e. + 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be +// CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on +// stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able +// to get the current time, or get/set file times, and the C run-time funcs that +// get/set times won't be called. The current downside is the times written to +// your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive +// API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression +// API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent +// conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom +// user alloc/free/realloc callbacks to the zlib and archive API's, and a few +// stand-alone helper API's which don't provide custom user functions (such as +// tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +// TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc +// on Linux +#define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__i486__) || defined(__i486) || \ + defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient + * integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || \ + defined(_LP64) || defined(__LP64__) || defined(__ia64__) || \ + defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are +// reasonably fast (and don't involve compiler generated calls to helper +// functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __APPLE__ +#define ftello64 ftello +#define fseeko64 fseeko +#define fopen64 fopen +#define freopen64 freopen + +// Darwin OSX +#define MZ_PLATFORM 19 +#endif + +#ifndef MZ_PLATFORM +#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__) +#define MZ_PLATFORM 0 +#else +// UNIX +#define MZ_PLATFORM 3 +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some +// parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() +// unless you've modified the MZ_MALLOC macro) to release a block allocated from +// the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with +// ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with +// ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or + * modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: +// items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, + size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The +// other values are for advanced use (refer to the zlib docs). +enum { + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best +// possible compression (not zlib compatible, and may be very slow), +// MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s { + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func + zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been +// optimized purely for performance, not ratio. (This special func. is +// currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and +// MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with +// zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no +// header or footer) mem_level must be between [1, 9] (it's checked but +// ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as +// calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input +// and producing as much output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not +// available, and/or there's more output to be written but the output buffer +// is full). MZ_STREAM_END if all input has been consumed and all output bytes +// have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or +// output buffers are empty. (Fill up the input buffer or free up some output +// space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by deflate(), assuming flush is set to only +// MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on +// failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that +// controls the window size and whether or not the stream has been wrapped with +// a zlib header/footer: window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse +// zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the +// input as needed, and writing as much to the output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. On the first call, if flush is +// MZ_FINISH it's assumed the input and output buffers are both sized large +// enough to decompress the entire stream in a single call (this is slightly +// faster). MZ_FINISH implies that there are no more source bytes available +// beside what's already in the input buffer, and that the output buffer is +// large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or +// there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes +// have been written. For zlib streams, the adler-32 of the decompressed data +// has also been verified. MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is +// empty but the inflater needs more input to continue, or if the output +// buffer is not large enough. Call mz_inflate() again with more input data, +// or with more room in the output buffer (except when using single call +// decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on +// failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the +// error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used +// as a drop-in replacement for the subset of zlib that miniz.c supports. Define +// MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib +// in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional +// expression is constant" message. +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum { + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct { + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +typedef struct { + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is +// 0 this function returns the number of bytes needed to fully store the +// filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and +// modified times. This function only extracts files, not archive directory +// records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive +// file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient +// in-place file appends to occur on an existing archive. For archives opened +// using mz_zip_reader_init_file, pFilename must be the archive's filename so it +// can be reopened for writing. If the file can't be reopened, +// mz_zip_reader_end() will be called. For archives opened using +// mz_zip_reader_init_mem, the memory block must be growable using the realloc +// callback (which defaults to realloc unless you've overridden it). Finally, +// for archives opened using mz_zip_reader_init, the mz_zip_archive's user +// provided m_pWrite function cannot be NULL. Note: In-place archive +// modification is not recommended unless you know what you're doing, because if +// execution stops or something goes wrong before the archive is finalized the +// file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record +// the current local time into the archive. To add a directory entry, call this +// method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or +// just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records +// the disk file's modified time into the archive. level_and_flags - compression +// level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd +// with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no +// recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by +// the end of central directory record. After an archive is finalized, the only +// valid call on the mz_zip_archive struct is mz_zip_writer_end(). An archive +// must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if +// mz_zip_writer_init_file() was used. Note for the archive to be valid, it must +// have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) +// appends a memory blob to a ZIP archive. level_and_flags - compression level +// (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero +// or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and +// ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the +// input is a raw deflate stream. TINFL_FLAG_HAS_MORE_INPUT: If set, there are +// more input bytes available beyond the end of the supplied input buffer. If +// clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large +// enough to hold the entire decompressed stream. If clear, the output buffer is +// at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the +// decompressed bytes. +enum { + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data +// to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must call mz_free() on +// the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block +// in memory. Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the +// number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an +// internal 32KB buffer, and a user provided callback function will be called to +// flush the buffer. Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum { + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) \ + do { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function +// actually needed for decompression. All the other functions are just +// high-level helpers for improved usability. This is a universal API, i.e. it +// can be used as a building block to build any desired higher level +// decompression API. In the limit case, it can be called once per every byte +// input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum { + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct { + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], + m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag { + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, + m_check_adler32, m_dist, m_counter, m_num_extra, + m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], + m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly +// slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain +// the max. number of probes per dictionary search): TDEFL_DEFAULT_MAX_PROBES: +// The compressor defaults to 128 dictionary probes per dictionary search. +// 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ +// (slowest/best compression). +enum { + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before +// the deflate data, and the Adler-32 of the source data at the end. Otherwise, +// you'll get raw deflate data. TDEFL_COMPUTE_ADLER32: Always compute the +// adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more +// efficient lazy parsing. TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to +// decrease the compressor's initialization time to the minimum, but the output +// may vary from run to run given the same input (depending on the contents of +// memory). TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a +// distance of 1) TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per +// dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum { + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against +// the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must free() the returned +// block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in +// memory. Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be +// 1, 2, 3, or 4. The image pitch in bytes per scanline will be w*num_chans. +// The leftmost pixel on the top scanline is stored first in memory. level may +// range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL If flip is +// true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be +// larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write +// compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, + void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The +// above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +enum { + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed +// output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum { + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +// The low-level tdefl functions below may be used directly if the above helper +// functions aren't flexible enough. The low-level functions don't make any heap +// allocations, unlike the above helper functions. +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct { + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, + m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, + m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, + m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not +// dynamically allocate memory. pBut_buf_func: If NULL, output data will be +// supplied to the specified callback. In this case, the user should call the +// tdefl_compress_buffer() API for compression. If pBut_buf_func is NULL the +// user should always call the tdefl_compress() API. flags: See the above enums +// (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer +// as possible, and writing as much compressed data to the specified output +// buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a +// non-NULL tdefl_put_buf_func_ptr. tdefl_compress_buffer() always consumes the +// entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't +// defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be +// much slower on some files) window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, +// MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want +// the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) \ + (((mz_uint64)MZ_READ_LE32(p)) | \ + (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) \ + << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C +// implementation that balances processor cache usage against speed": +// http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { + static const mz_uint32 s_crc32[16] = { + 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, + 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; +} + +void mz_free(void *p) { MZ_FREE(p); } + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +static void def_free_func(void *opaque, void *address) { + (void)opaque, (void)address; + MZ_FREE(address); +} +static void *def_realloc_func(void *opaque, void *address, size_t items, + size_t size) { + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) { return MZ_VERSION; } + +int mz_deflateInit(mz_streamp pStream, int level) { + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, + MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy) { + tdefl_compressor *pComp; + mz_uint comp_flags = + TDEFL_COMPUTE_ADLER32 | + tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || + ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, + sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) { + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || + (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, + ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) { + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || + (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == + TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, + pStream->next_in, &in_bytes, pStream->next_out, + &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) { + mz_status = MZ_STREAM_ERROR; + break; + } else if (defl_status == TDEFL_STATUS_DONE) { + mz_status = MZ_STREAM_END; + break; + } else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { + if ((flush) || (pStream->total_in != orig_total_in) || + (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty + // tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, + 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level) { + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + return mz_compress2(pDest, pDest_len, pSource, source_len, + MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) { + return mz_deflateBound(NULL, source_len); +} + +typedef struct { + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) { + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, + sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) { + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) { + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) { + // MZ_FINISH on the first call implies that the input and output buffers are + // large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, + pStream->next_out, pStream->next_out, &out_bytes, + decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && + (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; + } + + for (;;) { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress( + &pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, + pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some + // uncompressed data left in the output dictionary - + // oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress + // without supplying more input or by setting flush + // to MZ_FINISH. + else if (flush == MZ_FINISH) { + // The output buffer MUST be large to hold the remaining uncompressed data + // when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's + // at least 1 more byte on the way. If there's no more room left in the + // output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || + (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR + : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) { + static struct { + int m_err; + const char *m_pDesc; + } s_error_descs[] = {{MZ_OK, ""}, + {MZ_STREAM_END, "stream end"}, + {MZ_NEED_DICT, "need dictionary"}, + {MZ_ERRNO, "file error"}, + {MZ_STREAM_ERROR, "stream error"}, + {MZ_DATA_ERROR, "data error"}, + {MZ_MEM_ERROR, "out of memory"}, + {MZ_BUF_ERROR, "buf error"}, + {MZ_VERSION_ERROR, "version error"}, + {MZ_PARAM_ERROR, "parameter error"}}; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all +// compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do { \ + for (;;) { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt +// to read beyond the input buf, then something is wrong with the input because +// the inflator never reads ahead more than it needs to. Currently +// TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) \ + do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for (;;) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes +// remaining in the input buffer falls below 2. It reads just enough bytes from +// the input stream that are needed to decode the next Huffman code (and +// absolutely no more). It works by trying to fully decode a Huffman code by +// using whatever bits are currently present in the bit buffer. If this fails, +// it reads another byte, and tries again until it succeeds or until the bit +// buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex +// than you would initially expect because the zlib API expects the decompressor +// to never read beyond the final byte of the deflate stream. (In other words, +// when this macro wants to read another byte from the input, it REALLY needs +// another byte in order to fully decode the next Huffman code.) Handling this +// properly is particularly important on raw deflate (non-zlib) streams, which +// aren't followed by a byte aligned adler-32. The slow path is only executed at +// the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | \ + (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= \ + 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags) { + static const int s_length_base[31] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const int s_length_extra[31] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 4, 4, 5, 5, 5, 5, 0, 0, 0}; + static const int s_dist_base[32] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, + 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0}; + static const int s_dist_extra[32] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const mz_uint8 s_length_dezigzag[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + static const int s_min_table_sizes[3] = {257, 1, 4}; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = + pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = + pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) + ? (size_t)-1 + : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, + dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer + // is large enough to hold the entire output file (in which case it doesn't + // matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || + (pOut_buf_next < pOut_buf_start)) { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || + (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || + ((out_buf_size_mask + 1) < + (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != + (mz_uint)(0xFFFF ^ + (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } else { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), + (size_t)(pIn_buf_end - pIn_buf_cur)), + counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } else if (r->m_type == 3) { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } else { + if (r->m_type == 1) { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } else { + for (counter = 0; counter < 3; counter++) { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], + total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; + sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { + mz_uint rev_code = 0, l, cur_code, + code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == + (tree_cur = pTable->m_look_up[rev_code & + (TINFL_FAST_LOOKUP_SIZE - 1)])) { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = + (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) { + for (counter = 0; + counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, + (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, + r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, + r->m_len_codes + r->m_table_sizes[0], + r->m_table_sizes[1]); + } + } + for (;;) { + mz_uint8 *pSrc; + for (;;) { + if (((pIn_buf_end - pIn_buf_cur) < 4) || + ((pOut_buf_end - pOut_buf_cur) < 2)) { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } else { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { + while (counter--) { + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = + pOut_buf_start[(dist_from_out_buf_start++ - dist) & + out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) { + if (counter) { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_SKIP_BITS(32, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf; + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & + (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && + (status >= 0)) { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, + s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && + (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && + (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) { + size_t src_buf_size = src_buf_len - src_buf_ofs, + dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress( + &decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, + (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, + &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = + tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, + (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED + : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, + dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = + tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, + &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && + (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression +// API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, + 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, + 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, + 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, + 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, + 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, + 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, + 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 285}; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0}; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17}; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, + 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29}; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted +// values. +typedef struct { + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, + tdefl_sym_freq *pSyms0, + tdefl_sym_freq *pSyms1) { + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = + pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, +// alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) { + while (root >= 0 && (int)A[root].m_key == dpth) { + used++; + root--; + } + while (avbl > used) { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, + int code_list_len, + int max_code_size) { + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, + int table_len, int code_size_limit, + int static_table) { + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } else { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], + *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, + code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)( \ + d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = \ + (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) { + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, + rle_repeat_count, packed_code_sizes_index; + mz_uint8 + code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], + sizeof(mz_uint8) * num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], + sizeof(mz_uint8) * num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, + sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = + (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } else if (++rle_repeat_count == 6) { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { + TDEFL_RLE_PREV_CODE_SIZE(); + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes + [2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS( + d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; + packed_code_sizes_index < num_packed_code_sizes;) { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], + "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) { + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && \ + MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], + d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], + num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + if (match_dist < 512) { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } else { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && + // MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) { + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = + ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && + (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = + ((d->m_pPut_buf_func == NULL) && + ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) + ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) + : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = + tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || + (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output + // buffer and send a raw block instead. + if (((use_raw_block) || + ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= + d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) { + TDEFL_PUT_BITS( + d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], + 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed + // block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) { + if (flush == TDEFL_FINISH) { + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } else { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { + if (d->m_pPut_buf_func) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } else if (pOutput_buf_start == d->m_output_buf) { + int bytes_to_copy = (int)MZ_MIN( + (size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, + bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } else { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) ((p)[0] | (p)[1] << 8) +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), + s01 = *s; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (*q != s01) + continue; + p = s; + probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (--probe_len > 0)); + if (!probe_len) { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); + break; + } else if ((probe_len = ((mz_uint)(p - s) * 2) + + (mz_uint)(*(const mz_uint8 *)p == + *(const mz_uint8 *)q)) > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == + max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && \ + (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) { + // Faster, minimally featured LZRW1-style match+parse loop with better + // register utilization. Intended for applications where raw throughput is + // valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, + lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, + total_lz_bytes = d->m_total_lz_bytes, + num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = + (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, + MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = + (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & + TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= + dict_size) && + ((mz_uint32)( + *(d->m_dict + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 1)) + << 8) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 2)) + << 16)) == first_trigram)) { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = + (const mz_uint16 *)(d->m_dict + + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)); + mz_uint32 probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (*(++p) == *(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || + ((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U))) { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } else { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 1) && + (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - + TDEFL_MIN_MATCH_LEN]]++; + } + } else { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, + mz_uint8 lit) { + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void +tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && + (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) { + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to + // TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK, + ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } else { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << (TDEFL_LZ_HASH_SHIFT * 2)) ^ + (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + c) & + (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = + MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = + d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } else { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, + d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U)) || + (cur_pos == cur_match_dist) || + ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) { + if (cur_match_len > d->m_saved_match_len) { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } else { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } else if (!cur_match_dist) + tdefl_record_literal(d, + d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || + (cur_match_len >= 128)) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output + // buffer. + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && + (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= + d->m_total_lz_bytes) || + (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { + if (d->m_pIn_buf_size) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, + d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, + d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE + : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush) { + if (!d) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == + ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || + (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || + (pIn_buf_size && *pIn_buf_size && !pIn_buf) || + (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | + TDEFL_RLE_MATCHES)) == 0)) { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && + (pIn_buf)) + d->m_adler32 = + (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, + d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && + (!d->m_output_flush_remaining)) { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush) { + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = + d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = + d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == + TDEFL_STATUS_OKAY); + succeeded = + succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == + TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct { + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, + void *pUser) { + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = {0, 1, 6, 32, 16, 32, + 128, 256, 512, 768, 1500}; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we +// want a bit more compression and it's fine if throughput to fall off a cliff +// on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy) { + mz_uint comp_flags = + s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | + ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif // MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) // nonstandard extension used : non-constant + // aggregate initializer (also supported by GNU + // C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public +// domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files +// generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip) { + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was + // defined. + static const mz_uint s_tdefl_png_num_probes[11] = { + 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500}; + tdefl_compressor *pComp = + (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { + MZ_FREE(pComp); + return NULL; + } + // write dummy header + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, + s_tdefl_png_num_probes[MZ_MIN(10, level)] | + TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, + (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, + bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != + TDEFL_STATUS_DONE) { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + // write real header + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41] = {0x89, + 0x50, + 0x4e, + 0x47, + 0x0d, + 0x0a, + 0x1a, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x49, + 0x48, + 0x44, + 0x52, + 0, + 0, + (mz_uint8)(w >> 8), + (mz_uint8)w, + 0, + 0, + (mz_uint8)(h >> 8), + (mz_uint8)h, + 8, + chans[num_chans], + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (mz_uint8)(*pLen_out >> 24), + (mz_uint8)(*pLen_out >> 16), + (mz_uint8)(*pLen_out >> 8), + (mz_uint8)*pLen_out, + 0x49, + 0x44, + 0x41, + 0x54}; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter( + "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, + *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out) { + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we + // can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's + // where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, + pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#include + +#if defined(_MSC_VER) +static FILE *mz_fopen(const char *pFilename, const char *pMode) { + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#else +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#else +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler +// alignment and platform endian issues, miniz.c doesn't use structs for any of +// this stuff. +enum { + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct { + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag { + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. + */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 + * will also be slammed to true too, even if we didn't find a zip64 end of + * central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write + * helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) \ + (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) \ + ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, + mz_zip_array *pArray) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t min_new_capacity, + mz_uint growing) { + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, + pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_capacity, + mz_uint growing) { + if (new_capacity > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_size, + mz_uint growing) { + if (new_size > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t n) { + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, + mz_zip_array *pArray, + const void *pElements, + size_t n) { + if (0 == n) + return MZ_TRUE; + if (!pElements) + return MZ_FALSE; + + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, + pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(time_t time, mz_uint16 *pDOS_time, + mz_uint16 *pDOS_date) { +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, + time_t *pTime) { + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= + * 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, + time_t modified_time) { + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, + mz_zip_error err_num) { + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, + mz_uint32 flags) { + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool +mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, mz_uint r_index) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), + r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central +// directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), +// but it could allocate memory.) +static void +mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) { + int child, root = start; + for (;;) { + if ((child = (root << 1) + 1) >= size) + break; + child += + (((child + 1) < size) && + (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + start--; + } + + end = size - 1; + while (end > 0) { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) { + if ((child = (root << 1) + 1) >= end) + break; + child += + (((child + 1) < end) && + mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, + mz_uint32 record_sig, + mz_uint32 record_size, + mz_int64 *pOfs) { + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = + MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) { + int i, + n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" + * (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= + (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, + mz_uint flags) { + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, + cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = + ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = + (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first + * 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig( + pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { + if (pZip->m_pRead(pZip->m_pIO_opaque, + cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, + pZip64_locator, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) { + mz_uint32 zip64_total_num_of_disks = + MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < + (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = + (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough + * for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && + ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another + * heap block to hold the unsorted central dir file record offsets, and + * possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, + MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, + pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) { + if (!mz_zip_array_resize(pZip, + &pZip->m_pState->m_sorted_central_dir_offsets, + pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, + pZip->m_pState->m_central_dir.m_p, + cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some + * basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { + mz_uint total_header_size, disk_index, bit_flags, filename_size, + ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || + (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + i) = + (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, + mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == + MZ_UINT32_MAX)) { + /* Attempt to find zip64 extended information field in the entry's extra + * data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) { + const mz_uint8 *pExtra_data; + void *buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > + n) { + buf = MZ_MALLOC(ext_data_size); + if (buf == NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, + cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + filename_size, + buf, ext_data_size) != ext_data_size) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8 *)buf; + } else { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > + extra_size_remaining) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { + /* Ok, the archive didn't have any zip64 headers but it uses a + * zip64 extended information field so mark it as zip64 anyway + * (this can occur with infozip's zip util when it reads + * compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = + extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext + * data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && + (decomp_size != comp_size)) || + (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || + ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > + n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags) { + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) + ? 0 + : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags) { + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags) { + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 * +mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, + // which wasn't correct. Most/all zip writers (hopefully) set DOS + // file/directory attributes in the low 16-bits, so check for the DOS + // directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = + mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), + MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, + p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), + n); + pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { + if (filename_buf_size) + pFilename[0] = '\0'; + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, + const char *pB, + mz_uint len, + mz_uint flags) { + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int +mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, const char *pR, mz_uint r_len) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) { + int m = (l + h) >> 1, file_index = pIndices[m], + comp = + mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, + file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags) { + mz_uint file_index; + size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && + (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); + if (name_len > 0xFFFF) + return -1; + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > 0xFFFF) + return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = + (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), + file_comment_len = + MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || + (!mz_zip_reader_string_equal(pComment, pFile_comment, + file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { + int ofs = filename_len - 1; + do { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || + (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && + (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size) { + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, + out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size + : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input + // buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else if (pUser_read_buf) { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } else { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && + (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do { + size_t in_buf_size, + out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | + (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, pUser_read_buf, + user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags) { + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags) { + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, + buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags) { + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, + flags)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, + out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input + // buffer. + if (pZip->m_pState->m_pMem) { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && + (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && + (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, + (size_t)file_stat.m_comp_size); + // cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + // comp_remaining = 0; + } else { + while (comp_remaining) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32( + file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } else { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else { + do { + mz_uint8 *pWrite_buf_cur = + (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, + out_buf_size = + TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, + comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != + out_buf_size) { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || + (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && + (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, + flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, + const void *pBuf, size_t n) { + (void)ofs; + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, + mz_uint flags) { + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback( + pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) { + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); + } +#endif + + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags) { + int file_index = + mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || + (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if ((!n) || + ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + return 0; + + if (new_size > pState->m_mem_capacity) { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + while (new_capacity < new_size) + new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc( + pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size) { + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, + size_to_reserve_at_beginning))) { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning) { + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) { + mz_uint64 cur_ofs = 0; + char buf[4096]; + MZ_CLEAR_OBJ(buf); + do { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max + // size + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) { +#ifdef MINIZ_NO_STDIO + pFilename; + return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == + (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is + // NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } else if (pState->m_pMem) { + // Archive lives in a memory block. Assume it's from the heap that we can + // resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the + // user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory + // location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags) { + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, + level_and_flags, 0, 0); +} + +typedef struct { + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, + void *pUser) { + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, + pState->m_cur_archive_file_ofs, pBuf, + len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date) { + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, + mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, + mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + (void)pZip; + mz_uint16 version_made_by = 10 * MZ_VER_MAJOR + MZ_VER_MINOR; + version_made_by |= (MZ_PLATFORM << 8); + + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_MADE_BY_OFS, version_made_by); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir( + mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, + mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, + mz_uint32 ext_attributes) { + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || + (((mz_uint64)pState->m_central_dir.m_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header( + pZip, central_dir_header, filename_size, extra_size, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, + dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, + filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, + extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, + comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, + ¢ral_dir_ofs, 1))) { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { + // Basic ZIP archive filename validity checks: Valid filenames cannot start + // with a forward slash, cannot contain a drive letter, and cannot use + // DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint +mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & + (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, + mz_uint64 cur_file_ofs, mz_uint32 n) { + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32) { + mz_uint32 ext_attributes = 0; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = + ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || + (!pArchive_name) || ((comment_size) && (!pComment)) || + (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an + // allocation fails the file remains unmodified. (A good idea if we're doing + // an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size)) || + (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { + uncomp_crc32 = + (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, + buf_size) != buf_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } else if (buf_size) { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != + TDEFL_STATUS_DONE)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes) { + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + time_t file_modified_time; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, uncomp_size = 0, + comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || + ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return MZ_FALSE; + mz_zip_time_t_to_dos_time(file_modified_time, &dos_time, &dos_date); + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = + pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) { + while (uncomp_remaining) { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || + (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, + n) != n)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = + (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } else { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for (;;) { + size_t in_buf_size = + (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32( + uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, + uncomp_remaining ? TDEFL_NO_FLUSH + : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) { + result = MZ_TRUE; + break; + } else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); + pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index) { + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == + (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > + 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, + pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, + num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = + n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)MZ_MAX(sizeof(mz_uint32) * 4, + MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, + comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + // cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, + local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back( + pZip, &pState->m_central_dir, + pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || + ((pZip->m_archive_size + pState->m_central_dir.m_size + + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, + pState->m_central_dir.m_p, + (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, + pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, + sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize) { + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags) { + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || + ((comment_size) && (!pComment)) || + ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } else { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + level_and_flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = + mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, + pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid + // central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint flags) { + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, + flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ diff --git a/ports/wapy/cmod/common/_zipfile/mod_zipfile.c b/ports/wapy/cmod/common/_zipfile/mod_zipfile.c new file mode 100644 index 000000000..49478d1d3 --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/mod_zipfile.c @@ -0,0 +1,313 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/common/_zipfile.pym */ +/* + _zipfile.c AUTO-GENERATED by /data/cross/wapy/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// reuse embed exports +extern void print(mp_obj_t str); +extern void null_pointer_exception(void); +extern mp_obj_t PyBytes_FromString(char *string); +extern const char *nullbytes; + +// =========== embedded header from .pym ============ + +#include "zip.h" + +// end of embedded header +// ============================================================= + +typedef struct __zipfile_ZipFile_obj_t { + mp_obj_base_t base; + uint8_t hash; + void * zbuf; + size_t zbuf_size; + struct zip_t * zip; + char * path; +} _zipfile_ZipFile_obj_t; + + + +const mp_obj_type_t _zipfile_ZipFile_type; //forward decl + +mp_obj_t +_zipfile_ZipFile_make_new( const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args ) { + + mp_arg_check_num(n_args, n_kw, 0, 0, true); + + _zipfile_ZipFile_obj_t *self = m_new_obj(_zipfile_ZipFile_obj_t); + + self->base.type = &_zipfile_ZipFile_type; + +// self->hash = object_id++; +// printf("Object serial #%d\n", self->hash ); + + self->hash = 0; + self->zbuf = NULL; + self->zbuf_size = 0; + self->zip = None; + self->path = NULL; + + return MP_OBJ_FROM_PTR(self); +} + + +void +_zipfile_ZipFile_print( const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind ) { + // get a ptr to the C-struct of the object + _zipfile_ZipFile_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // print the number + mp_printf (print, "<_zipfile.ZipFile at 0x%p>", self); +} + + +// Begin : class ZipFile: + + + +STATIC mp_obj_t // bytes -> bytes +_zipfile_ZipFile_read(size_t argc, const mp_obj_t *argv) { + mp_obj_t __preturn__; + char * __creturn__ = (char *)nullbytes; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // def read(name: const_char_p="") -> bytes: + // ('const_char_p', '""') => const char * = "" + + const char *name; // ('const char *', 'name', '""') + if (argc>1) { name = mp_obj_str_get_str(argv[1]); } else { name = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body -------- + zip_entry_open(self->zip, name); + zip_entry_read(self->zip, &self->zbuf, &self->zbuf_size); + zip_entry_close(self->zip); + { //try: + if (self->zbuf) { + __creturn__ = (char *)self->zbuf; + } + { //finally: + __preturn__ = PyBytes_FromString(__creturn__); + free(self->zbuf); + } + } +return __preturn__; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_read_obj, 0, 2, _zipfile_ZipFile_read); + + + +STATIC mp_obj_t // void -> void +_zipfile_ZipFile_open(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // def open(path : const_char_p="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *path; // ('const char *', 'path', '""') + if (argc>1) { path = mp_obj_str_get_str(argv[1]); } else { path = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + self->path = strdup(path); + self->hash = strlen(path); + printf("ZIPFILE[%d;%s]self\n", self->hash, self->path); + self->zip = zip_open(self->path, 0, 'r'); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_open_obj, 0, 2, _zipfile_ZipFile_open); + + + +STATIC mp_obj_t // bytes -> bytes +_zipfile_ZipFile_filename(size_t argc, const mp_obj_t *argv) { +// opt: no finally bytes slot + char * __creturn__ = (char *)nullbytes; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + + // ------- method body (try/finally) ----- + { __creturn__ = (char *)self->path; goto lreturn__; }; +lreturn__: return PyBytes_FromString(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_filename_obj, 0, 1, _zipfile_ZipFile_filename); + + + +STATIC mp_obj_t // void -> void +_zipfile_ZipFile_close(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + + // ------- method body (try/finally) ----- + if (self->path) { + zip_close(self->zip); + free(self->path); + } +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_close_obj, 0, 1, _zipfile_ZipFile_close); + + + +STATIC mp_obj_t // int -> int +_zipfile_ZipFile_pouet(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // TODO: async : resume with go after last yield + + // ------- method body (try/finally) ----- + if (1) { + printf(" pouet arg0 %zu\n", argc ); + { __creturn__ = (long)42; goto lreturn__; }; + } +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_pouet_obj, 0, 1, _zipfile_ZipFile_pouet); + + + +STATIC mp_obj_t // int -> int +_zipfile_ZipFile_pouet2(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // TODO: async : resume with go after last yield + + // ------- method body (try/finally) ----- + if (1) { + if (2) { + { __creturn__ = (long)43; goto lreturn__; }; + } + } +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_pouet2_obj, 0, 1, _zipfile_ZipFile_pouet2); + + + +// ++++++++ class ZipFile interface +++++++ + + + +STATIC const mp_map_elem_t _zipfile_ZipFile_dict_table[] = { + {MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&_zipfile_ZipFile_read_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&_zipfile_ZipFile_open_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_filename), (mp_obj_t)&_zipfile_ZipFile_filename_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&_zipfile_ZipFile_close_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_pouet), (mp_obj_t)&_zipfile_ZipFile_pouet_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_pouet2), (mp_obj_t)&_zipfile_ZipFile_pouet2_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(_zipfile_ZipFile_dict, _zipfile_ZipFile_dict_table); + + + +const mp_obj_type_t _zipfile_ZipFile_type = { + + // "inherit" the type "type" + { &mp_type_type }, + + // give it a name + .name = MP_QSTR_ZipFile, + + // give it a print-function + .print = _zipfile_ZipFile_print, + + // give it a constructor + .make_new = _zipfile_ZipFile_make_new, + + // and its locals members + .locals_dict = (mp_obj_dict_t*)&_zipfile_ZipFile_dict, +}; + +// End: **************** class ZipFile ************ + + + +// global module dict : + + + +STATIC const mp_map_elem_t _zipfile_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR__zipfile) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + + +// Classes : + +// ZipFile class + {MP_OBJ_NEW_QSTR(MP_QSTR_ZipFile), (mp_obj_t)&_zipfile_ZipFile_type }, + + +// __main__ + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(_zipfile_dict, _zipfile_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module__zipfile = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&_zipfile_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR__zipfile, module__zipfile, MODULE__ZIPFILE_ENABLED); + diff --git a/ports/wapy/cmod/common/_zipfile/zip.c b/ports/wapy/cmod/common/_zipfile/zip.c new file mode 100644 index 000000000..3b2821e6a --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/zip.c @@ -0,0 +1,959 @@ +/* + * 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 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 __STDC_WANT_LIB_EXT1__ 1 + +#include +#include +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +/* Win32, DOS, MSVC, MSVS */ +#include + +#define MKDIR(DIRNAME) _mkdir(DIRNAME) +#define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL) +#define HAS_DEVICE(P) \ + ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ + (P)[1] == ':') +#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) + +#else + +#include // needed for symlink() on BSD +int symlink(const char *target, const char *linkpath); // needed on Linux + +#define MKDIR(DIRNAME) mkdir(DIRNAME, 0755) +#define STRCLONE(STR) ((STR) ? strdup(STR) : NULL) + +#endif + +#include "miniz.h" +#include "zip.h" + +#ifndef HAS_DEVICE +#define HAS_DEVICE(P) 0 +#endif + +#ifndef FILESYSTEM_PREFIX_LEN +#define FILESYSTEM_PREFIX_LEN(P) 0 +#endif + +#ifndef ISSLASH +#define ISSLASH(C) ((C) == '/' || (C) == '\\') +#endif + +#define CLEANUP(ptr) \ + do { \ + if (ptr) { \ + free((void *)ptr); \ + ptr = NULL; \ + } \ + } while (0) + +static const char *base_name(const char *name) { + char const *p; + char const *base = name += FILESYSTEM_PREFIX_LEN(name); + int all_slashes = 1; + + for (p = name; *p; p++) { + if (ISSLASH(*p)) + base = p + 1; + else + all_slashes = 0; + } + + /* If NAME is all slashes, arrange to return `/'. */ + if (*base == '\0' && ISSLASH(*name) && all_slashes) + --base; + + return base; +} + +static int mkpath(char *path) { + char *p; + char npath[MAX_PATH + 1]; + int len = 0; + int has_device = HAS_DEVICE(path); + + memset(npath, 0, MAX_PATH + 1); + if (has_device) { + // only on windows + npath[0] = path[0]; + npath[1] = path[1]; + len = 2; + } + for (p = path + len; *p && len < MAX_PATH; p++) { + if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) { +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if ('\\' == *p) { + *p = '/'; + } +#endif + + if (MKDIR(npath) == -1) { + if (errno != EEXIST) { + return -1; + } + } + } + npath[len++] = *p; + } + + return 0; +} + +static char *strrpl(const char *str, size_t n, char oldchar, char newchar) { + char c; + size_t i; + char *rpl = (char *)calloc((1 + n), sizeof(char)); + char *begin = rpl; + if (!rpl) { + return NULL; + } + + for (i = 0; (i < n) && (c = *str++); ++i) { + if (c == oldchar) { + c = newchar; + } + *rpl++ = c; + } + + return begin; +} + +struct zip_entry_t { + int index; + char *name; + mz_uint64 uncomp_size; + mz_uint64 comp_size; + mz_uint32 uncomp_crc32; + mz_uint64 offset; + mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint64 header_offset; + mz_uint16 method; + mz_zip_writer_add_state state; + tdefl_compressor comp; + mz_uint32 external_attr; + time_t m_time; +}; + +struct zip_t { + mz_zip_archive archive; + mz_uint level; + struct zip_entry_t entry; +}; + +struct zip_t *zip_open(const char *zipname, int level, char mode) { + struct zip_t *zip = NULL; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + goto cleanup; + } + + if (level < 0) + level = MZ_DEFAULT_LEVEL; + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + goto cleanup; + } + + zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) + goto cleanup; + + zip->level = (mz_uint)level; + switch (mode) { + case 'w': + // Create a new archive. + if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) { + // Cannot initialize zip_archive writer + goto cleanup; + } + break; + + case 'r': + case 'a': + if (!mz_zip_reader_init_file( + &(zip->archive), zipname, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + goto cleanup; + } + if (mode == 'a' && + !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) { + mz_zip_reader_end(&(zip->archive)); + goto cleanup; + } + break; + + default: + goto cleanup; + } + + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +void zip_close(struct zip_t *zip) { + if (zip) { + // Always finalize, even if adding failed for some reason, so we have a + // valid central directory. + mz_zip_writer_finalize_archive(&(zip->archive)); + + mz_zip_writer_end(&(zip->archive)); + mz_zip_reader_end(&(zip->archive)); + + CLEANUP(zip); + } +} + +int zip_is64(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (!zip->archive.m_pState) { + // zip state is not initialized + return -1; + } + + return (int)zip->archive.m_pState->m_zip64; +} + +int zip_entry_open(struct zip_t *zip, const char *entryname) { + size_t entrylen = 0; + mz_zip_archive *pzip = NULL; + mz_uint num_alignment_padding_bytes, level; + mz_zip_archive_file_stat stats; + + if (!zip || !entryname) { + return -1; + } + + entrylen = strlen(entryname); + if (entrylen < 1) { + return -1; + } + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(entryname, entrylen, '\\', '/'); + if (!zip->entry.name) { + // Cannot parse zip entry name + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + zip->entry.index = + mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0); + if (zip->entry.index < 0) { + goto cleanup; + } + + if (!mz_zip_reader_file_stat(pzip, (mz_uint)zip->entry.index, &stats)) { + goto cleanup; + } + + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; + zip->entry.m_time = stats.m_time; + + return 0; + } + + zip->entry.index = (int)zip->archive.m_total_files; + zip->entry.comp_size = 0; + zip->entry.uncomp_size = 0; + zip->entry.uncomp_crc32 = MZ_CRC32_INIT; + zip->entry.offset = zip->archive.m_archive_size; + zip->entry.header_offset = zip->archive.m_archive_size; + memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); + zip->entry.method = 0; + + // UNIX or APPLE +#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19 + // regular file with rw-r--r-- persmissions + zip->entry.external_attr = (mz_uint32)(0100644) << 16; +#else + zip->entry.external_attr = 0; +#endif + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); + + if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) { + // Wrong zip mode + goto cleanup; + } + if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) { + // Wrong zip compression level + goto cleanup; + } + // no zip64 support yet + if ((pzip->m_total_files == 0xFFFF) || + ((pzip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + entrylen) > 0xFFFFFFFF)) { + // No zip64 support yet + goto cleanup; + } + if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset, + num_alignment_padding_bytes + + sizeof(zip->entry.header))) { + // Cannot memset zip entry header + goto cleanup; + } + + zip->entry.header_offset += num_alignment_padding_bytes; + if (pzip->m_file_offset_alignment) { + MZ_ASSERT( + (zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0); + } + zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header); + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, + entrylen) != entrylen) { + // Cannot write data to zip entry + goto cleanup; + } + + zip->entry.offset += entrylen; + level = zip->level & 0xF; + if (level) { + zip->entry.state.m_pZip = pzip; + zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset; + zip->entry.state.m_comp_size = 0; + + if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, + &(zip->entry.state), + (int)tdefl_create_comp_flags_from_zip_params( + (int)level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + // Cannot initialize the zip compressor + goto cleanup; + } + } + + zip->entry.m_time = time(NULL); + + return 0; + +cleanup: + CLEANUP(zip->entry.name); + return -1; +} + +int zip_entry_openbyindex(struct zip_t *zip, int index) { + mz_zip_archive *pZip = NULL; + mz_zip_archive_file_stat stats; + mz_uint namelen; + const mz_uint8 *pHeader; + const char *pFilename; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pZip = &(zip->archive); + if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) { + // open by index requires readonly mode + return -1; + } + + if (index < 0 || (mz_uint)index >= pZip->m_total_files) { + // index out of range + return -1; + } + + if (!(pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, + mz_uint32, index)))) { + // cannot find header in central directory + return -1; + } + + namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(pFilename, namelen, '\\', '/'); + if (!zip->entry.name) { + // local entry name is NULL + return -1; + } + + if (!mz_zip_reader_file_stat(pZip, (mz_uint)index, &stats)) { + return -1; + } + + zip->entry.index = index; + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; + zip->entry.m_time = stats.m_time; + + return 0; +} + +int zip_entry_close(struct zip_t *zip) { + mz_zip_archive *pzip = NULL; + mz_uint level; + tdefl_status done; + mz_uint16 entrylen; + mz_uint16 dos_time, dos_date; + int status = -1; + + if (!zip) { + // zip_t handler is not initialized + goto cleanup; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + status = 0; + goto cleanup; + } + + level = zip->level & 0xF; + if (level) { + done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH); + if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) { + // Cannot flush compressed buffer + goto cleanup; + } + zip->entry.comp_size = zip->entry.state.m_comp_size; + zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs; + zip->entry.method = MZ_DEFLATED; + } + + entrylen = (mz_uint16)strlen(zip->entry.name); + // no zip64 support yet + if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) { + // No zip64 support, yet + goto cleanup; + } + + mz_zip_time_t_to_dos_time(zip->entry.m_time, &dos_time, &dos_date); + if (!mz_zip_writer_create_local_dir_header( + pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, + zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, + dos_time, dos_date)) { + // Cannot create zip entry header + goto cleanup; + } + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, + zip->entry.header, + sizeof(zip->entry.header)) != sizeof(zip->entry.header)) { + // Cannot write zip entry header + goto cleanup; + } + + if (!mz_zip_writer_add_to_central_dir( + pzip, zip->entry.name, entrylen, NULL, 0, "", 0, + zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, + zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset, + zip->entry.external_attr)) { + // Cannot write to zip central dir + goto cleanup; + } + + pzip->m_total_files++; + pzip->m_archive_size = zip->entry.offset; + status = 0; + +cleanup: + if (zip) { + zip->entry.m_time = 0; + CLEANUP(zip->entry.name); + } + return status; +} + +const char *zip_entry_name(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return NULL; + } + + return zip->entry.name; +} + +int zip_entry_index(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return zip->entry.index; +} + +int zip_entry_isdir(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (zip->entry.index < 0) { + // zip entry is not opened + return -1; + } + + return (int)mz_zip_reader_is_file_a_directory(&zip->archive, + (mz_uint)zip->entry.index); +} + +unsigned long long zip_entry_size(struct zip_t *zip) { + return zip ? zip->entry.uncomp_size : 0; +} + +unsigned int zip_entry_crc32(struct zip_t *zip) { + return zip ? zip->entry.uncomp_crc32 : 0; +} + +int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) { + mz_uint level; + mz_zip_archive *pzip = NULL; + tdefl_status status; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (buf && bufsize > 0) { + zip->entry.uncomp_size += bufsize; + zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32( + zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize); + + level = zip->level & 0xF; + if (!level) { + if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, + bufsize) != bufsize)) { + // Cannot write buffer + return -1; + } + zip->entry.offset += bufsize; + zip->entry.comp_size += bufsize; + } else { + status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, + TDEFL_NO_FLUSH); + if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) { + // Cannot compress buffer + return -1; + } + } + } + + return 0; +} + +int zip_entry_fwrite(struct zip_t *zip, const char *filename) { + int status = 0; + size_t n = 0; + FILE *stream = NULL; + mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE]; + struct MZ_FILE_STAT_STRUCT file_stat; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + memset(buf, 0, MZ_ZIP_MAX_IO_BUF_SIZE); + memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT)); + if (MZ_FILE_STAT(filename, &file_stat) != 0) { + // problem getting information - check errno + return -1; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + zip->entry.external_attr |= 0x01; + } + zip->entry.external_attr |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + zip->entry.m_time = file_stat.st_mtime; + +#if defined(_MSC_VER) + if (fopen_s(&stream, filename, "rb")) +#else + if (!(stream = fopen(filename, "rb"))) +#endif + { + // Cannot open filename + return -1; + } + + while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > + 0) { + if (zip_entry_write(zip, buf, n) < 0) { + status = -1; + break; + } + } + fclose(stream); + + return status; +} + +ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + size_t size = 0; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + *buf = mz_zip_reader_extract_to_heap(pzip, idx, &size, 0); + if (*buf && bufsize) { + *bufsize = size; + } + return size; +} + +ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { + mz_zip_archive *pzip = NULL; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index, + buf, bufsize, 0, NULL, 0)) { + return -1; + } + + return (ssize_t)zip->entry.uncomp_size; +} + +int zip_entry_fread(struct zip_t *zip, const char *filename) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + mz_uint32 xattr = 0; + mz_zip_archive_file_stat info; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + if (!mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) { + return -1; + } + +#if defined(_MSC_VER) +#else + if (!mz_zip_reader_file_stat(pzip, idx, &info)) { + // Cannot get information about zip archive; + return -1; + } + + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(filename, (mode_t)xattr) < 0) { + return -1; + } + } +#endif + + return 0; +} + +int zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *buf, size_t bufsize), + void *arg) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) + ? 0 + : -1; +} + +int zip_total_entries(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return (int)zip->archive.m_total_files; +} + +int zip_create(const char *zipname, const char *filenames[], size_t len) { + int status = 0; + size_t i; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_uint32 ext_attributes = 0; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + return -1; + } + + // Create a new archive. + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive writer + return -1; + } + + memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT)); + + for (i = 0; i < len; ++i) { + const char *name = filenames[i]; + if (!name) { + status = -1; + break; + } + + if (MZ_FILE_STAT(name, &file_stat) != 0) { + // problem getting information - check errno + status = -1; + break; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + ext_attributes |= 0x01; + } + ext_attributes |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + + if (!mz_zip_writer_add_file(&zip_archive, base_name(name), name, "", 0, + ZIP_DEFAULT_COMPRESSION_LEVEL, + ext_attributes)) { + // Cannot add file to zip_archive + status = -1; + break; + } + } + + mz_zip_writer_finalize_archive(&zip_archive); + mz_zip_writer_end(&zip_archive); + return status; +} + +int zip_extract(const char *zipname, const char *dir, + int (*on_extract)(const char *filename, void *arg), void *arg) { + int status = -1; + mz_uint i, n; + char path[MAX_PATH + 1]; + char symlink_to[MAX_PATH + 1]; + mz_zip_archive zip_archive; + mz_zip_archive_file_stat info; + size_t dirlen = 0; + mz_uint32 xattr = 0; + + memset(path, 0, sizeof(path)); + memset(symlink_to, 0, sizeof(symlink_to)); + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!zipname || !dir) { + // Cannot parse zip archive name + return -1; + } + + dirlen = strlen(dir); + if (dirlen + 1 > MAX_PATH) { + return -1; + } + + // Now try to open the archive. + if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive reader + return -1; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + +#if defined(_MSC_VER) + strcpy_s(path, MAX_PATH, dir); +#else + strcpy(path, dir); +#endif + + if (!ISSLASH(path[dirlen - 1])) { +#if defined(_WIN32) || defined(__WIN32__) + path[dirlen] = '\\'; +#else + path[dirlen] = '/'; +#endif + ++dirlen; + } + + // Get and print information about each file in the archive. + n = mz_zip_reader_get_num_files(&zip_archive); + for (i = 0; i < n; ++i) { + if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) { + // Cannot get information about zip archive; + goto out; + } +#if defined(_MSC_VER) + strncpy_s(&path[dirlen], MAX_PATH - dirlen, info.m_filename, + MAX_PATH - dirlen); +#else + strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen); +#endif + if (mkpath(path) < 0) { + // Cannot make a path + goto out; + } + + if ((((info.m_version_made_by >> 8) == 3) || + ((info.m_version_made_by >> 8) == + 19)) // if zip is produced on Unix or macOS (3 and 19 from + // section 4.4.2.2 of zip standard) + && info.m_external_attr & + (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 + // is directory) +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if (info.m_uncomp_size > MAX_PATH || + !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, + MAX_PATH, 0, NULL, 0)) { + goto out; + } + symlink_to[info.m_uncomp_size] = '\0'; + if (symlink(symlink_to, path) != 0) { + goto out; + } +#endif + } else { + if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) { + if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) { + // Cannot extract zip archive to file + goto out; + } + } + +#if defined(_MSC_VER) +#else + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(path, (mode_t)xattr) < 0) { + goto out; + } + } +#endif + } + + if (on_extract) { + if (on_extract(path, arg) < 0) { + goto out; + } + } + } + status = 0; + +out: + // Close the archive, freeing any resources it was using + if (!mz_zip_reader_end(&zip_archive)) { + // Cannot end zip reader + status = -1; + } + + return status; +} diff --git a/ports/wapy/cmod/common/_zipfile/zip.h b/ports/wapy/cmod/common/_zipfile/zip.h new file mode 100644 index 000000000..cd3ab5cd0 --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/zip.h @@ -0,0 +1,322 @@ +/* + * 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 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. + */ + +#pragma once +#ifndef ZIP_H +#define ZIP_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) && \ + !defined(__DEFINED_ssize_t) && !defined(__ssize_t_defined) && \ + !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(_SSIZE_T_DECLARED) + +// 64-bit Windows is the only mainstream platform +// where sizeof(long) != sizeof(void*) +#ifdef _WIN64 +typedef long long ssize_t; /* byte count or error */ +#else +typedef long ssize_t; /* byte count or error */ +#endif + +#define _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED_ +#define __DEFINED_ssize_t +#define __ssize_t_defined +#define _SSIZE_T +#define _SSIZE_T_ +#define _SSIZE_T_DECLARED + +#endif + +#ifndef MAX_PATH +#define MAX_PATH 32767 /* # chars in a path name including NULL */ +#endif + +/** + * @mainpage + * + * Documenation for @ref zip. + */ + +/** + * @addtogroup zip + * @{ + */ + +/** + * Default zip compression level. + */ + +#define ZIP_DEFAULT_COMPRESSION_LEVEL 6 + +/** + * @struct zip_t + * + * This data structure is used throughout the library to represent zip archive - + * forward declaration. + */ +struct zip_t; + +/** + * Opens zip archive with compression level using the given mode. + * + * @param zipname zip archive file name. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ +extern struct zip_t *zip_open(const char *zipname, int level, char mode); + +/** + * Closes the zip archive, releases resources - always finalize. + * + * @param zip zip archive handler. + */ +extern void zip_close(struct zip_t *zip); + +/** + * Determines if the archive has a zip64 end of central directory headers. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern int zip_is64(struct zip_t *zip); + +/** + * Opens an entry by name in the zip archive. + * + * For zip archive opened in 'w' or 'a' mode the function will append + * a new entry. In readonly mode the function tries to locate the entry + * in global dictionary. + * + * @param zip zip archive handler. + * @param entryname an entry name in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_open(struct zip_t *zip, const char *entryname); + +/** + * Opens a new entry by index in the zip archive. + * + * This function is only valid if zip archive was opened in 'r' (readonly) mode. + * + * @param zip zip archive handler. + * @param index index in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_openbyindex(struct zip_t *zip, int index); + +/** + * Closes a zip entry, flushes buffer and releases resources. + * + * @param zip zip archive handler. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_close(struct zip_t *zip); + +/** + * Returns a local name of the current zip entry. + * + * The main difference between user's entry name and local entry name + * is optional relative path. + * Following .ZIP File Format Specification - the path stored MUST not contain + * a drive or device letter, or a leading slash. + * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + * for compatibility with Amiga and UNIX file systems etc. + * + * @param zip: zip archive handler. + * + * @return the pointer to the current zip entry name, or NULL on error. + */ +extern const char *zip_entry_name(struct zip_t *zip); + +/** + * Returns an index of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the index on success, negative number (< 0) on error. + */ +extern int zip_entry_index(struct zip_t *zip); + +/** + * Determines if the current zip entry is a directory entry. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern int zip_entry_isdir(struct zip_t *zip); + +/** + * Returns an uncompressed size of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the uncompressed size in bytes. + */ +extern unsigned long long zip_entry_size(struct zip_t *zip); + +/** + * Returns CRC-32 checksum of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the CRC-32 checksum. + */ +extern unsigned int zip_entry_crc32(struct zip_t *zip); + +/** + * Compresses an input buffer for the current zip entry. + * + * @param zip zip archive handler. + * @param buf input buffer. + * @param bufsize input buffer size (in bytes). + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize); + +/** + * Compresses a file for the current zip entry. + * + * @param zip zip archive handler. + * @param filename input file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_fwrite(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry into output buffer. + * + * The function allocates sufficient memory for a output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note remember to release memory allocated for a output buffer. + * for large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error. + */ +extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize); + +/** + * Extracts the current zip entry into a memory buffer using no memory + * allocation. + * + * @param zip zip archive handler. + * @param buf preallocated output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note ensure supplied output buffer is large enough. + * zip_entry_size function (returns uncompressed size for the current + * entry) can be handy to estimate how big buffer is needed. for large + * entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error (e.g. bufsize is not large enough). + */ +extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, + size_t bufsize); + +/** + * Extracts the current zip entry into output file. + * + * @param zip zip archive handler. + * @param filename output file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_fread(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry using a callback function (on_extract). + * + * @param zip zip archive handler. + * @param on_extract callback function. + * @param arg opaque pointer (optional argument, which you can pass to the + * on_extract callback) + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int +zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *data, size_t size), + void *arg); + +/** + * Returns the number of all entries (files and directories) in the zip archive. + * + * @param zip zip archive handler. + * + * @return the return code - the number of entries on success, negative number + * (< 0) on error. + */ +extern int zip_total_entries(struct zip_t *zip); + +/** + * Creates a new archive and puts files into a single zip archive. + * + * @param zipname zip archive file. + * @param filenames input files. + * @param len: number of input files. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_create(const char *zipname, const char *filenames[], size_t len); + +/** + * Extracts a zip archive file into directory. + * + * If on_extract_entry is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract_entry callback. + * + * @param zipname zip archive file. + * @param dir output directory. + * @param on_extract_entry on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_extract(const char *zipname, const char *dir, + int (*on_extract_entry)(const char *filename, void *arg), + void *arg); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/wapy/cmod/common/embed.pym b/ports/wapy/cmod/common/embed.pym new file mode 100644 index 000000000..73ee16943 --- /dev/null +++ b/ports/wapy/cmod/common/embed.pym @@ -0,0 +1,326 @@ +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// + + +#if __EMSCRIPTEN__ + #define WAPY_VALUE (1) + extern int VMFLAGS_IF; + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#else + #define WAPY_VALUE (0) + int VMFLAGS_IF = 0; + int show_os_loop(int state) {return 0;} + int state_os_loop(int state) {return 0;} + void emscripten_sleep(int t){} +#endif + +#include "emscripten.h" + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include +#include + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +//#include +//extern uintptr_t SDL_embed(uintptr_t *ptr); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +# this one is used on MCU to run asyncio loop from repl loop idle state +# TODO: fix NO_NLR mode +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + +# corepy + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(stderr,"FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +# finalizers + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max ; + cstr[0]=0; + +def run(runstr : const_char_p = "[]"): + # os interfacing, this is not really running except you could have eval() on the other side + # this is actually for passing json / cbor etc ... + + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + } + + + +def CLI(): + VMFLAGS_IF--; + +def STI(): + VMFLAGS_IF++; + +def FLAGS_IF() -> _int_from_uint: + return VMFLAGS_IF + +def WAPY() -> _int_from_uint: + return WAPY_VALUE + +def os_print( data : const_char_p = "{}" ) -> void : + #fprintf( stderr, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s\n" , data ); + +def os_write( data : const_char_p = "{}" ) -> void : + #fprintf( stderr, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s" , data ); + +def os_stderr( data : const_char_p = "" ) -> void : + fprintf( stderr, "embed.os_stderr(%s)\n", data ); + +def log( data : const_char_p = "" ) -> void : + fprintf( stderr, "%s\n", data ); + +def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mod_globals; + +def os_state_loop(state:int = 0)->int: + return mp_obj_new_int( state_os_loop(state) ); + +def os_showloop()->void : + fprintf(stderr,"will show begin/end for os loop\n"); + show_os_loop(1); + +def os_hideloop()->void : + fprintf(stderr,"will hide begin/end for os loop\n"); + show_os_loop(0); + + +def os_hook() ->void: + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass ; + printf("coro task started\n"); + coropass(); + } + } + +// https://www.python.org/dev/peps/pep-0564/ +// https://vstinner.github.io/python37-pep-564-nanoseconds.html +def time_ns() -> int: + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + //unsigned long ul = ts.tv_nsec ; + return mp_obj_new_int_from_ull( ts.tv_nsec ); + +// upy +def time_ms() -> int: + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + //unsigned long ul = ts.tv_nsec * 1000000 ; + return mp_obj_new_int_from_ull( ts.tv_nsec * 1000000); + +def ticks_add(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks + delta ); + +def ticks_diff(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks - delta ); + + +def sleep_ms(ms : int =0) -> void : + //emscripten_sleep_with_yield( ms ); + emscripten_sleep(ms); + +def sleep(s : float =0) -> void : + emscripten_sleep( (int)(s*1000) ); + + +def ticks_period() -> int: + return mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); + + +//def os_read() -> bytes: +// return bytes( repl_line ); + +// TODO: remove after tests + + +def os_read_useless() -> bytes: + // simple read string + + static char buf[256]; + //fputs(p, stdout); + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + //return mp_obj_new_int(0); + buf[0]=0; + fprintf(stderr,"embed.os_read EOF\n" ); + } else { + int l = strlen(buf); + if (buf[l - 1] == '\n') { + if ( (l>1) && (buf[l - 2] == '\r') ) { + buf[l - 2] = 0; + } else { + buf[l - 1] = 0; + } + } else { + l++; + } + fprintf(stderr,"embed.os_read [%s]\n", buf ); + } + return bytes(buf); + # py comment #1 + + +def echosum1(num : int=0) -> int: + return MP_OBJ_NEW_SMALL_INT(num+1); + +# py comment #2 + +def callsome(fnptr : void_p=npe) -> void: + void (*fn)() = fnptr; + (*fn)(); + +def callpy(fn: const_char_p ="") -> void: + fprintf(stderr,"embed.callpy[%s]\n", fn ) + pycore(fn) + + +def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + return ptrvalue; + + +def set_ffpy(fn: void_p) -> void: + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn ; + +def set_ffpy_add(fn: void_p) -> void: + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn ; + + + +def corepy(fn: const_char_p = "") -> _int_from_ptr: + fprintf(stderr,"embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy): + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + + +def somecall(s:str='pouet'): + fprintf(stderr, "FPRINTF[%s]\n", mp_obj_str_get_str((char *)s) ); + print( (char *)s); + +// c comment #3 + diff --git a/ports/wapy/cmod/common/embed/micropython.mk b/ports/wapy/cmod/common/embed/micropython.mk new file mode 100644 index 000000000..b214da743 --- /dev/null +++ b/ports/wapy/cmod/common/embed/micropython.mk @@ -0,0 +1,8 @@ + +EMBED_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(EMBED_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(EMBED_MOD_DIR) diff --git a/ports/wapy/cmod/common/embed/modembed.c b/ports/wapy/cmod/common/embed/modembed.c new file mode 100644 index 000000000..9fe6296a0 --- /dev/null +++ b/ports/wapy/cmod/common/embed/modembed.c @@ -0,0 +1,933 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/common/embed.pym */ +/* + embed.c AUTO-GENERATED by /data/cross/wapy/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// embed exports +void print(mp_obj_t str) { + mp_obj_print(str, PRINT_STR); + mp_obj_print(mp_obj_new_str_via_qstr("\n",1), PRINT_STR); +} + +void +null_pointer_exception(void) { + fprintf(stderr, "null pointer exception in function pointer call\n"); +} + +mp_obj_t +PyBytes_FromString(char *string){ + vstr_t vstr; + vstr_init_len(&vstr, strlen(string)); + strcpy(vstr.buf, string); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} + +const char *nullbytes = ""; +//static int orem_id = 0; + + +// =========== embedded header from .pym ============ + +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// + + +#if __EMSCRIPTEN__ + #define wa_clock_gettime(clockid, timespec) clock_gettime(clockid, timespec) + #define wa_gettimeofday(timeval, tmz) gettimeofday(timeval, tmz) + + #define WAPY_VALUE (1) + extern int VMFLAGS_IF; + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#else + #define WAPY_VALUE (0) + int VMFLAGS_IF = 0; + int show_os_loop(int state) {return 0;} + int state_os_loop(int state) {return 0;} + void emscripten_sleep(int t){} +#endif + +#include "emscripten.h" + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include +#include + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +//#include +//extern uintptr_t SDL_embed(uintptr_t *ptr); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +// this one is used on MCU to run asyncio loop from repl loop idle state + +// TODO: fix NO_NLR mode + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + +// code trace + +// ========================================== + +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +// corepy + +// ========================================== + + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(stderr,"FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +// finalizers + +// ========================================== + + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +// end of embedded header +// ============================================================= + + + +STATIC mp_obj_t // void -> void +embed_set_io_buffer(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + // ('int', '0') => int = 0 + + int ptr; // ('int', 'ptr', '0') + if (argc>0) { ptr = mp_obj_get_int(argv[0]); } else { ptr = 0 ; } + + + int ptr_max; // ('int', 'ptr_max', '0') + if (argc>1) { ptr_max = mp_obj_get_int(argv[1]); } else { ptr_max = 0 ; } + + + // ------- method body (try/finally) ----- + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max; + cstr[0]=0; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_io_buffer_obj, 0, 2, embed_set_io_buffer); + + + +STATIC mp_obj_t // void -> void +embed_run(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def run(runstr : const_char_p = "[]"): + // ('const_char_p', '"[]"') => const char * = "[]" + + const char *runstr; // ('const char *', 'runstr', '"[]"') + if (argc>0) { runstr = mp_obj_str_get_str(argv[0]); } else { runstr = mp_obj_new_str_via_qstr("[]",2); } + + + // ------- method body (try/finally) ----- + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_run_obj, 0, 1, embed_run); + + + +STATIC mp_obj_t // void -> void +embed_CLI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF--; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_CLI_obj, 0, 0, embed_CLI); + + + +STATIC mp_obj_t // void -> void +embed_STI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF++; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_STI_obj, 0, 0, embed_STI); + + + +STATIC mp_obj_t // _int_from_uint -> _int_from_uint +embed_FLAGS_IF(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_uint slot + uint __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (uint)VMFLAGS_IF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_uint(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_FLAGS_IF_obj, 0, 0, embed_FLAGS_IF); + + + +STATIC mp_obj_t // _int_from_uint -> _int_from_uint +embed_WAPY(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_uint slot + uint __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (uint)WAPY_VALUE; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_uint(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_WAPY_obj, 0, 0, embed_WAPY); + + + +STATIC mp_obj_t // void -> void +embed_os_print(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_print( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s\n" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_print_obj, 0, 1, embed_os_print); + + + +STATIC mp_obj_t // void -> void +embed_os_write(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_write( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_write_obj, 0, 1, embed_os_write); + + + +STATIC mp_obj_t // void -> void +embed_os_stderr(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_stderr( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf( stderr, "embed.os_stderr(%s)\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_stderr_obj, 0, 1, embed_os_stderr); + + + +STATIC mp_obj_t // void -> void +embed_log(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def log( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf( stderr, "%s\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_log_obj, 0, 1, embed_log); + + + +STATIC mp_obj_t // dict -> dict +embed_builtins_vars(size_t argc, const mp_obj_t *argv) { +// opt: no finally dict slot + mp_obj_dict_t * __creturn__; + // def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + // ('mp_obj_t', 'None') => mp_obj_t = None + + mp_obj_t module_obj; // ('mp_obj_t', 'module_obj', 'NULL') + if (argc>0) { module_obj = (mp_obj_t)argv[0]; } + else { module_obj = NULL ; } + + + // ------- method body (try/finally) ----- + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + { __creturn__ = (mp_obj_dict_t *)mod_globals; goto lreturn__; }; +lreturn__: return (mp_obj_t)__creturn__ ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_builtins_vars_obj, 0, 1, embed_builtins_vars); + + + +STATIC mp_obj_t // int -> int +embed_os_state_loop(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def os_state_loop(state:int = 0)->int: + // ('int', '0') => int = 0 + + int state; // ('int', 'state', '0') + if (argc>0) { state = mp_obj_get_int(argv[0]); } else { state = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int( state_os_loop(state) ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_state_loop_obj, 0, 1, embed_os_state_loop); + + + +STATIC mp_obj_t // void -> void +embed_os_showloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr,"will show begin/end for os loop\n"); + show_os_loop(1); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_showloop_obj, 0, 0, embed_os_showloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hideloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr,"will hide begin/end for os loop\n"); + show_os_loop(0); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hideloop_obj, 0, 0, embed_os_hideloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hook(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass; + printf("coro task started\n"); + coropass(); + }; + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hook_obj, 0, 0, embed_os_hook); + + + +STATIC mp_obj_t // int -> int +embed_time_ns(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ns_obj, 0, 0, embed_time_ns); + + + +STATIC mp_obj_t // int -> int +embed_time_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ms_obj, 0, 0, embed_time_ms); + + + +STATIC mp_obj_t // int -> int +embed_ticks_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_add(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks + delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_add_obj, 0, 2, embed_ticks_add); + + + +STATIC mp_obj_t // int -> int +embed_ticks_diff(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_diff(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks - delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_diff_obj, 0, 2, embed_ticks_diff); + + + +STATIC mp_obj_t // void -> void +embed_sleep_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def sleep_ms(ms : int =0) -> void : + // ('int', '0') => int = 0 + + int ms; // ('int', 'ms', '0') + if (argc>0) { ms = mp_obj_get_int(argv[0]); } else { ms = 0 ; } + + + // ------- method body (try/finally) ----- + emscripten_sleep(ms); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_sleep_ms_obj, 0, 1, embed_sleep_ms); + + + +STATIC mp_obj_t // void -> void +embed_sleep(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def sleep(s : float =0) -> void : + // ('float', '0') => float = 0 + + double s; // ('double', 's', '0') + if (argc>0) { s = mp_obj_get_double(argv[0]); } else { s = 0 ; } + + + // ------- method body (try/finally) ----- + emscripten_sleep( (int)(s*1000) ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_sleep_obj, 0, 1, embed_sleep); + + + +STATIC mp_obj_t // int -> int +embed_ticks_period(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_period_obj, 0, 0, embed_ticks_period); + + + +STATIC mp_obj_t // bytes -> bytes +embed_os_read_useless(size_t argc, const mp_obj_t *argv) { +// opt: no finally bytes slot + char * __creturn__ = (char *)nullbytes; + + // ------- method body (try/finally) ----- + static char buf[256]; + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + buf[0]=0; + fprintf(stderr,"embed.os_read EOF\n" ); + } else { + int l = strlen(buf); + if (buf[l - 1] == '\n') { + if ( (l>1) && (buf[l - 2] == '\r') ) { + buf[l - 2] = 0; + } else { + buf[l - 1] = 0; + }; + } else { + l++; + }; + fprintf(stderr,"embed.os_read [%s]\n", buf ); + }; + { __creturn__ = (char *)bytes(buf); goto lreturn__; }; +lreturn__: return PyBytes_FromString(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_read_useless_obj, 0, 0, embed_os_read_useless); + + + +STATIC mp_obj_t // int -> int +embed_echosum1(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def echosum1(num : int=0) -> int: + // ('int', '0') => int = 0 + + int num; // ('int', 'num', '0') + if (argc>0) { num = mp_obj_get_int(argv[0]); } else { num = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)MP_OBJ_NEW_SMALL_INT(num+1); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_echosum1_obj, 0, 1, embed_echosum1); + + + +STATIC mp_obj_t // void -> void +embed_callsome(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callsome(fnptr : void_p=npe) -> void: + // ('void_p', 'npe') => void * = NULL + + void * fnptr; // ('void *', 'fnptr', 'NULL') + if (argc>0) { fnptr = (void *)argv[0]; } + else { fnptr = NULL ; } + + + // ------- method body (try/finally) ----- + void (*fn)() = fnptr; + (*fn)(); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callsome_obj, 0, 1, embed_callsome); + + + +STATIC mp_obj_t // void -> void +embed_callpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callpy(fn: const_char_p ="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.callpy[%s]\n", fn ); + pycore(fn); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callpy_obj, 0, 1, embed_callpy); + + + +STATIC mp_obj_t // _int_from_unsigned_long -> _int_from_unsigned_long +embed_hash_djb2(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_unsigned_long slot + unsigned_long __creturn__ = 0; + // def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + // ('const_char_p', '""') => const char * = "" + + const char *cstr; // ('const char *', 'cstr', '""') + if (argc>0) { cstr = mp_obj_str_get_str(argv[0]); } else { cstr = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + unsigned long hash = 5381; + int c; + while ((c = *cstr++)) { + hash = ((hash << 5) + hash) + c; + } + { __creturn__ = (unsigned_long)hash % 0xFFFFFFFF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_unsigned_long(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_hash_djb2_obj, 0, 1, embed_hash_djb2); + + + +STATIC mp_obj_t // int -> int +embed_show_trace(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + trace_on = 1; + fprintf(stderr,"TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line); + { __creturn__ = (long)trace_prev_line-1; goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_show_trace_obj, 0, 0, embed_show_trace); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_address_of(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + // ('void_p', 'NULL') => void * = NULL + + void * ptr; // ('void *', 'ptr', 'NULL') + if (argc>0) { ptr = (void *)argv[0]; } + else { ptr = NULL ; } + + + // ------- method body (try/finally) ----- + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + { __creturn__ = (uintptr_t)ptrvalue; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_address_of_obj, 0, 1, embed_address_of); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_obj, 0, 1, embed_set_ffpy); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy_add(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_add_obj, 0, 1, embed_set_ffpy_add); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_corepy(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def corepy(fn: const_char_p = "") -> _int_from_ptr: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + } +return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_corepy_obj, 0, 1, embed_corepy); + + + +STATIC mp_obj_t // void -> void +embed_somecall(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def somecall(s:str='pouet'): + // ('str', "'pouet'") => str = 'pouet' + + const char *s; // ('str', 's', "'pouet'") + if (argc>0) { s = mp_obj_str_get_str(argv[0]); } else { s = mp_obj_new_str_via_qstr("pouet",5); } + + + // ------- method body (try/finally) ----- + fprintf(stderr, "FPRINTF[%s]\n", mp_obj_str_get_str((char *)s) ); + print( (char *)s); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_somecall_obj, 0, 1, embed_somecall); + + + +// global module dict : + + + +STATIC const mp_map_elem_t embed_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_embed) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + {MP_OBJ_NEW_QSTR(MP_QSTR_on_del), MP_ROM_PTR(&mp_type_on_del) }, + + +// Classes : + + +// __main__ + {MP_OBJ_NEW_QSTR(MP_QSTR_set_io_buffer), (mp_obj_t)&embed_set_io_buffer_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_run), (mp_obj_t)&embed_run_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_CLI), (mp_obj_t)&embed_CLI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_STI), (mp_obj_t)&embed_STI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_FLAGS_IF), (mp_obj_t)&embed_FLAGS_IF_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_WAPY), (mp_obj_t)&embed_WAPY_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_print), (mp_obj_t)&embed_os_print_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_write), (mp_obj_t)&embed_os_write_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_stderr), (mp_obj_t)&embed_os_stderr_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&embed_log_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_builtins_vars), (mp_obj_t)&embed_builtins_vars_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_state_loop), (mp_obj_t)&embed_os_state_loop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_showloop), (mp_obj_t)&embed_os_showloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hideloop), (mp_obj_t)&embed_os_hideloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hook), (mp_obj_t)&embed_os_hook_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ns), (mp_obj_t)&embed_time_ns_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ms), (mp_obj_t)&embed_time_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_add), (mp_obj_t)&embed_ticks_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_diff), (mp_obj_t)&embed_ticks_diff_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_sleep_ms), (mp_obj_t)&embed_sleep_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_sleep), (mp_obj_t)&embed_sleep_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_period), (mp_obj_t)&embed_ticks_period_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_read_useless), (mp_obj_t)&embed_os_read_useless_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_echosum1), (mp_obj_t)&embed_echosum1_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callsome), (mp_obj_t)&embed_callsome_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callpy), (mp_obj_t)&embed_callpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_hash_djb2), (mp_obj_t)&embed_hash_djb2_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_show_trace), (mp_obj_t)&embed_show_trace_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_address_of), (mp_obj_t)&embed_address_of_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy), (mp_obj_t)&embed_set_ffpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy_add), (mp_obj_t)&embed_set_ffpy_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_corepy), (mp_obj_t)&embed_corepy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_somecall), (mp_obj_t)&embed_somecall_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(embed_dict, embed_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module_embed = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&embed_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR_embed, module_embed, MODULE_EMBED_ENABLED); + diff --git a/ports/wapy/cmod/common/example/example.c b/ports/wapy/cmod/common/example/example.c new file mode 100644 index 000000000..562e75069 --- /dev/null +++ b/ports/wapy/cmod/common/example/example.c @@ -0,0 +1,40 @@ + + +// Include required definitions first. +#include "py/obj.h" +#include "py/runtime.h" +#include "py/builtin.h" + +// This is the function which will be called from Python as example.add_ints(a, b). +STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { + // Extract the ints from the micropython input objects + int a = mp_obj_get_int(a_obj); + int b = mp_obj_get_int(b_obj); + + // Calculate the addition and convert to MicroPython object. + return mp_obj_new_int(a + b); +} +// Define a Python reference to the function above +STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); + +// Define all properties of the example module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t example_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) }, + { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); + +// Define module object. +const mp_obj_module_t example_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&example_module_globals, +}; + +// Register the module to make it available in Python +MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED); + + diff --git a/ports/wapy/cmod/common/example/micropython.mk b/ports/wapy/cmod/common/example/micropython.mk new file mode 100644 index 000000000..6ab6a4d54 --- /dev/null +++ b/ports/wapy/cmod/common/example/micropython.mk @@ -0,0 +1,9 @@ +EXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c + +# We can add our module folder to include paths if needed +# This is not actually needed in this example. +CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) + diff --git a/ports/wapy/cmod/wasi/embed.pym b/ports/wapy/cmod/wasi/embed.pym new file mode 100644 index 000000000..25aa00e87 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed.pym @@ -0,0 +1,337 @@ +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// +// you *must* take care of return type yourself, glue code can only return None for you. + +#if __LVGL__ +#error LVGL +static int show_os_loop(int state) { + return 0; +} +#define state_os_loop show_os_loop +#else + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#endif + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include + +//#include +//#include + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +#define WAPY_VALUE (1) +extern int VMFLAGS_IF; +extern int show_os_loop(int state); +extern int state_os_loop(int state); +//extern unsigned long long TV_NS ; + + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + + +# code trace +# ========================================== +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +# corepy +# ========================================== + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(stderr,"FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +# finalizers +# ========================================== + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +//========================================================================== + + + +def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max ; + cstr[0]=0; + +def run(runstr : const_char_p = "[]"): + # os interfacing, this is not really running except you could have eval() on the other side + # this is actually for passing json / cbor etc ... + + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + } + + + + +def CLI(): + VMFLAGS_IF--; + +def STI(): + VMFLAGS_IF++; + +def FLAGS_IF() -> int: + return mp_obj_new_int_from_uint(VMFLAGS_IF); + +def WAPY() -> int: +#if defined(__EMSCRIPTEN__) || defined(__WASM__) + return mp_obj_new_int_from_uint(1); +#else + return mp_obj_new_int_from_uint(0); +#endif + + +def os_print( data : const_char_p = "{}" ) -> void : + #fprintf( stderr, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s\n" , data ); + +def os_write( data : const_char_p = "{}" ) -> void : + #fprintf( stderr, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s" , data ); + +def os_stderr( data : const_char_p = "" ) -> void : + fprintf( stderr, "embed.os_stderr(%s)\n", data ); + +def log( data : const_char_p = "" ) -> void : + fprintf( stderr, "%s\n", data ); + + +def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mod_globals; + + +def os_compile(source_file : const_char_p="", mpy_file : const_char_p="") -> void: + vstr_t vstr; + + if (argc == 2 && argv[1] != mp_const_none) { + //done by glue code + } else { + vstr_init(&vstr, strlen(source_file) + 5); // +5 for NUL and .mpy + vstr_add_str(&vstr, source_file); + if (vstr.len > 3 && memcmp(&vstr.buf[vstr.len - 3], ".py", 3) == 0) { + // remove .py extension to replace with .mpy + vstr_cut_tail_bytes(&vstr, 3); + } + vstr_add_str(&vstr, ".mpy"); + mpy_file = vstr_null_terminated_str(&vstr); + } + #if MICROPY_PERSISTENT_CODE_SAVE + mp_lexer_t *lex = mp_lexer_new_from_file(source_file); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, + qstr_from_str(source_file), + false); + + // mp_raw_code_save_file(rc, mpy_file); + #else + fprintf(stderr,"os_compile: MICROPY_PERSISTENT_CODE_SAVE not enabled\n"); + #endif + +def os_state_loop(state:int = 0)->int: + return mp_obj_new_int( state_os_loop(state) ); + +def os_showloop()->void : + fprintf(stderr,"will show begin/end for os loop\n"); + show_os_loop(1); + +def os_hideloop()->void : + fprintf(stderr,"will hide begin/end for os loop\n"); + show_os_loop(0); + + +def os_hook() ->void: + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass ; + printf("coro task started\n"); + coropass(); + } + } + + +// https://www.python.org/dev/peps/pep-0564/ +// https://vstinner.github.io/python37-pep-564-nanoseconds.html +def time_ns() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec ; + return mp_obj_new_int_from_ull(ul) + +// upy +def time_ms() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000 ; + return mp_obj_new_int_from_ull(ul); + +def ticks_add(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks + delta ); + +def ticks_diff(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks - delta ); + + +def ticks_period() -> int: + return mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); + + +def callpy(fn: const_char_p ="") -> void: + fprintf(stderr,"embed.callpy[%s]\n", fn ) + pycore(fn) + +def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + unsigned long hash = 5381 + int c + while ((c = *cstr++)): + hash = ((hash << 5) + hash) + c + return hash % 0xFFFFFFFF + +def show_trace() -> int: + trace_on = 1 + fprintf(stderr,"TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line) + return trace_prev_line-1 + + +def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + return ptrvalue; + +def set_ffpy(fn: void_p) -> void: + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn ; + +def set_ffpy_add(fn: void_p) -> void: + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn ; + + +def corepy(fn: const_char_p = "") -> _int_from_ptr: + fprintf(stderr,"embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy): + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + + diff --git a/ports/wapy/cmod/wasi/embed/micropython.mk b/ports/wapy/cmod/wasi/embed/micropython.mk new file mode 100644 index 000000000..b214da743 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed/micropython.mk @@ -0,0 +1,8 @@ + +EMBED_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(EMBED_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(EMBED_MOD_DIR) diff --git a/ports/wapy/cmod/wasi/embed/modembed.c b/ports/wapy/cmod/wasi/embed/modembed.c new file mode 100644 index 000000000..0311aa1a6 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed/modembed.c @@ -0,0 +1,840 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/wasi/embed.pym */ +/* + embed.c AUTO-GENERATED by /data/cross/wapy/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// embed exports +void print(mp_obj_t str) { + mp_obj_print(str, PRINT_STR); + mp_obj_print(mp_obj_new_str_via_qstr("\n",1), PRINT_STR); +} + +void +null_pointer_exception(void) { + fprintf(stderr, "null pointer exception in function pointer call\n"); +} + +mp_obj_t +PyBytes_FromString(char *string){ + vstr_t vstr; + vstr_init_len(&vstr, strlen(string)); + strcpy(vstr.buf, string); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} + +const char *nullbytes = ""; +//static int orem_id = 0; + + +// =========== embedded header from .pym ============ + +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// +// you *must* take care of return type yourself, glue code can only return None for you. + +#if __LVGL__ +#error LVGL +static int show_os_loop(int state) { + return 0; +} +#define state_os_loop show_os_loop +#else + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#endif + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include + +//#include +//#include + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +#define WAPY_VALUE (1) +extern int VMFLAGS_IF; +extern int show_os_loop(int state); +extern int state_os_loop(int state); +//extern unsigned long long TV_NS ; + + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + + +// code trace + +// ========================================== + +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +// corepy + +// ========================================== + + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(stderr,"FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +// finalizers + +// ========================================== + + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +//========================================================================== + + + +// end of embedded header +// ============================================================= + + + +STATIC mp_obj_t // void -> void +embed_set_io_buffer(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + // ('int', '0') => int = 0 + + int ptr; // ('int', 'ptr', '0') + if (argc>0) { ptr = mp_obj_get_int(argv[0]); } else { ptr = 0 ; } + + + int ptr_max; // ('int', 'ptr_max', '0') + if (argc>1) { ptr_max = mp_obj_get_int(argv[1]); } else { ptr_max = 0 ; } + + + // ------- method body (try/finally) ----- + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max; + cstr[0]=0; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_io_buffer_obj, 0, 2, embed_set_io_buffer); + + + +STATIC mp_obj_t // void -> void +embed_run(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def run(runstr : const_char_p = "[]"): + // ('const_char_p', '"[]"') => const char * = "[]" + + const char *runstr; // ('const char *', 'runstr', '"[]"') + if (argc>0) { runstr = mp_obj_str_get_str(argv[0]); } else { runstr = mp_obj_new_str_via_qstr("[]",2); } + + + // ------- method body (try/finally) ----- + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_run_obj, 0, 1, embed_run); + + + +STATIC mp_obj_t // void -> void +embed_CLI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF--; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_CLI_obj, 0, 0, embed_CLI); + + + +STATIC mp_obj_t // void -> void +embed_STI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF++; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_STI_obj, 0, 0, embed_STI); + + + +STATIC mp_obj_t // int -> int +embed_FLAGS_IF(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint(VMFLAGS_IF); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_FLAGS_IF_obj, 0, 0, embed_FLAGS_IF); + + + +STATIC mp_obj_t // int -> int +embed_WAPY(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint(1); goto lreturn__; }; + { __creturn__ = (long)mp_obj_new_int_from_uint(0); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_WAPY_obj, 0, 0, embed_WAPY); + + + +STATIC mp_obj_t // void -> void +embed_os_print(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_print( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s\n" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_print_obj, 0, 1, embed_os_print); + + + +STATIC mp_obj_t // void -> void +embed_os_write(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_write( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_write_obj, 0, 1, embed_os_write); + + + +STATIC mp_obj_t // void -> void +embed_os_stderr(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_stderr( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf( stderr, "embed.os_stderr(%s)\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_stderr_obj, 0, 1, embed_os_stderr); + + + +STATIC mp_obj_t // void -> void +embed_log(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def log( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf( stderr, "%s\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_log_obj, 0, 1, embed_log); + + + +STATIC mp_obj_t // dict -> dict +embed_builtins_vars(size_t argc, const mp_obj_t *argv) { +// opt: no finally dict slot + mp_obj_dict_t * __creturn__; + // def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + // ('mp_obj_t', 'None') => mp_obj_t = None + + mp_obj_t module_obj; // ('mp_obj_t', 'module_obj', 'NULL') + if (argc>0) { module_obj = (mp_obj_t)argv[0]; } + else { module_obj = NULL ; } + + + // ------- method body (try/finally) ----- + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + { __creturn__ = (mp_obj_dict_t *)mod_globals; goto lreturn__; }; +lreturn__: return (mp_obj_t)__creturn__ ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_builtins_vars_obj, 0, 1, embed_builtins_vars); + + + +STATIC mp_obj_t // void -> void +embed_os_compile(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_compile(source_file : const_char_p="", mpy_file : const_char_p="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *source_file; // ('const char *', 'source_file', '""') + if (argc>0) { source_file = mp_obj_str_get_str(argv[0]); } else { source_file = mp_obj_new_str_via_qstr("",0); } + + + const char *mpy_file; // ('const char *', 'mpy_file', '""') + if (argc>1) { mpy_file = mp_obj_str_get_str(argv[1]); } else { mpy_file = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + vstr_t vstr; + if (argc == 2 && argv[1] != mp_const_none) { + } else { + vstr_init(&vstr, strlen(source_file) + 5); // +5 for NUL and .mpy; + vstr_add_str(&vstr, source_file); + if (vstr.len > 3 && memcmp(&vstr.buf[vstr.len - 3], ".py", 3) == 0) { + vstr_cut_tail_bytes(&vstr, 3); + }; + vstr_add_str(&vstr, ".mpy"); + mpy_file = vstr_null_terminated_str(&vstr); + }; + mp_lexer_t *lex = mp_lexer_new_from_file(source_file); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, + qstr_from_str(source_file), + false); + fprintf(stderr,"os_compile: MICROPY_PERSISTENT_CODE_SAVE not enabled\n"); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_compile_obj, 0, 2, embed_os_compile); + + + +STATIC mp_obj_t // int -> int +embed_os_state_loop(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def os_state_loop(state:int = 0)->int: + // ('int', '0') => int = 0 + + int state; // ('int', 'state', '0') + if (argc>0) { state = mp_obj_get_int(argv[0]); } else { state = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int( state_os_loop(state) ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_state_loop_obj, 0, 1, embed_os_state_loop); + + + +STATIC mp_obj_t // void -> void +embed_os_showloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr,"will show begin/end for os loop\n"); + show_os_loop(1); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_showloop_obj, 0, 0, embed_os_showloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hideloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr,"will hide begin/end for os loop\n"); + show_os_loop(0); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hideloop_obj, 0, 0, embed_os_hideloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hook(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass; + printf("coro task started\n"); + coropass(); + }; + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hook_obj, 0, 0, embed_os_hook); + + + +STATIC mp_obj_t // int -> int +embed_time_ns(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ns_obj, 0, 0, embed_time_ns); + + + +STATIC mp_obj_t // int -> int +embed_time_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ms_obj, 0, 0, embed_time_ms); + + + +STATIC mp_obj_t // int -> int +embed_ticks_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_add(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks + delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_add_obj, 0, 2, embed_ticks_add); + + + +STATIC mp_obj_t // int -> int +embed_ticks_diff(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_diff(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks - delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_diff_obj, 0, 2, embed_ticks_diff); + + + +STATIC mp_obj_t // int -> int +embed_ticks_period(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_period_obj, 0, 0, embed_ticks_period); + + + +STATIC mp_obj_t // void -> void +embed_callpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callpy(fn: const_char_p ="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.callpy[%s]\n", fn ); + pycore(fn); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callpy_obj, 0, 1, embed_callpy); + + + +STATIC mp_obj_t // _int_from_unsigned_long -> _int_from_unsigned_long +embed_hash_djb2(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_unsigned_long slot + unsigned_long __creturn__ = 0; + // def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + // ('const_char_p', '""') => const char * = "" + + const char *cstr; // ('const char *', 'cstr', '""') + if (argc>0) { cstr = mp_obj_str_get_str(argv[0]); } else { cstr = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + unsigned long hash = 5381; + int c; + while ((c = *cstr++)) { + hash = ((hash << 5) + hash) + c; + } + { __creturn__ = (unsigned_long)hash % 0xFFFFFFFF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_unsigned_long(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_hash_djb2_obj, 0, 1, embed_hash_djb2); + + + +STATIC mp_obj_t // int -> int +embed_show_trace(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + trace_on = 1; + fprintf(stderr,"TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line); + { __creturn__ = (long)trace_prev_line-1; goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_show_trace_obj, 0, 0, embed_show_trace); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_address_of(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + // ('void_p', 'NULL') => void * = NULL + + void * ptr; // ('void *', 'ptr', 'NULL') + if (argc>0) { ptr = (void *)argv[0]; } + else { ptr = NULL ; } + + + // ------- method body (try/finally) ----- + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + { __creturn__ = (uintptr_t)ptrvalue; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_address_of_obj, 0, 1, embed_address_of); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_obj, 0, 1, embed_set_ffpy); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy_add(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_add_obj, 0, 1, embed_set_ffpy_add); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_corepy(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def corepy(fn: const_char_p = "") -> _int_from_ptr: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr,"embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + } +return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_corepy_obj, 0, 1, embed_corepy); + + + +// global module dict : + + + +STATIC const mp_map_elem_t embed_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_embed) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + {MP_OBJ_NEW_QSTR(MP_QSTR_on_del), MP_ROM_PTR(&mp_type_on_del) }, + + +// Classes : + + +// __main__ + {MP_OBJ_NEW_QSTR(MP_QSTR_set_io_buffer), (mp_obj_t)&embed_set_io_buffer_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_run), (mp_obj_t)&embed_run_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_CLI), (mp_obj_t)&embed_CLI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_STI), (mp_obj_t)&embed_STI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_FLAGS_IF), (mp_obj_t)&embed_FLAGS_IF_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_WAPY), (mp_obj_t)&embed_WAPY_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_print), (mp_obj_t)&embed_os_print_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_write), (mp_obj_t)&embed_os_write_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_stderr), (mp_obj_t)&embed_os_stderr_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&embed_log_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_builtins_vars), (mp_obj_t)&embed_builtins_vars_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_compile), (mp_obj_t)&embed_os_compile_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_state_loop), (mp_obj_t)&embed_os_state_loop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_showloop), (mp_obj_t)&embed_os_showloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hideloop), (mp_obj_t)&embed_os_hideloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hook), (mp_obj_t)&embed_os_hook_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ns), (mp_obj_t)&embed_time_ns_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ms), (mp_obj_t)&embed_time_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_add), (mp_obj_t)&embed_ticks_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_diff), (mp_obj_t)&embed_ticks_diff_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_period), (mp_obj_t)&embed_ticks_period_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callpy), (mp_obj_t)&embed_callpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_hash_djb2), (mp_obj_t)&embed_hash_djb2_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_show_trace), (mp_obj_t)&embed_show_trace_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_address_of), (mp_obj_t)&embed_address_of_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy), (mp_obj_t)&embed_set_ffpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy_add), (mp_obj_t)&embed_set_ffpy_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_corepy), (mp_obj_t)&embed_corepy_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(embed_dict, embed_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module_embed = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&embed_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR_embed, module_embed, MODULE_EMBED_ENABLED); + diff --git a/ports/wapy/cmod/wasi/example b/ports/wapy/cmod/wasi/example new file mode 120000 index 000000000..de77548b3 --- /dev/null +++ b/ports/wapy/cmod/wasi/example @@ -0,0 +1 @@ +../common/example \ No newline at end of file diff --git a/ports/wapy/core/fdfile.h b/ports/wapy/core/fdfile.h new file mode 100644 index 000000000..69a9b6be4 --- /dev/null +++ b/ports/wapy/core/fdfile.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * 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_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/wapy/core/file.wapy.c b/ports/wapy/core/file.wapy.c new file mode 100644 index 000000000..02988a5fa --- /dev/null +++ b/ports/wapy/core/file.wapy.c @@ -0,0 +1,323 @@ +/* + * 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 +#include +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#if MICROPY_PY_IO && !MICROPY_VFS + +#ifdef _WIN32 +#define fsync _commit +#endif + +#ifdef MICROPY_CPYTHON_COMPAT +STATIC int check_fd_is_open(const mp_obj_fdfile_t *o) { + if (o->fd < 0) { + mp_raise_ValueError_o("I/O operation on closed file"); + return 1; + } + return 0; +} +#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_str(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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + mp_int_t r = read(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + 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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + #if MICROPY_PY_OS_DUPTERM + if (o->fd <= STDERR_FILENO) { + mp_hal_stdout_tx_strn(buf, size); + return size; + } + #endif + MP_THREAD_GIL_EXIT(); + mp_int_t r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + 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; + mp_raise_o(obj); + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + } + 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); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + switch (request) { + 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; + } + s->offset = off; + return 0; + } + case MP_STREAM_FLUSH: + MP_THREAD_GIL_EXIT(); + int ret = fsync(o->fd); + MP_THREAD_GIL_ENTER(); + if (ret == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + 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; + } +} + +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); + if (check_fd_is_open(self)) { + return MP_OBJ_NULL; + } + 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_NONE} }, + { 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_NONE} }, + { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, +}; +#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) + +#if __EMSCRIPTEN__ +extern int wasm_file_open(const char *url); +#endif + +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); +#if __EMSCRIPTEN__ +int can_online = 0; +#endif + + 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 = 0; + MP_THREAD_GIL_EXIT(); +#if __EMSCRIPTEN__ + if (can_online) + fd = wasm_file_open( fname ); +#endif + if (!fd) + fd=open(fname, mode_x | mode_rw, 0644); + MP_THREAD_GIL_ENTER(); + if (fd == -1) { + return mp_raise_OSError_o(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, size_t n_kw, const mp_obj_t *args) { + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all_kw_array(n_args, n_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 = { + .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 = { + .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/wapy/core/ringbuf_b.c b/ports/wapy/core/ringbuf_b.c new file mode 100644 index 000000000..152cd634b --- /dev/null +++ b/ports/wapy/core/ringbuf_b.c @@ -0,0 +1,110 @@ + + +/****************************************************************************** + + Copyright (c) 2018 EmbedJournal + + 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. + + Author : Siddharth Chandrasekaran + Email : siddharth@embedjournal.com + Date : Sun Aug 5 09:42:31 IST 2018 + +******************************************************************************/ + +#include "ringbuf_b.h" + +int rbb_append(rbb_t *c, uint8_t data) +{ + int next; + + next = c->head + 1; // next is where head will point to after this write. + if (next >= c->maxlen) + next = 0; + + // if the head + 1 == tail, circular buffer is full. Notice that one slot + // is always left empty to differentiate empty vs full condition + if (next == c->tail) + return -1; + + c->buffer[c->head] = data; // Load data and then move + c->head = next; // head to next data offset. + return 0; // return success to indicate successful push. +} + +int rbb_is_empty(rbb_t *c) { + return (c->head == c->tail); // if the head == tail, we don't have any data +} + + +int rbb_pop(rbb_t *c, uint8_t *data) +{ + int next; + + if (c->head == c->tail) // if the head == tail, we don't have any data + return 0; + + next = c->tail + 1; // next is where tail will point to after this read. + if(next >= c->maxlen) + next = 0; + + *data = c->buffer[c->tail]; // Read data and then move + c->tail = next; // tail to next offset. + return 1; // return success to indicate successful pop. +} + +int rbb_free_space(rbb_t *c) +{ + int freeSpace; + freeSpace = c->tail - c->head; + if (freeSpace <= 0) + freeSpace += c->maxlen; + return freeSpace - 1; // -1 to account for the always-empty slot. +} + +#ifdef C_UTILS_TESTING +/* To test this module, + * $ gcc -Wall -DC_UTILS_TESTING rinbguf_b.c + * $ ./a.out +*/ + +RBB_T(my_circ_buf, 32); + +#include + +int main() +{ + uint8_t out_data=0, in_data = 0x55; + + if (rbb_append(&my_circ_buf, in_data)) { + printf("Out of space in CB\n"); + return -1; + } + + if (rbb_pop(&my_circ_buf, &out_data)) { + printf("CB is empty\n"); + return -1; + } + + printf("Push: 0x%x\n", in_data); + printf("Pop: 0x%x\n", out_data); + return 0; +} + +#endif diff --git a/ports/wapy/core/ringbuf_b.h b/ports/wapy/core/ringbuf_b.h new file mode 100644 index 000000000..bb14ea73f --- /dev/null +++ b/ports/wapy/core/ringbuf_b.h @@ -0,0 +1,75 @@ +/****************************************************************************** + + Copyright (c) 2018 EmbedJournal + + 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. + + Author : Siddharth Chandrasekaran + Email : siddharth@embedjournal.com + Date : Sun Aug 5 09:42:31 IST 2018 + +******************************************************************************/ +#ifndef RINGBUF_B_H +#define RINGBUF_B_H + +#include + +typedef struct { + uint8_t * const buffer; + int head; + int tail; + const int maxlen; +} rbb_t; + +#define RBB_T(x,y) \ + uint8_t x##_data_space[y+1]; \ + rbb_t x = { \ + .buffer = x##_data_space, \ + .head = 0, \ + .tail = 0, \ + .maxlen = y+1 \ + } + +/* + * Method: circ_buf_pop + * Returns: + * 0 - Success + * -1 - Empty + */ +int rbb_pop(rbb_t *c, uint8_t *data); + +/* + * Method: circ_buf_push + * Returns: + * 0 - Success + * -1 - Out of space + */ +int rbb_append(rbb_t *c, uint8_t data); + +/* + * Method: rbb_free_space + * Returns: number of bytes available + */ +int rbb_free_space(rbb_t *c); + + +int rbb_is_empty(rbb_t *c); + +#endif /* RINGBUF_B_H */ + diff --git a/ports/wapy/core/ringbuf_o.c b/ports/wapy/core/ringbuf_o.c new file mode 100644 index 000000000..8e162382d --- /dev/null +++ b/ports/wapy/core/ringbuf_o.c @@ -0,0 +1,221 @@ +/* + ringbuf_o.c - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ +#include +#include +#include +#include +#include + +#include "ringbuf_o.h" + + + +typedef struct rbo_t { + // Invariant: end and start is always in bounds + unsigned char *buf; + unsigned int len, size, start, end, elements; + + // Private: + int (*next_end_index) (rbo_t*); + int (*incr_end_index) (rbo_t*); + + int (*incr_start_index) (rbo_t*); + + //public: + // Returns true if full + bool (*isFull) (rbo_t*); + // Returns true if empty + bool (*isEmpty) (rbo_t*); + // Returns number of elemnts in buffer + unsigned int (*numElements)(rbo_t*); + // Add Event, Returns index where added in buffer, -1 on full buffer + int (*add) (rbo_t*, const void*); + // Returns pointer to nth element, NULL when nth element is empty + void *(*peek) (rbo_t*, unsigned int); + // Removes element and copies it to location pointed to by void * + // Returns pointer passed in, NULL on empty buffer + void *(*pull) (rbo_t*, void *); + +} rbo_t; + +/////// Constructor ////////// +rbo_t* +rbo_t_new(int size, int len) { + rbo_t *self = (rbo_t *)malloc(sizeof(rbo_t)); + if (!self) return NULL; + memset(self, 0, sizeof(rbo_t)); + if (rbo_init(self, size, len) < 0) + { + free(self); + return NULL; + } + return self; +} + +int +rbo_init(rbo_t *self, int size, int len) { + self->buf = (unsigned char *)malloc(size*len); + if (!self->buf) return -1; + memset(self->buf, 0, size*len); + + self->size = size; + self->len = len; + self->start = 0; + self->end = 0; + self->elements = 0; + + self->next_end_index = &rbo_next_end_index; + self->incr_end_index = &rbo_incr_end; + self->incr_start_index = &rbo_incr_start; + self->isFull = &rbo_is_full; + self->isEmpty = &rbo_is_empty; + self->add = &rbo_append; + self->numElements = &rbo_count; + self->peek = &rbo_peek; + self->pull = &rbo_pop; + return 0; +} +/////// Deconstructor ////////// +int +rbo_delete(rbo_t *self) { + free(self->buf); + free(self); + return 0; +} + +/////// PRIVATE METHODS ////////// + +// get next empty index +int +rbo_next_end_index(rbo_t *self) { + //buffer is full + if (self->isFull(self)) return -1; + //if empty dont incriment + return (self->end+(unsigned int)!self->isEmpty(self))%self->len; +} + +// incriment index of rbo_t struct, only call if safe to do so +int +rbo_incr_end(rbo_t *self) { + self->end = (self->end+1)%self->len; + return self->end; +} + + +// incriment index of rbo_t struct, only call if safe to do so +int +rbo_incr_start(rbo_t *self) { + self->start = (self->start+1)%self->len; + return self->start; +} + +/////// PUBLIC METHODS ////////// + +// Add an object struct to rbo_t +int +rbo_append(rbo_t *self, const void *object) { + int index; + // Perform all atomic operations + RB_ATOMIC_START + { + index = self->next_end_index(self); + //if not full + if (index >= 0) + { + memcpy(self->buf + index*self->size, object, self->size); + if (!self->isEmpty(self)) self->incr_end_index(self); + self->elements++; + } + } + RB_ATOMIC_END + + return index; +} + +// Return pointer to num element, return null on empty or num out of bounds +void* +rbo_peek(rbo_t *self, unsigned int num) { + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + //empty or out of bounds + if (self->isEmpty(self) || num > self->elements - 1) ret = NULL; + else ret = &self->buf[((self->start + num)%self->len)*self->size]; + } + RB_ATOMIC_END + + return ret; +} + +// Returns and removes first buffer element +void* +rbo_pop(rbo_t *self, void *object) { + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + if (self->isEmpty(self)) ret = NULL; + // Else copy Object + else + { + memcpy(object, self->buf+self->start*self->size, self->size); + self->elements--; + // don't increment start if removing last element + if (!self->isEmpty(self)) self->incr_start_index(self); + ret = object; + } + } + RB_ATOMIC_END + + return ret; +} + +// Returns number of elemnts in buffer +unsigned int +rbo_count(rbo_t *self) { + unsigned int elements; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + elements = self->elements; + } + RB_ATOMIC_END + + return elements; +} + +// Returns true if buffer is full +bool +rbo_is_full(rbo_t *self) { + bool ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = self->elements == self->len; + } + RB_ATOMIC_END + + return ret; +} + +// Returns true if buffer is empty +bool +rbo_is_empty(rbo_t *self) { + bool ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = !self->elements; + } + RB_ATOMIC_END + + return ret; +} diff --git a/ports/wapy/core/ringbuf_o.h b/ports/wapy/core/ringbuf_o.h new file mode 100644 index 000000000..e9592e4c5 --- /dev/null +++ b/ports/wapy/core/ringbuf_o.h @@ -0,0 +1,120 @@ + +/* + rbo_t.h - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ +#ifndef RINGBUF_O_H +#define RINGBUF_O_H + +#if __ARDUINO__ + #include "Arduino.h" + #define RB_ATOMIC_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + #define RB_ATOMIC_END } +#else + #define RB_ATOMIC_START + #define RB_ATOMIC_END +#endif + + +#ifndef __cplusplus + #ifndef bool + #define bool uint8_t + #endif +#endif + +#if defined(ARDUINO_ARCH_AVR) + #include + +#elif defined(ARDUINO_ARCH_ESP8266) + #ifndef __STRINGIFY + #define __STRINGIFY(a) #a + #endif + + #ifndef xt_rsil + #define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;})) + #endif + + #ifndef xt_wsr_ps + #define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory") + #endif + + #define RB_ATOMIC_START do { uint32_t _savedIS = xt_rsil(15) ; + #define RB_ATOMIC_END xt_wsr_ps(_savedIS) ;} while(0); +#else + + #if __EMSCRIPTEN__ || __WASM__ || __ANDROID__ + + #else + #if __ARDUINO__ + #error ("This library only supports AVR and ESP8266 Boards.") + #endif + #endif + +#endif + + +typedef struct rbo_t rbo_t; + + + +#ifdef __cplusplus +extern "C" { +#endif + +rbo_t *rbo_t_new(int size, int len); + +int rbo_init(rbo_t *self, int size, int len); +int rbo_delete(rbo_t *self); + +int rbo_next_end_index(rbo_t *self); +int rbo_incr_end(rbo_t *self); +int rbo_incr_start(rbo_t *self); +int rbo_append(rbo_t *self, const void *object); +void *rbo_peek(rbo_t *self, unsigned int num); +void *rbo_pop(rbo_t *self, void *object); +bool rbo_is_full(rbo_t *self); +bool rbo_is_empty(rbo_t *self); +unsigned int rbo_count(rbo_t *self); + +#ifdef __cplusplus +} +#endif + + +// For those of you who cant live without pretty C++ objects.... +#ifdef __cplusplus +class rbo_tC +{ + +public: + rbo_tC(int size, int len) { buf = rbo_t_new(size, len); } + ~rbo_tC() { rbo_delete(buf); } + + bool isFull() { return rbo_is_full(buf); } + bool isEmpty() { return rbo_is_empty(buf); } + unsigned int numElements() { return rbo_count(buf); } + + unsigned int add(const void *object) { return rbo_append(buf, object); } + void *peek(unsigned int num) { return rbo_peek(buf, num); } + void *pull(void *object) { return rbo_pop(buf, object); } + + // Use this to check if memory allocation failed + bool allocFailed() { return !buf; } + +private: + rbo_t *buf; +}; +#endif + + +#endif // RINGBUF_O_H + + + + + + + + diff --git a/ports/wapy/core/vfs.c b/ports/wapy/core/vfs.c new file mode 100644 index 000000000..3c561476c --- /dev/null +++ b/ports/wapy/core/vfs.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include + +#include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/repl.h" +#include "py/gc.h" +#include "lib/utils/pyexec.h" +#include "py/mphal.h" + +#include "../upython.h" + + +#if !MICROPY_VFS +// FIXME: +extern mp_import_stat_t +wasm_find_module(const char *modname); + +mp_import_stat_t mp_import_stat(const char *path) { + fprintf(stderr,"stat(%s) : ", path); + return wasm_find_module(path); +} +#endif + + +#if !MICROPY_READER_POSIX && MICROPY_EMIT_WASM +extern size_t bsd_strlen(const char *str); + +mp_lexer_t * +mp_lexer_new_from_file(const char *filename) { + FILE *file = fopen(filename,"r"); + if (!file) { + printf("404: fopen(%s)\n", filename); + fprintf(stderr, "404: fopen(%s)\n", filename); + return NULL; + } + fseek(file, 0, SEEK_END); + long long size_of_file = ftell(file); + fprintf(stderr, "mp_lexer_new_from_file(%s size=%lld)\n", filename, (long long)size_of_file ); + fseek(file, 0, SEEK_SET); + + char * cbuf = malloc(size_of_file+1); + fread(cbuf, size_of_file, 1, file); + cbuf[size_of_file]=0; + + if (cbuf == NULL) { + fprintf(stderr, "READ ERROR: mp_lexer_new_from_file(%s size=%lld)\n", filename, (long long)size_of_file ); + return NULL; + } + mp_lexer_t* lex = mp_lexer_new_from_str_len(qstr_from_str(filename), cbuf, bsd_strlen(cbuf), 0); + //fprintf(stderr, "===== EXPECT FAILURE =====\n%s\n===== EXPECT FAILURE ========\n" , cbuf); + free(cbuf); // <- remove that and emcc -shared will break + return lex; +} +#if MICROPY_HELPER_LEXER_UNIX + +mp_lexer_t *mp_lexer_new_from_fd(qstr filename, int fd, bool close_fd) { + mp_reader_t reader; + mp_reader_new_file_from_fd(&reader, fd, close_fd); + return mp_lexer_new(filename, reader); +} + +#endif + +#else +extern mp_lexer_t *mp_lexer_new_from_file(const char *filename); +#endif + + + +#if !MICROPY_READER_POSIX && MICROPY_EMIT_WASM + +#include +#include +#include +#include "py/mperrno.h" + +typedef struct _mp_reader_posix_t { + bool close_fd; + int fd; + size_t len; + size_t pos; + byte buf[20]; +} mp_reader_posix_t; + +STATIC mp_uint_t +mp_reader_posix_readbyte(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t*)data; + if (reader->pos >= reader->len) { + if (reader->len == 0) { + return MP_READER_EOF; + } else { + int n = read(reader->fd, reader->buf, sizeof(reader->buf)); + if (n <= 0) { + reader->len = 0; + return MP_READER_EOF; + } + reader->len = n; + reader->pos = 0; + } + } + return reader->buf[reader->pos++]; +} + +STATIC void +mp_reader_posix_close(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t*)data; + if (reader->close_fd) { + close(reader->fd); + } + m_del_obj(mp_reader_posix_t, reader); +} + +void +mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { + mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); + rp->close_fd = close_fd; + rp->fd = fd; + int n = read(rp->fd, rp->buf, sizeof(rp->buf)); + if (n == -1) { + if (close_fd) { + close(fd); + } + mp_raise_OSError_o(errno); + } + rp->len = n; + rp->pos = 0; + reader->data = rp; + reader->readbyte = mp_reader_posix_readbyte; + reader->close = mp_reader_posix_close; +} + +void +mp_reader_new_file(mp_reader_t *reader, const char *filename) { + int fd = open(filename, O_RDONLY, 0644); + if (fd < 0) { + mp_raise_OSError_o(errno); + } + mp_reader_new_file_from_fd(reader, fd, true); +} +#endif diff --git a/ports/wapy/cpy.c b/ports/wapy/cpy.c new file mode 100644 index 000000000..4215e0b84 --- /dev/null +++ b/ports/wapy/cpy.c @@ -0,0 +1,148 @@ +#include + +#include "py/objlist.h" +#include "py/runtime.h" + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + +#define False 0 +#define True !False + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +/* +extern void gc_init(void *start, void *end); +extern void mp_pystack_init(void *start, void *end); +extern void mp_init(void); +*/ + + +void +Py_Initialize(){ + int stack_dummy; + stack_top = (char *) &stack_dummy; + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); + mp_init(); + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + + + +struct +wPyInterpreterState { + char *shm_stdio ; + char *shm_input_event_0; +}; + + +struct +wPyThreadState { + struct wPyInterpreterState *interp; +}; + + +//static +struct wPyInterpreterState i_main; +//static +struct wPyThreadState i_state; + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + + +char * +wPy_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + #if MICROPY_REPL_EVENT_DRIVEN + pyexec_event_repl_init(); + #else + fprintf(stdout,"no repl\n"); + #endif + return shm_ptr(); +} + + + + + + + +#include "py/compile.h" + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stderr, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, False); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + 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; + //mp_raise(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stderr, "150:NULL FUNCTION\n"); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + diff --git a/ports/wapy/flash/frozen_str.py b/ports/wapy/flash/frozen_str.py new file mode 100644 index 000000000..24ea25efc --- /dev/null +++ b/ports/wapy/flash/frozen_str.py @@ -0,0 +1,2 @@ +print('Hello FROZEN STR') + diff --git a/ports/wapy/modbuiltins_no_nlr.c b/ports/wapy/modbuiltins_no_nlr.c new file mode 100644 index 000000000..550d849c3 --- /dev/null +++ b/ports/wapy/modbuiltins_no_nlr.c @@ -0,0 +1,819 @@ +/* + * 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 + +#include "py/smallint.h" +#include "py/objint.h" +#include "py/objstr.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stream.h" + +#if MICROPY_PY_BUILTINS_FLOAT +#include +#endif + +#if MICROPY_PY_IO +extern struct _mp_dummy_t mp_sys_stdout_obj; // type is irrelevant, just need pointer +#endif + +// args[0] is function from class body +// args[1] is class name +// args[2:] are base objects +STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) { + assert(2 <= n_args); + + // set the new classes __locals__ object + mp_obj_dict_t *old_locals = mp_locals_get(); + mp_obj_t class_locals = mp_obj_new_dict(0); + mp_locals_set(MP_OBJ_TO_PTR(class_locals)); + + // call the class code + mp_obj_t cell = mp_call_function_0(args[0]); + + // restore old __locals__ object + mp_locals_set(old_locals); + + // get the class type (meta object) from the base objects + mp_obj_t meta; + if (n_args == 2) { + // no explicit bases, so use 'type' + meta = MP_OBJ_FROM_PTR(&mp_type_type); + } else { + // use type of first base object + meta = MP_OBJ_FROM_PTR(mp_obj_get_type(args[2])); + } + + // TODO do proper metaclass resolution for multiple base objects + + // create the new class using a call to the meta object + mp_obj_t meta_args[3]; + meta_args[0] = args[1]; // class name + meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases + meta_args[2] = class_locals; // dict of members + mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); + + // store into cell if neede + if (cell != mp_const_none) { + mp_obj_cell_set(cell, new_class); + } + + return new_class; +} +MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj, 2, mp_builtin___build_class__); + +STATIC mp_obj_t mp_builtin_abs(mp_obj_t o_in) { + return mp_unary_op(MP_UNARY_OP_ABS, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_abs_obj, mp_builtin_abs); + +STATIC mp_obj_t mp_builtin_all(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (!mp_obj_is_true(item)) { + return mp_const_false; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_all_obj, mp_builtin_all); + +STATIC mp_obj_t mp_builtin_any(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (mp_obj_is_true(item)) { + return mp_const_true; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return mp_const_false; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_any_obj, mp_builtin_any); + +STATIC mp_obj_t mp_builtin_bin(mp_obj_t o_in) { + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_b_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_bin_obj, mp_builtin_bin); + +STATIC mp_obj_t mp_builtin_callable(mp_obj_t o_in) { + if (mp_obj_is_callable(o_in)) { + return mp_const_true; + } else { + return mp_const_false; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_callable_obj, mp_builtin_callable); + +STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { +#pragma message "interning everything is *not* clever (pmpp-p)" + #if MICROPY_PY_BUILTINS_STR_UNICODE + mp_uint_t c = mp_obj_get_int(o_in); + uint8_t str[4]; + int len = 0; + if (c < 0x80) { + *str = c; + len = 1; + } else if (c < 0x800) { + str[0] = (c >> 6) | 0xC0; + str[1] = (c & 0x3F) | 0x80; + len = 2; + } else if (c < 0x10000) { + str[0] = (c >> 12) | 0xE0; + str[1] = ((c >> 6) & 0x3F) | 0x80; + str[2] = (c & 0x3F) | 0x80; + len = 3; + } else if (c < 0x110000) { + str[0] = (c >> 18) | 0xF0; + str[1] = ((c >> 12) & 0x3F) | 0x80; + str[2] = ((c >> 6) & 0x3F) | 0x80; + str[3] = (c & 0x3F) | 0x80; + len = 4; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(0x110000)")); + } + return mp_obj_new_str_via_qstr((char *)str, len); + #else + mp_int_t ord = mp_obj_get_int(o_in); + if (0 <= ord && ord <= 0xff) { + uint8_t str[1] = {ord}; + + return mp_obj_new_str_via_qstr((char *)str, 1); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(256)")); + } + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); + +STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { + mp_obj_t dir = mp_obj_new_list(0, NULL); + if (n_args == 0) { + // Make a list of names in the local namespace + mp_obj_dict_t *dict = mp_locals_get(); + for (size_t i = 0; i < dict->map.alloc; i++) { + if (mp_map_slot_is_filled(&dict->map, i)) { + mp_obj_list_append(dir, dict->map.table[i].key); + } + } + } else { // n_args == 1 + // Make a list of names in the given object + // Implemented by probing all possible qstrs with mp_load_method_maybe + size_t nqstr = QSTR_TOTAL(); + for (size_t i = MP_QSTR_ + 1; i < nqstr; ++i) { + mp_obj_t dest[2]; + mp_load_method_protected(args[0], i, dest, false); + if (dest[0] != MP_OBJ_NULL) { + #if MICROPY_PY_ALL_SPECIAL_METHODS + // Support for __dir__: see if we can dispatch to this special method + // This relies on MP_QSTR__dir__ being first after MP_QSTR_ + if (i == MP_QSTR___dir__ && dest[1] != MP_OBJ_NULL) { + return mp_call_method_n_kw(0, 0, dest); + } + #endif + mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i)); + } + } + } + return dir; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); + +STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { + return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_divmod_obj, mp_builtin_divmod); + +STATIC mp_obj_t mp_builtin_hash(mp_obj_t o_in) { + // result is guaranteed to be a (small) int + return mp_unary_op(MP_UNARY_OP_HASH, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hash_obj, mp_builtin_hash); + +STATIC mp_obj_t mp_builtin_hex(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_x), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_x_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hex_obj, mp_builtin_hex); + +#if MICROPY_PY_BUILTINS_INPUT + +#include "py/mphal.h" +#include "lib/mp-readline/readline.h" + +// A port can define mp_hal_readline if they want to use a custom function here +#ifndef mp_hal_readline +#define mp_hal_readline readline +#endif + +STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_print(args[0], PRINT_STR); + } + vstr_t line; + vstr_init(&line, 16); + int ret = mp_hal_readline(&line, ""); + if (ret == CHAR_CTRL_C) { + mp_raise_type(&mp_type_KeyboardInterrupt); + } + if (line.len == 0 && ret == CHAR_CTRL_D) { + mp_raise_type(&mp_type_EOFError); + } + return mp_obj_new_str_from_vstr(&mp_type_str, &line); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj, 0, 1, mp_builtin_input); + +#endif + +STATIC mp_obj_t mp_builtin_iter(mp_obj_t o_in) { + return mp_getiter(o_in, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter); + +#if MICROPY_PY_BUILTINS_MIN_MAX + +STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs, mp_uint_t op) { + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_key), MP_MAP_LOOKUP); + mp_map_elem_t *default_elem; + mp_obj_t key_fn = key_elem == NULL ? MP_OBJ_NULL : key_elem->value; + if (n_args == 1) { + // given an iterable + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? item : mp_call_function_1(key_fn, item); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = item; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + if (best_obj == MP_OBJ_NULL) { + default_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_default), MP_MAP_LOOKUP); + if (default_elem != NULL) { + best_obj = default_elem->value; + } else { + return mp_raise_ValueError_o("arg is an empty sequence"); + } + } + return best_obj; + } else { + // given many args + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + for (size_t i = 0; i < n_args; i++) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? args[i] : mp_call_function_1(key_fn, args[i]); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = args[i]; + } + } + return best_obj; + } +} + +STATIC mp_obj_t mp_builtin_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_MORE); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_max_obj, 1, mp_builtin_max); + +STATIC mp_obj_t mp_builtin_min(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_LESS); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_min_obj, 1, mp_builtin_min); + +#endif + +#if MICROPY_PY_BUILTINS_NEXT2 +STATIC mp_obj_t mp_builtin_next(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_t ret = mp_iternext_allow_raise(args[0]); + if (ret == MP_OBJ_STOP_ITERATION) { + mp_raise_type(&mp_type_StopIteration); + } else { + return ret; + } + } else { + mp_obj_t ret = mp_iternext(args[0]); + return ret == MP_OBJ_STOP_ITERATION ? args[1] : ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); +#else +STATIC mp_obj_t mp_builtin_next(mp_obj_t o) { + mp_obj_t ret = mp_iternext_allow_raise(o); + if (ret == MP_OBJ_STOP_ITERATION) { + mp_raise_type(&mp_type_StopIteration); + } else { + return ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_next_obj, mp_builtin_next); +#endif + +STATIC mp_obj_t mp_builtin_oct(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_o), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_o_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_oct_obj, mp_builtin_oct); + +STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { + size_t len; + const byte *str = (const byte *)mp_obj_str_get_data(o_in, &len); + if (str == NULL) { + // exception + return MP_OBJ_NULL; + } + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (mp_obj_is_str(o_in)) { + len = utf8_charlen(str, len); + if (len == 1) { + return mp_obj_new_int(utf8_get_char(str)); + } + } else + #endif + { + // a bytes object, or a str without unicode support (don't sign extend the char) + if (len == 1) { + return MP_OBJ_NEW_SMALL_INT(str[0]); + } + } + + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("ord expects a character")); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("ord() expected a character, but string of length %d found"), (int)len)); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); + +STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { + switch (n_args) { + case 2: + return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); + default: + #if !MICROPY_PY_BUILTINS_POW3 + mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); + #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); + #else + return mp_obj_int_pow3(args[0], args[1], args[2]); + #endif + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); + +STATIC mp_obj_t mp_builtin_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sep, ARG_end, ARG_file }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} }, + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + { MP_QSTR_file, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_sys_stdout_obj)} }, + #endif + }; + + // parse args (a union is used to reduce the amount of C stack that is needed) + union { + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + size_t len[2]; + } u; + mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, u.args); + + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + if (mp_get_stream_raise(u.args[ARG_file].u_obj, MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } + mp_print_t print = {MP_OBJ_TO_PTR(u.args[ARG_file].u_obj), mp_stream_write_adaptor}; + #endif + + // extract the objects first because we are going to use the other part of the union + mp_obj_t sep = u.args[ARG_sep].u_obj; + mp_obj_t end = u.args[ARG_end].u_obj; + const char *sep_data = mp_obj_str_get_data(sep, &u.len[0]); + const char *end_data = mp_obj_str_get_data(end, &u.len[1]); + + for (size_t i = 0; i < n_args; i++) { + if (i > 0) { + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, sep_data, u.len[0]); + #else + mp_print_strn(&mp_plat_print, sep_data, u.len[0], 0, 0, 0); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_obj_print_helper(&print, pos_args[i], PRINT_STR); + #else + mp_obj_print_helper(&mp_plat_print, pos_args[i], PRINT_STR); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, end_data, u.len[1]); + #else + mp_print_strn(&mp_plat_print, end_data, u.len[1], 0, 0, 0); + #endif + if (MP_STATE_THREAD(active_exception) != NULL) { + // some printing function above had an exception + return MP_OBJ_NULL; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_print_obj, 0, mp_builtin_print); + +STATIC mp_obj_t mp_builtin___repl_print__(mp_obj_t o) { + if (o != mp_const_none) { + mp_obj_print_helper(MP_PYTHON_PRINTER, o, PRINT_REPR); + mp_print_str(MP_PYTHON_PRINTER, "\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Set "_" special variable + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, o}; + mp_type_module.attr(MP_OBJ_FROM_PTR(&mp_module_builtins), MP_QSTR__, dest); + #endif + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin___repl_print___obj, mp_builtin___repl_print__); + +STATIC mp_obj_t mp_builtin_repr(mp_obj_t o_in) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, o_in, PRINT_REPR); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_repr_obj, mp_builtin_repr); + +STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { + mp_obj_t o_in = args[0]; + if (mp_obj_is_int(o_in)) { + if (n_args <= 1) { + return o_in; + } + + #if !MICROPY_PY_BUILTINS_ROUND_INT + mp_raise_NotImplementedError(NULL); + #else + mp_int_t num_dig = mp_obj_get_int(args[1]); + if (num_dig >= 0) { + return o_in; + } + + mp_obj_t mult = mp_binary_op(MP_BINARY_OP_POWER, MP_OBJ_NEW_SMALL_INT(10), MP_OBJ_NEW_SMALL_INT(-num_dig)); + mp_obj_t half_mult = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, mult, MP_OBJ_NEW_SMALL_INT(2)); + mp_obj_t modulo = mp_binary_op(MP_BINARY_OP_MODULO, o_in, mult); + mp_obj_t rounded = mp_binary_op(MP_BINARY_OP_SUBTRACT, o_in, modulo); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, half_mult, modulo))) { + return rounded; + } else if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, modulo, half_mult))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + // round to even number + mp_obj_t floor = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, o_in, mult); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_AND, floor, MP_OBJ_NEW_SMALL_INT(1)))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + return rounded; + } + } + #endif + } + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(o_in); + if (n_args > 1) { + mp_int_t num_dig = mp_obj_get_int(args[1]); + mp_float_t mult = MICROPY_FLOAT_C_FUN(pow)(10, (mp_float_t)num_dig); + // TODO may lead to overflow + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val * mult) / mult; + return mp_obj_new_float(rounded); + } + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val); + return mp_obj_new_int_from_float(rounded); + #else + mp_int_t r = mp_obj_get_int(o_in); + return mp_obj_new_int(r); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj, 1, 2, mp_builtin_round); + +STATIC mp_obj_t mp_builtin_sum(size_t n_args, const mp_obj_t *args) { + mp_obj_t value; + switch (n_args) { + case 1: + value = MP_OBJ_NEW_SMALL_INT(0); + break; + default: + value = args[1]; + break; + } + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + value = mp_binary_op(MP_BINARY_OP_ADD, value, item); + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return value; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum); + +STATIC mp_obj_t mp_builtin_sorted(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args > 1) { + return mp_raise_TypeError_o("must use keyword argument for key function"); + } + mp_obj_t self = mp_type_list.make_new(&mp_type_list, 1, 0, args); + mp_obj_list_sort(1, &self, kwargs); + + return self; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); + +// See mp_load_attr() if making any changes +static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { + mp_obj_t dest[2]; + // use load_method, raising or not raising exception + ((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) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +STATIC mp_obj_t mp_builtin_getattr(size_t n_args, const mp_obj_t *args) { + mp_obj_t defval = MP_OBJ_NULL; + if (n_args > 2) { + defval = args[2]; + } + return mp_load_attr_default(args[0], mp_obj_str_get_qstr(args[1]), defval); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr); + +STATIC mp_obj_t mp_builtin_setattr(mp_obj_t base, mp_obj_t attr, mp_obj_t value) { + if (mp_store_attr(base, mp_obj_str_get_qstr(attr), value) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(mp_builtin_setattr_obj, mp_builtin_setattr); + +#if MICROPY_CPYTHON_COMPAT +STATIC mp_obj_t mp_builtin_delattr(mp_obj_t base, mp_obj_t attr) { + return mp_builtin_setattr(base, attr, MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_delattr_obj, mp_builtin_delattr); +#endif + +STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) { + qstr attr = mp_obj_str_get_qstr(attr_in); + if (attr == MP_QSTR_NULL) { + // exception + return MP_OBJ_NULL; + } + mp_obj_t dest[2]; + if (mp_load_method_protected(object_in, attr, dest, false) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_bool(dest[0] != MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr); + +STATIC mp_obj_t mp_builtin_globals(void) { + return MP_OBJ_FROM_PTR(mp_globals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_builtin_globals); + +STATIC mp_obj_t mp_builtin_locals(void) { + return MP_OBJ_FROM_PTR(mp_locals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_builtin_locals); + +// These are defined in terms of MicroPython API functions right away +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_id_obj, mp_obj_id); +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_len_obj, mp_obj_len); + +STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_builtins) }, + + // built-in core functions + { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, + { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, +// { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + { MP_ROM_QSTR(MP_QSTR_displayhook), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + + // built-in types + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, + { MP_ROM_QSTR(MP_QSTR_bytes), MP_ROM_PTR(&mp_type_bytes) }, + #if MICROPY_PY_BUILTINS_BYTEARRAY + { MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&mp_type_bytearray) }, + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_PTR(&mp_type_complex) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&mp_type_dict) }, + #if MICROPY_PY_BUILTINS_ENUMERATE + { MP_ROM_QSTR(MP_QSTR_enumerate), MP_ROM_PTR(&mp_type_enumerate) }, + #endif + #if MICROPY_PY_BUILTINS_FILTER + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&mp_type_filter) }, + #endif + #if MICROPY_PY_BUILTINS_FLOAT + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_PTR(&mp_type_float) }, + #endif + #if MICROPY_PY_BUILTINS_SET && MICROPY_PY_BUILTINS_FROZENSET + { MP_ROM_QSTR(MP_QSTR_frozenset), MP_ROM_PTR(&mp_type_frozenset) }, + #endif + { MP_ROM_QSTR(MP_QSTR_int), MP_ROM_PTR(&mp_type_int) }, + { MP_ROM_QSTR(MP_QSTR_list), MP_ROM_PTR(&mp_type_list) }, + { MP_ROM_QSTR(MP_QSTR_map), MP_ROM_PTR(&mp_type_map) }, + #if MICROPY_PY_BUILTINS_MEMORYVIEW + { MP_ROM_QSTR(MP_QSTR_memoryview), MP_ROM_PTR(&mp_type_memoryview) }, + #endif + { MP_ROM_QSTR(MP_QSTR_object), MP_ROM_PTR(&mp_type_object) }, + #if MICROPY_PY_BUILTINS_PROPERTY + { MP_ROM_QSTR(MP_QSTR_property), MP_ROM_PTR(&mp_type_property) }, + #endif + { MP_ROM_QSTR(MP_QSTR_range), MP_ROM_PTR(&mp_type_range) }, + #if MICROPY_PY_BUILTINS_REVERSED + { MP_ROM_QSTR(MP_QSTR_reversed), MP_ROM_PTR(&mp_type_reversed) }, + #endif + #if MICROPY_PY_BUILTINS_SET + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mp_type_set) }, + #endif + #if MICROPY_PY_BUILTINS_SLICE + { MP_ROM_QSTR(MP_QSTR_slice), MP_ROM_PTR(&mp_type_slice) }, + #endif + { MP_ROM_QSTR(MP_QSTR_str), MP_ROM_PTR(&mp_type_str) }, + { MP_ROM_QSTR(MP_QSTR_super), MP_ROM_PTR(&mp_type_super) }, + { MP_ROM_QSTR(MP_QSTR_tuple), MP_ROM_PTR(&mp_type_tuple) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&mp_type_type) }, + { MP_ROM_QSTR(MP_QSTR_zip), MP_ROM_PTR(&mp_type_zip) }, + + { MP_ROM_QSTR(MP_QSTR_classmethod), MP_ROM_PTR(&mp_type_classmethod) }, + { MP_ROM_QSTR(MP_QSTR_staticmethod), MP_ROM_PTR(&mp_type_staticmethod) }, + + // built-in objects + { MP_ROM_QSTR(MP_QSTR_Ellipsis), MP_ROM_PTR(&mp_const_ellipsis_obj) }, + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + { MP_ROM_QSTR(MP_QSTR_NotImplemented), MP_ROM_PTR(&mp_const_notimplemented_obj) }, + #endif + + // built-in user functions + { MP_ROM_QSTR(MP_QSTR_abs), MP_ROM_PTR(&mp_builtin_abs_obj) }, + { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&mp_builtin_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&mp_builtin_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_bin), MP_ROM_PTR(&mp_builtin_bin_obj) }, + { MP_ROM_QSTR(MP_QSTR_callable), MP_ROM_PTR(&mp_builtin_callable_obj) }, + #if MICROPY_PY_BUILTINS_COMPILE + { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mp_builtin_compile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_chr), MP_ROM_PTR(&mp_builtin_chr_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, + #if MICROPY_PY_BUILTINS_EVAL_EXEC + { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&mp_builtin_exec_obj) }, + #endif + #if MICROPY_PY_BUILTINS_EXECFILE + { MP_ROM_QSTR(MP_QSTR_execfile), MP_ROM_PTR(&mp_builtin_execfile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_getattr), MP_ROM_PTR(&mp_builtin_getattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setattr), MP_ROM_PTR(&mp_builtin_setattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_globals), MP_ROM_PTR(&mp_builtin_globals_obj) }, + { MP_ROM_QSTR(MP_QSTR_hasattr), MP_ROM_PTR(&mp_builtin_hasattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mp_builtin_hash_obj) }, + #if MICROPY_PY_BUILTINS_HELP + { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&mp_builtin_help_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_hex), MP_ROM_PTR(&mp_builtin_hex_obj) }, + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&mp_builtin_id_obj) }, + #if MICROPY_PY_BUILTINS_INPUT + { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mp_builtin_input_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_isinstance), MP_ROM_PTR(&mp_builtin_isinstance_obj) }, + { MP_ROM_QSTR(MP_QSTR_issubclass), MP_ROM_PTR(&mp_builtin_issubclass_obj) }, + { MP_ROM_QSTR(MP_QSTR_iter), MP_ROM_PTR(&mp_builtin_iter_obj) }, + { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&mp_builtin_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_locals), MP_ROM_PTR(&mp_builtin_locals_obj) }, + #if MICROPY_PY_BUILTINS_MIN_MAX + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&mp_builtin_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&mp_builtin_min_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, + { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, + { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, + { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, + { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, + { MP_ROM_QSTR(MP_QSTR_repr), MP_ROM_PTR(&mp_builtin_repr_obj) }, + { MP_ROM_QSTR(MP_QSTR_round), MP_ROM_PTR(&mp_builtin_round_obj) }, + { MP_ROM_QSTR(MP_QSTR_sorted), MP_ROM_PTR(&mp_builtin_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&mp_builtin_sum_obj) }, + + // built-in exceptions + { MP_ROM_QSTR(MP_QSTR_BaseException), MP_ROM_PTR(&mp_type_BaseException) }, + { MP_ROM_QSTR(MP_QSTR_ArithmeticError), MP_ROM_PTR(&mp_type_ArithmeticError) }, + { MP_ROM_QSTR(MP_QSTR_AssertionError), MP_ROM_PTR(&mp_type_AssertionError) }, + { MP_ROM_QSTR(MP_QSTR_AttributeError), MP_ROM_PTR(&mp_type_AttributeError) }, + { MP_ROM_QSTR(MP_QSTR_EOFError), MP_ROM_PTR(&mp_type_EOFError) }, + { MP_ROM_QSTR(MP_QSTR_Exception), MP_ROM_PTR(&mp_type_Exception) }, + { MP_ROM_QSTR(MP_QSTR_GeneratorExit), MP_ROM_PTR(&mp_type_GeneratorExit) }, + { MP_ROM_QSTR(MP_QSTR_ImportError), MP_ROM_PTR(&mp_type_ImportError) }, + { MP_ROM_QSTR(MP_QSTR_IndentationError), MP_ROM_PTR(&mp_type_IndentationError) }, + { MP_ROM_QSTR(MP_QSTR_IndexError), MP_ROM_PTR(&mp_type_IndexError) }, + { MP_ROM_QSTR(MP_QSTR_KeyboardInterrupt), MP_ROM_PTR(&mp_type_KeyboardInterrupt) }, + { MP_ROM_QSTR(MP_QSTR_KeyError), MP_ROM_PTR(&mp_type_KeyError) }, + { MP_ROM_QSTR(MP_QSTR_LookupError), MP_ROM_PTR(&mp_type_LookupError) }, + { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_MemoryError) }, + { MP_ROM_QSTR(MP_QSTR_NameError), MP_ROM_PTR(&mp_type_NameError) }, + { MP_ROM_QSTR(MP_QSTR_NotImplementedError), MP_ROM_PTR(&mp_type_NotImplementedError) }, + { MP_ROM_QSTR(MP_QSTR_OSError), MP_ROM_PTR(&mp_type_OSError) }, + { MP_ROM_QSTR(MP_QSTR_OverflowError), MP_ROM_PTR(&mp_type_OverflowError) }, + { MP_ROM_QSTR(MP_QSTR_RuntimeError), MP_ROM_PTR(&mp_type_RuntimeError) }, + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR_StopAsyncIteration), MP_ROM_PTR(&mp_type_StopAsyncIteration) }, + #endif + { MP_ROM_QSTR(MP_QSTR_StopIteration), MP_ROM_PTR(&mp_type_StopIteration) }, + { MP_ROM_QSTR(MP_QSTR_SyntaxError), MP_ROM_PTR(&mp_type_SyntaxError) }, + { MP_ROM_QSTR(MP_QSTR_SystemExit), MP_ROM_PTR(&mp_type_SystemExit) }, + { MP_ROM_QSTR(MP_QSTR_TypeError), MP_ROM_PTR(&mp_type_TypeError) }, + #if MICROPY_PY_BUILTINS_STR_UNICODE + { MP_ROM_QSTR(MP_QSTR_UnicodeError), MP_ROM_PTR(&mp_type_UnicodeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ValueError), MP_ROM_PTR(&mp_type_ValueError) }, + #if MICROPY_EMIT_NATIVE + { MP_ROM_QSTR(MP_QSTR_ViperTypeError), MP_ROM_PTR(&mp_type_ViperTypeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ZeroDivisionError), MP_ROM_PTR(&mp_type_ZeroDivisionError) }, + + // Extra builtins as defined by a port + MICROPY_PORT_BUILTINS +}; + +MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); + +const mp_obj_module_t mp_module_builtins = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_builtins_globals, +}; diff --git a/ports/wapy/objarray_no_nlr.c b/ports/wapy/objarray_no_nlr.c new file mode 100644 index 000000000..c3057248a --- /dev/null +++ b/ports/wapy/objarray_no_nlr.c @@ -0,0 +1,715 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 "py/runtime.h" +#include "py/binary.h" +#include "py/objstr.h" +#include "py/objarray.h" + +#if MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW + +// About memoryview object: We want to reuse as much code as possible from +// array, and keep the memoryview object 4 words in size so it fits in 1 GC +// block. Also, memoryview must keep a pointer to the base of the buffer so +// that the buffer is not GC'd if the original parent object is no longer +// around (we are assuming that all memoryview'able objects return a pointer +// which points to the start of a GC chunk). Given the above constraints we +// do the following: +// - typecode high bit is set if the buffer is read-write (else read-only) +// - free is the offset in elements to the first item in the memoryview +// - len is the length in elements +// - items points to the start of the original buffer +// Note that we don't handle the case where the original buffer might change +// size due to a resize of the original parent object. + +#if MICROPY_PY_BUILTINS_MEMORYVIEW +#define TYPECODE_MASK (0x7f) +#define memview_offset free +#else +// make (& TYPECODE_MASK) a null operation if memorview not enabled +#define TYPECODE_MASK (~(size_t)0) +// memview_offset should not be accessed if memoryview is not enabled, +// so not defined to catch errors +#endif + +STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg); +STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in); +STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); + +/******************************************************************************/ +// array + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC void array_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + if (o->typecode == BYTEARRAY_TYPECODE) { + mp_print_str(print, "bytearray(b"); + mp_str_print_quoted(print, o->items, o->len, true); + } else { + mp_printf(print, "array('%c'", o->typecode); + if (o->len > 0) { + mp_print_str(print, ", ["); + for (size_t i = 0; i < o->len; i++) { + if (i > 0) { + mp_print_str(print, ", "); + } + mp_obj_print_helper(print, mp_binary_get_val_array(o->typecode, o->items, i), PRINT_REPR); + } + mp_print_str(print, "]"); + } + } + mp_print_str(print, ")"); +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC mp_obj_array_t *array_new(char typecode, size_t n) { + int typecode_size = mp_binary_get_size('@', typecode, NULL); + if (typecode_size == 0) { + return NULL; + } + mp_obj_array_t *o = m_new_obj(mp_obj_array_t); + if (o == NULL) { + return NULL; + } + #if MICROPY_PY_BUILTINS_BYTEARRAY && MICROPY_PY_ARRAY + o->base.type = (typecode == BYTEARRAY_TYPECODE) ? &mp_type_bytearray : &mp_type_array; + #elif MICROPY_PY_BUILTINS_BYTEARRAY + o->base.type = &mp_type_bytearray; + #else + o->base.type = &mp_type_array; + #endif + o->typecode = typecode; + o->free = 0; + o->len = n; + o->items = m_new(byte, typecode_size * o->len); + return o; +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +#include +STATIC mp_obj_t array_construct(char typecode, mp_obj_t initializer) { + // bytearrays can be raw-initialised from anything with the buffer protocol + // other arrays can only be raw-initialised from bytes and bytearray objects + mp_buffer_info_t bufinfo; + if (((MICROPY_PY_BUILTINS_BYTEARRAY + && typecode == BYTEARRAY_TYPECODE) + || (MICROPY_PY_ARRAY + && (mp_obj_is_type(initializer, &mp_type_bytes) + || (MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(initializer, &mp_type_bytearray))))) + && mp_get_buffer(initializer, &bufinfo, MP_BUFFER_READ)) { + // construct array from raw bytes + // we round-down the len to make it a multiple of sz (CPython raises error) + size_t sz = mp_binary_get_size('@', typecode, NULL); + size_t len = bufinfo.len / sz; + mp_obj_array_t *o = array_new(typecode, len); + if (o == NULL) { + return MP_OBJ_NULL; + } + memcpy(o->items, bufinfo.buf, len * sz); + return MP_OBJ_FROM_PTR(o); + } + + size_t len; + // Try to create array of exact len if initializer len is known + mp_obj_t len_in = mp_obj_len_maybe(initializer); + if (len_in == MP_OBJ_NULL) { + len = 0; + } else { + len = MP_OBJ_SMALL_INT_VALUE(len_in); + } + + mp_obj_array_t *array = array_new(typecode, len); + if (array == NULL) { + return MP_OBJ_NULL; + } + + mp_obj_t iterable = mp_getiter(initializer, NULL); + mp_obj_t item; + size_t i = 0; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (len == 0) { + array_append(MP_OBJ_FROM_PTR(array), item); + } else { + mp_binary_set_val_array(typecode, array->items, i++, item); + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + + return MP_OBJ_FROM_PTR(array); +} +#endif + +#if MICROPY_PY_ARRAY +STATIC mp_obj_t array_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + mp_arg_check_num(n_args, n_kw, 1, 2, false); + + // get typecode + const char *typecode = mp_obj_str_get_str(args[0]); + + if (n_args == 1) { + // 1 arg: make an empty array + return MP_OBJ_FROM_PTR(array_new(*typecode, 0)); + } else { + // 2 args: construct the array from the given object + return array_construct(*typecode, args[1]); + } +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY +STATIC mp_obj_t bytearray_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + // Can take 2nd/3rd arg if constructs from str + mp_arg_check_num(n_args, n_kw, 0, 3, false); + + if (n_args == 0) { + // no args: construct an empty bytearray + return MP_OBJ_FROM_PTR(array_new(BYTEARRAY_TYPECODE, 0)); + } else if (mp_obj_is_int(args[0])) { + // 1 arg, an integer: construct a blank bytearray of that length + mp_uint_t len = mp_obj_get_int(args[0]); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, len); + if (o == NULL) { + return MP_OBJ_NULL; + } + memset(o->items, 0, len); + return MP_OBJ_FROM_PTR(o); + } else { + // 1 arg: construct the bytearray from that + return array_construct(BYTEARRAY_TYPECODE, args[0]); + } +} +#endif + +#if MICROPY_PY_BUILTINS_MEMORYVIEW + +mp_obj_t mp_obj_new_memoryview(byte typecode, size_t nitems, void *items) { + mp_obj_array_t *self = m_new_obj(mp_obj_array_t); + if (self == NULL) { + return MP_OBJ_NULL; + } + self->base.type = &mp_type_memoryview; + self->typecode = typecode; + self->memview_offset = 0; + self->len = nitems; + self->items = items; + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t memoryview_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + // TODO possibly allow memoryview constructor to take start/stop so that one + // can do memoryview(b, 4, 8) instead of memoryview(b)[4:8] (uses less RAM) + + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); + + mp_obj_array_t *self = MP_OBJ_TO_PTR(mp_obj_new_memoryview(bufinfo.typecode, + bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL), + bufinfo.buf)); + + // test if the object can be written to + if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) { + self->typecode |= MP_OBJ_ARRAY_TYPECODE_FLAG_RW; // indicate writable buffer + } + + return MP_OBJ_FROM_PTR(self); +} + +#if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE +STATIC void memoryview_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + return; + } + if (attr == MP_QSTR_itemsize) { + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_binary_get_size('@', self->typecode & TYPECODE_MASK, NULL)); + } +} +#endif + +#endif + +STATIC mp_obj_t array_unary_op(mp_unary_op_t op, mp_obj_t o_in) { + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(o->len != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(o->len); + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + mp_obj_array_t *lhs = MP_OBJ_TO_PTR(lhs_in); + switch (op) { + case MP_BINARY_OP_ADD: { + // allow to add anything that has the buffer protocol (extension to CPython) + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + if (!mp_get_buffer_raise(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + return MP_OBJ_NULL; + } + + size_t sz = mp_binary_get_size('@', lhs_bufinfo.typecode, NULL); + + // convert byte count to element count (in case rhs is not multiple of sz) + size_t rhs_len = rhs_bufinfo.len / sz; + + // note: lhs->len is element count of lhs, lhs_bufinfo.len is byte count + mp_obj_array_t *res = array_new(lhs_bufinfo.typecode, lhs->len + rhs_len); + if (res == NULL) { + return MP_OBJ_NULL; + } + mp_seq_cat((byte *)res->items, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_len * sz, byte); + return MP_OBJ_FROM_PTR(res); + } + + case MP_BINARY_OP_INPLACE_ADD: { + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (lhs->base.type == &mp_type_memoryview) { + return MP_OBJ_NULL; // op not supported + } + #endif + if (array_extend(lhs_in, rhs_in) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return lhs_in; + } + + case MP_BINARY_OP_CONTAINS: { + #if MICROPY_PY_BUILTINS_BYTEARRAY + // Can search string only in bytearray + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + if (mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + if (!mp_obj_is_type(lhs_in, &mp_type_bytearray)) { + return mp_const_false; + } + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + return mp_obj_new_bool( + find_subbytes(lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len, 1) != NULL); + } + #endif + + // Otherwise, can only look for a scalar numeric value in an array + if (mp_obj_is_int(rhs_in) || mp_obj_is_float(rhs_in)) { + return mp_raise_NotImplementedError_o(NULL); + } + + return mp_const_false; + } + + case MP_BINARY_OP_EQUAL: { + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + if (!mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + return mp_const_false; + } + return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len)); + } + + default: + return MP_OBJ_NULL; // op not supported + } +} + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg) { + // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) + assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) + || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->free == 0) { + size_t item_sz = mp_binary_get_size('@', self->typecode, NULL); + // TODO: alloc policy + byte *new_items = m_renew(byte, self->items, item_sz * self->len, item_sz * (self->len + 8)); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->free = 8; + self->items = new_items; + mp_seq_clear(self->items, self->len + 1, self->len + self->free, item_sz); + } + mp_binary_set_val_array(self->typecode, self->items, self->len, arg); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + // only update length/free if set succeeded + self->len++; + self->free--; + return mp_const_none; // return None, as per CPython +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_append_obj, array_append); + +STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) { + // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) + assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) + || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + + // allow to extend by anything that has the buffer protocol (extension to CPython) + mp_buffer_info_t arg_bufinfo; + mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ); + + size_t sz = mp_binary_get_size('@', self->typecode, NULL); + + // convert byte count to element count + size_t len = arg_bufinfo.len / sz; + + // make sure we have enough room to extend + // TODO: alloc policy; at the moment we go conservative + if (self->free < len) { + byte *new_items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->items = new_items; + self->free = 0; + } else { + self->free -= len; + } + + // extend + mp_seq_copy((byte *)self->items + self->len * sz, arg_bufinfo.buf, len * sz, byte); + self->len += len; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_extend_obj, array_extend); +#endif + +STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete item + // TODO implement + // TODO: confirmed that both bytearray and array.array support + // slice deletion + return MP_OBJ_NULL; // op not supported + } else { + mp_obj_array_t *o = MP_OBJ_TO_PTR(self_in); + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index_in, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(o->len, index_in, &slice)) { + return mp_raise_NotImplementedError_o("only slices with step=1 (aka None) are supported"); + } + if (value != MP_OBJ_SENTINEL) { + #if MICROPY_PY_ARRAY_SLICE_ASSIGN + // Assign + size_t src_len; + void *src_items; + size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + if (mp_obj_is_obj(value) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type->subscr == array_subscr) { + // value is array, bytearray or memoryview + mp_obj_array_t *src_slice = MP_OBJ_TO_PTR(value); + if (item_sz != mp_binary_get_size('@', src_slice->typecode & TYPECODE_MASK, NULL)) { + compat_error: + return mp_raise_ValueError_o("lhs and rhs should be compatible"); + } + src_len = src_slice->len; + src_items = src_slice->items; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (mp_obj_is_type(value, &mp_type_memoryview)) { + src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz); + } + #endif + } else if (mp_obj_is_type(value, &mp_type_bytes)) { + if (item_sz != 1) { + goto compat_error; + } + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_READ); + src_len = bufinfo.len; + src_items = bufinfo.buf; + } else { + return mp_raise_NotImplementedError_o("array/bytes required on right side"); + } + + // TODO: check src/dst compat + mp_int_t len_adj = src_len - (slice.stop - slice.start); + uint8_t *dest_items = o->items; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { + // store to read-only memoryview not allowed + return MP_OBJ_NULL; + } + if (len_adj != 0) { + goto compat_error; + } + dest_items += o->memview_offset * item_sz; + } + #endif + if (len_adj > 0) { + if ((unsigned long)len_adj > o->free) { + // TODO: alloc policy; at the moment we go conservative + dest_items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz); + if (dest_items == NULL) { + return MP_OBJ_NULL; + } + o->items = dest_items; + o->free = len_adj; + } + mp_seq_replace_slice_grow_inplace(dest_items, o->len, + slice.start, slice.stop, src_items, src_len, len_adj, item_sz); + } else { + mp_seq_replace_slice_no_grow(dest_items, o->len, + slice.start, slice.stop, src_items, src_len, item_sz); + // Clear "freed" elements at the end of list + // TODO: This is actually only needed for typecode=='O' + mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz); + // TODO: alloc policy after shrinking + } + o->free -= len_adj; + o->len += len_adj; + return mp_const_none; + #else + return MP_OBJ_NULL; // op not supported + #endif + } + + mp_obj_array_t *res; + size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + assert(sz > 0); + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + res = m_new_obj(mp_obj_array_t); + if (res == NULL) { + return MP_OBJ_NULL; + } + *res = *o; + res->memview_offset += slice.start; + res->len = slice.stop - slice.start; + } else + #endif + { + res = array_new(o->typecode, slice.stop - slice.start); + if (res == NULL) { + return MP_OBJ_NULL; + } + memcpy(res->items, (uint8_t *)o->items + slice.start * sz, (slice.stop - slice.start) * sz); + } + return MP_OBJ_FROM_PTR(res); + } else +#endif + { + size_t index = mp_get_index(o->base.type, o->len, index_in, false); + if (index == (size_t)-1) { + return MP_OBJ_NULL; + } + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + index += o->memview_offset; + if (value != MP_OBJ_SENTINEL && !(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { + // store to read-only memoryview + return MP_OBJ_NULL; + } + } + #endif + if (value == MP_OBJ_SENTINEL) { + // load + return mp_binary_get_val_array(o->typecode & TYPECODE_MASK, o->items, index); + } else { + // store + mp_binary_set_val_array(o->typecode & TYPECODE_MASK, o->items, index, value); + return mp_const_none; + } + } + } +} + +STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + bufinfo->buf = o->items; + bufinfo->len = o->len * sz; + bufinfo->typecode = o->typecode & TYPECODE_MASK; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW) && (flags & MP_BUFFER_WRITE)) { + // read-only memoryview + return 1; + } + bufinfo->buf = (uint8_t *)bufinfo->buf + (size_t)o->memview_offset * sz; + } + #else + (void)flags; + #endif + return 0; +} + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC const mp_rom_map_elem_t array_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&array_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&array_extend_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(array_locals_dict, array_locals_dict_table); +#endif + +#if MICROPY_PY_ARRAY +const mp_obj_type_t mp_type_array = { + { &mp_type_type }, + .name = MP_QSTR_array, + .print = array_print, + .make_new = array_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&array_locals_dict, +}; +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY +const mp_obj_type_t mp_type_bytearray = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, + .name = MP_QSTR_bytearray, + .print = array_print, + .make_new = bytearray_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&array_locals_dict, +}; +#endif + +#if MICROPY_PY_BUILTINS_MEMORYVIEW +const mp_obj_type_t mp_type_memoryview = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, + .name = MP_QSTR_memoryview, + .make_new = memoryview_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + #if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE + .attr = memoryview_attr, + #endif + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, +}; +#endif + +/* unused +size_t mp_obj_array_len(mp_obj_t self_in) { + return ((mp_obj_array_t *)self_in)->len; +} +*/ + +#if MICROPY_PY_BUILTINS_BYTEARRAY +mp_obj_t mp_obj_new_bytearray(size_t n, void *items) { + mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, n); + memcpy(o->items, items, n); + return MP_OBJ_FROM_PTR(o); +} + +// Create bytearray which references specified memory area +mp_obj_t mp_obj_new_bytearray_by_ref(size_t n, void *items) { + mp_obj_array_t *o = m_new_obj(mp_obj_array_t); + o->base.type = &mp_type_bytearray; + o->typecode = BYTEARRAY_TYPECODE; + o->free = 0; + o->len = n; + o->items = items; + return MP_OBJ_FROM_PTR(o); +} +#endif + +/******************************************************************************/ +// array iterator + +typedef struct _mp_obj_array_it_t { + mp_obj_base_t base; + mp_obj_array_t *array; + size_t offset; + size_t cur; +} mp_obj_array_it_t; + +STATIC mp_obj_t array_it_iternext(mp_obj_t self_in) { + mp_obj_array_it_t *self = MP_OBJ_TO_PTR(self_in); + if (self->cur < self->array->len) { + return mp_binary_get_val_array(self->array->typecode & TYPECODE_MASK, self->array->items, self->offset + self->cur++); + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +STATIC const mp_obj_type_t array_it_type = { + { &mp_type_type }, + .name = MP_QSTR_iterator, + .getiter = mp_identity_getiter, + .iternext = array_it_iternext, +}; + +STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_array_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_array_t *array = MP_OBJ_TO_PTR(array_in); + mp_obj_array_it_t *o = (mp_obj_array_it_t *)iter_buf; + o->base.type = &array_it_type; + o->array = array; + o->offset = 0; + o->cur = 0; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (array->base.type == &mp_type_memoryview) { + o->offset = array->memview_offset; + } + #endif + return MP_OBJ_FROM_PTR(o); +} + +#endif // MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW diff --git a/ports/wapy/objdict_no_nlr.c b/ports/wapy/objdict_no_nlr.c new file mode 100644 index 000000000..4f1f4496a --- /dev/null +++ b/ports/wapy/objdict_no_nlr.c @@ -0,0 +1,657 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2017 Paul Sokolovsky + * + * 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/builtin.h" +#include "py/objtype.h" +#include "py/objstr.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 == mp_obj_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 +// the iteration is held in *cur and should be initialised with zero for the +// first call. Will return NULL when no more elements are available. +STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur) { + size_t max = dict->map.alloc; + mp_map_t *map = &dict->map; + + for (size_t i = *cur; i < max; i++) { + if (mp_map_slot_is_filled(map, i)) { + *cur = i + 1; + return &(map->table[i]); + } + } + + return NULL; +} + +STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + bool first = true; + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } + if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { + mp_printf(print, "%q(", self->base.type->name); + } + mp_print_str(print, "{"); + size_t cur = 0; + mp_map_elem_t *next = NULL; + while ((next = dict_iter_next(self, &cur)) != NULL) { + if (!first) { + mp_print_str(print, ", "); + } + first = false; + bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key); + if (add_quote) { + mp_print_str(print, "\""); + } + mp_obj_print_helper(print, next->key, kind); + if (add_quote) { + mp_print_str(print, "\""); + } + mp_print_str(print, ": "); + mp_obj_print_helper(print, next->value, kind); + } + mp_print_str(print, "}"); + if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { + mp_print_str(print, ")"); + } +} + +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_t dict_out = mp_obj_new_dict(0); + mp_obj_dict_t *dict = MP_OBJ_TO_PTR(dict_out); + dict->base.type = type; + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + if (type == &mp_type_ordereddict) { + dict->map.is_ordered = 1; + } + #endif + if (n_args > 0 || n_kw > 0) { + mp_obj_t args2[2] = {dict_out, args[0]}; // args[0] is always valid, even if it's not a positional arg + mp_map_t kwargs; + mp_map_init_fixed_table(&kwargs, n_kw, args + n_args); + if (dict_update(n_args + 1, args2, &kwargs) == MP_OBJ_NULL) { // dict_update will check that n_args + 1 == 1 or 2 + return MP_OBJ_NULL; + } + } + return dict_out; +} + +STATIC mp_obj_t dict_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(self->map.used != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(self->map.used); + #if MICROPY_PY_SYS_GETSIZEOF + case MP_UNARY_OP_SIZEOF: { + size_t sz = sizeof(*self) + sizeof(*self->map.table) * self->map.alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + mp_obj_dict_t *o = MP_OBJ_TO_PTR(lhs_in); + switch (op) { + case MP_BINARY_OP_CONTAINS: { + mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP); + return mp_obj_new_bool(elem != NULL); + } + case MP_BINARY_OP_EQUAL: { + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + if (MP_UNLIKELY(mp_obj_is_type(lhs_in, &mp_type_ordereddict) && mp_obj_is_type(rhs_in, &mp_type_ordereddict))) { + // Iterate through both dictionaries simultaneously and compare keys and values. + mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); + size_t c1 = 0, c2 = 0; + mp_map_elem_t *e1 = dict_iter_next(o, &c1), *e2 = dict_iter_next(rhs, &c2); + for (; e1 != NULL && e2 != NULL; e1 = dict_iter_next(o, &c1), e2 = dict_iter_next(rhs, &c2)) { + if (!mp_obj_equal(e1->key, e2->key) || !mp_obj_equal(e1->value, e2->value)) { + return mp_const_false; + } + } + return e1 == NULL && e2 == NULL ? mp_const_true : mp_const_false; + } + #endif + + if (mp_obj_is_type(rhs_in, &mp_type_dict)) { + mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); + if (o->map.used != rhs->map.used) { + return mp_const_false; + } + + size_t cur = 0; + mp_map_elem_t *next = NULL; + while ((next = dict_iter_next(o, &cur)) != NULL) { + mp_map_elem_t *elem = mp_map_lookup(&rhs->map, next->key, MP_MAP_LOOKUP); + if (elem == NULL || !mp_obj_equal(next->value, elem->value)) { + return mp_const_false; + } + } + return mp_const_true; + } else { + // dict is not equal to instance of any other type + return mp_const_false; + } + } + default: + // op not supported + return MP_OBJ_NULL; + } +} + +// Note: Make sure this is inlined in load part of dict_subscr() below. +mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); + if (elem == NULL) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, index)); + } else { + return elem->value; + } +} + +STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete + if (mp_obj_dict_delete(self_in, index) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; + } else if (value == MP_OBJ_SENTINEL) { + // load + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); + if (elem == NULL) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, index)); + } else { + return elem->value; + } + } else { + // store + if (mp_obj_dict_store(self_in, index, value) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; + } +} + +/******************************************************************************/ +/* dict methods */ + +STATIC int mp_ensure_not_fixed(const mp_obj_dict_t *dict) { + if (dict->map.is_fixed) { + mp_raise_TypeError_o(NULL); + return 1; + } + return 0; +} + +STATIC mp_obj_t dict_clear(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + + mp_map_clear(&self->map); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_clear_obj, dict_clear); + +mp_obj_t mp_obj_dict_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t other_out = mp_obj_new_dict(self->map.alloc); + mp_obj_dict_t *other = MP_OBJ_TO_PTR(other_out); + other->base.type = self->base.type; + other->map.used = self->map.used; + other->map.all_keys_are_qstrs = self->map.all_keys_are_qstrs; + other->map.is_fixed = 0; + other->map.is_ordered = self->map.is_ordered; + memcpy(other->map.table, self->map.table, self->map.alloc * sizeof(mp_map_elem_t)); + return other_out; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, mp_obj_dict_copy); + +#if MICROPY_PY_BUILTINS_DICT_FROMKEYS +// this is a classmethod +STATIC mp_obj_t dict_fromkeys(size_t n_args, const mp_obj_t *args) { + mp_obj_t iter = mp_getiter(args[1], NULL); + mp_obj_t value = mp_const_none; + mp_obj_t next = MP_OBJ_NULL; + + if (n_args > 2) { + value = args[2]; + } + + // optimisation to allocate result based on len of argument + mp_obj_t self_out; + mp_obj_t len = mp_obj_len_maybe(args[1]); + if (len == MP_OBJ_NULL) { + /* object's type doesn't have a __len__ slot */ + self_out = mp_obj_new_dict(0); + } else { + self_out = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len)); + } + + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_out); + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + } + + return self_out; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_fun_obj, 2, 3, dict_fromkeys); +STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, MP_ROM_PTR(&dict_fromkeys_fun_obj)); +#endif + +STATIC mp_obj_t dict_get_helper(size_t n_args, const mp_obj_t *args, mp_map_lookup_kind_t lookup_kind) { + mp_check_self(mp_obj_is_dict_type(args[0])); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); + if (lookup_kind != MP_MAP_LOOKUP) { + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + } + mp_map_elem_t *elem = mp_map_lookup(&self->map, args[1], lookup_kind); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) { + if (n_args == 2) { + if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, args[1])); + } else { + value = mp_const_none; + } + } else { + value = args[2]; + } + if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { + elem->value = value; + } + } else { + value = elem->value; + if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { + elem->value = MP_OBJ_NULL; // so that GC can collect the deleted value + } + } + return value; +} + +STATIC mp_obj_t dict_get(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_get_obj, 2, 3, dict_get); + +STATIC mp_obj_t dict_pop(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_pop_obj, 2, 3, dict_pop); + +STATIC mp_obj_t dict_setdefault(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_setdefault_obj, 2, 3, dict_setdefault); + +STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + 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); + if (next == NULL) { + mp_raise_msg(&mp_type_KeyError, "popitem(): dictionary is empty"); + } + self->map.used--; + mp_obj_t items[] = {next->key, next->value}; + next->key = MP_OBJ_SENTINEL; // must mark key as sentinel to indicate that it was deleted + next->value = MP_OBJ_NULL; + mp_obj_t tuple = mp_obj_new_tuple(2, items); + + return tuple; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_popitem_obj, dict_popitem); + +STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + mp_check_self(mp_obj_is_dict_type(args[0])); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + + if (mp_arg_check_num(n_args, kwargs->used, 1, 2, true)) { + return MP_OBJ_NULL; + } + + if (n_args == 2) { + // given a positional argument + + if (mp_obj_is_dict_type(args[1])) { + // update from other dictionary (make sure other is not self) + if (args[1] != args[0]) { + size_t cur = 0; + mp_map_elem_t *elem = NULL; + while ((elem = dict_iter_next((mp_obj_dict_t *)MP_OBJ_TO_PTR(args[1]), &cur)) != NULL) { + mp_map_lookup(&self->map, elem->key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = elem->value; + } + } + } else { + // update from a generic iterable of pairs + mp_obj_t iter = mp_getiter(args[1], NULL); + mp_obj_t next = MP_OBJ_NULL; + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_obj_t inneriter = mp_getiter(next, NULL); + mp_obj_t key = mp_iternext(inneriter); + mp_obj_t value = mp_iternext(inneriter); + mp_obj_t stop = mp_iternext(inneriter); + if (key == MP_OBJ_STOP_ITERATION + || value == MP_OBJ_STOP_ITERATION + || stop != MP_OBJ_STOP_ITERATION) { + return mp_raise_ValueError_o("dict update sequence has wrong length"); + } else { + mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + } + } + } + } + + // update the dict with any keyword args + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + mp_map_lookup(&self->map, kwargs->table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = kwargs->table[i].value; + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dict_update_obj, 1, dict_update); + + +/******************************************************************************/ +/* dict views */ + +STATIC const mp_obj_type_t dict_view_type; +STATIC const mp_obj_type_t dict_view_it_type; + +typedef enum _mp_dict_view_kind_t { + MP_DICT_VIEW_ITEMS, + MP_DICT_VIEW_KEYS, + MP_DICT_VIEW_VALUES, +} mp_dict_view_kind_t; + +STATIC const char *const mp_dict_view_names[] = {"dict_items", "dict_keys", "dict_values"}; + +typedef struct _mp_obj_dict_view_it_t { + mp_obj_base_t base; + mp_dict_view_kind_t kind; + mp_obj_t dict; + size_t cur; +} mp_obj_dict_view_it_t; + +typedef struct _mp_obj_dict_view_t { + mp_obj_base_t base; + mp_obj_t dict; + mp_dict_view_kind_t kind; +} mp_obj_dict_view_t; + +STATIC mp_obj_t dict_view_it_iternext(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &dict_view_it_type)); + mp_obj_dict_view_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *next = dict_iter_next(MP_OBJ_TO_PTR(self->dict), &self->cur); + + if (next == NULL) { + return MP_OBJ_STOP_ITERATION; + } else { + switch (self->kind) { + case MP_DICT_VIEW_ITEMS: + default: { + mp_obj_t items[] = {next->key, next->value}; + return mp_obj_new_tuple(2, items); + } + case MP_DICT_VIEW_KEYS: + return next->key; + case MP_DICT_VIEW_VALUES: + return next->value; + } + } +} + +STATIC const mp_obj_type_t dict_view_it_type = { + { &mp_type_type }, + .name = MP_QSTR_iterator, + .getiter = mp_identity_getiter, + .iternext = dict_view_it_iternext, +}; + +STATIC mp_obj_t dict_view_getiter(mp_obj_t view_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_check_self(mp_obj_is_type(view_in, &dict_view_type)); + mp_obj_dict_view_t *view = MP_OBJ_TO_PTR(view_in); + mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; + o->base.type = &dict_view_it_type; + o->kind = view->kind; + o->dict = view->dict; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +STATIC void dict_view_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_check_self(mp_obj_is_type(self_in, &dict_view_type)); + mp_obj_dict_view_t *self = MP_OBJ_TO_PTR(self_in); + bool first = true; + mp_print_str(print, mp_dict_view_names[self->kind]); + mp_print_str(print, "(["); + mp_obj_iter_buf_t iter_buf; + mp_obj_t self_iter = dict_view_getiter(self_in, &iter_buf); + mp_obj_t next = MP_OBJ_NULL; + while ((next = dict_view_it_iternext(self_iter)) != MP_OBJ_STOP_ITERATION) { + if (!first) { + mp_print_str(print, ", "); + } + first = false; + mp_obj_print_helper(print, next, PRINT_REPR); + } + mp_print_str(print, "])"); +} + +STATIC mp_obj_t dict_view_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // only supported for the 'keys' kind until sets and dicts are refactored + mp_obj_dict_view_t *o = MP_OBJ_TO_PTR(lhs_in); + if (o->kind != MP_DICT_VIEW_KEYS) { + return MP_OBJ_NULL; // op not supported + } + if (op != MP_BINARY_OP_CONTAINS) { + return MP_OBJ_NULL; // op not supported + } + return dict_binary_op(op, o->dict, rhs_in); +} + +STATIC const mp_obj_type_t dict_view_type = { + { &mp_type_type }, + .name = MP_QSTR_dict_view, + .print = dict_view_print, + .binary_op = dict_view_binary_op, + .getiter = dict_view_getiter, +}; + +STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { + mp_obj_dict_view_t *o = m_new_obj(mp_obj_dict_view_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = &dict_view_type; + o->dict = dict; + o->kind = kind; + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { + mp_check_self(mp_obj_is_dict_type(self_in)); + return mp_obj_new_dict_view(self_in, kind); +} + +STATIC mp_obj_t dict_items(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_ITEMS); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_items_obj, dict_items); + +STATIC mp_obj_t dict_keys(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_KEYS); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_keys_obj, dict_keys); + +STATIC mp_obj_t dict_values(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_VALUES); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); + +/******************************************************************************/ +/* dict iterator */ + +STATIC mp_obj_t dict_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_check_self(mp_obj_is_dict_type(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; + o->dict = self_in; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +/* dict constructors & public C API */ + +STATIC const mp_rom_map_elem_t dict_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&dict_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&dict_copy_obj) }, + #if MICROPY_PY_BUILTINS_DICT_FROMKEYS + { MP_ROM_QSTR(MP_QSTR_fromkeys), MP_ROM_PTR(&dict_fromkeys_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&dict_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_items), MP_ROM_PTR(&dict_items_obj) }, + { MP_ROM_QSTR(MP_QSTR_keys), MP_ROM_PTR(&dict_keys_obj) }, + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&dict_pop_obj) }, + { MP_ROM_QSTR(MP_QSTR_popitem), MP_ROM_PTR(&dict_popitem_obj) }, + { MP_ROM_QSTR(MP_QSTR_setdefault), MP_ROM_PTR(&dict_setdefault_obj) }, + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&dict_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_values), MP_ROM_PTR(&dict_values_obj) }, + { MP_ROM_QSTR(MP_QSTR___getitem__), MP_ROM_PTR(&mp_op_getitem_obj) }, + { MP_ROM_QSTR(MP_QSTR___setitem__), MP_ROM_PTR(&mp_op_setitem_obj) }, + { MP_ROM_QSTR(MP_QSTR___delitem__), MP_ROM_PTR(&mp_op_delitem_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(dict_locals_dict, dict_locals_dict_table); + +const mp_obj_type_t mp_type_dict = { + { &mp_type_type }, + .name = MP_QSTR_dict, + .print = dict_print, + .make_new = mp_obj_dict_make_new, + .unary_op = dict_unary_op, + .binary_op = dict_binary_op, + .subscr = dict_subscr, + .getiter = dict_getiter, + .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, +}; + +#if MICROPY_PY_COLLECTIONS_ORDEREDDICT +const mp_obj_type_t mp_type_ordereddict = { + { &mp_type_type }, + .name = MP_QSTR_OrderedDict, + .print = dict_print, + .make_new = mp_obj_dict_make_new, + .unary_op = dict_unary_op, + .binary_op = dict_binary_op, + .subscr = dict_subscr, + .getiter = dict_getiter, + .parent = &mp_type_dict, + .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, +}; +#endif + +void mp_obj_dict_init(mp_obj_dict_t *dict, size_t n_args) { + dict->base.type = &mp_type_dict; + mp_map_init(&dict->map, n_args); +} + +mp_obj_t mp_obj_new_dict(size_t n_args) { + mp_obj_dict_t *o = m_new_obj(mp_obj_dict_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + mp_obj_dict_init(o, n_args); + return MP_OBJ_FROM_PTR(o); +} + +size_t mp_obj_dict_len(mp_obj_t self_in) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + return self->map.used; +} + +mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + mp_map_elem_t *elem = mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (elem == NULL) { + // exception + return MP_OBJ_NULL; + } + elem->value = value; + return self_in; +} + +mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key) { + mp_obj_t args[2] = {self_in, key}; + if (dict_get_helper(2, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return self_in; +} diff --git a/ports/wapy/objfun_no_nlr.c b/ports/wapy/objfun_no_nlr.c new file mode 100644 index 000000000..32d28444e --- /dev/null +++ b/ports/wapy/objfun_no_nlr.c @@ -0,0 +1,673 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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/objtuple.h" +#include "py/objfun.h" +#include "py/runtime.h" +#include "py/bc.h" +#include "py/stackctrl.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +// Note: the "name" entry in mp_obj_type_t for a function type must be +// MP_QSTR_function because it is used to determine if an object is of generic +// function type. + +/******************************************************************************/ +/* builtin functions */ + +STATIC mp_obj_t fun_builtin_0_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)args; + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_0)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 0, 0, false)) { + return MP_OBJ_NULL; + } + return self->fun._0(); +} + +const mp_obj_type_t mp_type_fun_builtin_0 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_0_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_1)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 1, 1, false)) { + return MP_OBJ_NULL; + } + return self->fun._1(args[0]); +} + +const mp_obj_type_t mp_type_fun_builtin_1 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_1_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_2_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_2)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 2, 2, false)) { + return MP_OBJ_NULL; + } + return self->fun._2(args[0], args[1]); +} + +const mp_obj_type_t mp_type_fun_builtin_2 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_2_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_3_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_3)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 3, 3, false)) { + return MP_OBJ_NULL; + } + return self->fun._3(args[0], args[1], args[2]); +} + +const mp_obj_type_t mp_type_fun_builtin_3 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_3_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_var)); + mp_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in); + + // check number of arguments + if (mp_arg_check_num_sig(n_args, n_kw, self->sig)) { + return MP_OBJ_NULL; + } + + if (self->sig & 1) { + // function allows keywords + + // we create a map directly from the given args array + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + return self->fun.kw(n_args, args, &kw_args); + + } else { + // function takes a variable number of arguments, but no keywords + + return self->fun.var(n_args, args); + } +} + +const mp_obj_type_t mp_type_fun_builtin_var = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_var_call, + .unary_op = mp_generic_unary_op, +}; + +/******************************************************************************/ +/* byte code functions */ + +qstr mp_obj_code_get_name(const byte *code_info) { + MP_BC_PRELUDE_SIZE_DECODE(code_info); + #if MICROPY_PERSISTENT_CODE + return code_info[0] | (code_info[1] << 8); + #else + return mp_decode_uint_value(code_info); + #endif +} + +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM +STATIC const mp_obj_type_t mp_type_fun_native; +#endif + +qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { + const mp_obj_fun_bc_t *fun = MP_OBJ_TO_PTR(fun_in); + #if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM + #else + #error "MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM required" + #endif + if (fun->base.type == &mp_type_fun_native || fun->base.type == &mp_type_native_gen_wrap) { + #pragma message "// TODO native functions don't have name stored" + } else { + + if (fun->base.type == &mp_type_fun_bc ) { + const byte *bc = fun->bytecode; + MP_BC_PRELUDE_SIG_DECODE(bc); + #pragma message "REVIEW: can that one return a NULL ?" + return mp_obj_code_get_name(bc); + } + + #if MICROPY_PY_FUNCTION_ATTRS + +#define STRIP_PREFIX strlen("mp_builtin_") + //PMPP + if ( + (fun->base.type == &mp_type_fun_builtin_0) || + (fun->base.type == &mp_type_fun_builtin_1) || + (fun->base.type == &mp_type_fun_builtin_2) || + (fun->base.type == &mp_type_fun_builtin_3) + ) { + mp_obj_fun_builtin_fixed_t *fn = (mp_obj_fun_builtin_fixed_t *)fun; + return qstr_from_str( &fn->name[STRIP_PREFIX] ); + } + + if ( fun->base.type == &mp_type_fun_builtin_var) { + mp_obj_fun_builtin_var_t *fn = (mp_obj_fun_builtin_var_t *)fun; + return qstr_from_str( &fn->name[STRIP_PREFIX] ); + } + #endif + } + return MP_QSTR_; +} + +#if MICROPY_CPYTHON_COMPAT +STATIC void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(o_in); + mp_printf(print, "", mp_obj_fun_get_name(o_in), o); +} +#endif + +#if DEBUG_PRINT +STATIC void dump_args(const mp_obj_t *a, size_t sz) { + DEBUG_printf("%p: ", a); + for (size_t i = 0; i < sz; i++) { + DEBUG_printf("%p ", a[i]); + } + DEBUG_printf("\n"); +} +#else +#define dump_args(...) (void)0 +#endif + +// With this macro you can tune the maximum number of function state bytes +// that will be allocated on the stack. Any function that needs more +// than this will try to use the heap, with fallback to stack allocation. +#define VM_MAX_STATE_ON_STACK (sizeof(mp_uint_t) * 11) + +#define DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ + { \ + const uint8_t *ip = bytecode; \ + size_t n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args; \ + MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state_out_var, n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args); \ + \ + /* state size in bytes */ \ + state_size_out_var = n_state_out_var *sizeof(mp_obj_t) \ + + n_exc_stack *sizeof(mp_exc_stack_t); \ + } + +static inline mp_obj_t INIT_CODESTATE(mp_code_state_t *code_state, mp_obj_fun_bc_t *_fun_bc, size_t _n_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + code_state->fun_bc = _fun_bc; \ + code_state->ip = 0; \ + code_state->n_state = _n_state; \ + // TODO can we save old_globals before this call? + mp_obj_t ret = mp_setup_code_state(code_state, n_args, n_kw, args); \ + code_state->old_globals = mp_globals_get(); + return ret; +} + +#if MICROPY_STACKLESS +mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return NULL; + } + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + mp_code_state_t *code_state; + #if MICROPY_ENABLE_PYSTACK + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + #else + // If we use m_new_obj_var(), then on no memory, MemoryError will be + // raised. But this is not correct exception for a function call, + // RuntimeError should be raised instead. So, we use m_new_obj_var_maybe(), + // return NULL, then vm.c takes the needed action (either raise + // RuntimeError or fallback to stack allocation). + code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); + if (!code_state) { + return NULL; + } + #endif + + // TODO write test where this fails + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + #if MICROPY_ENABLE_PYSTACK + mp_nonlocal_free(code_state, sizeof(mp_code_state_t)); + #else + m_del_var(mp_code_state_t, byte, state_size, code_state); + #endif + // exception + return NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + + return code_state; +} +#endif + + +STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + + DEBUG_printf("Input n_args: " UINT_FMT ", n_kw: " UINT_FMT "\n", n_args, n_kw); + DEBUG_printf("Input pos args: "); + dump_args(args, n_args); + DEBUG_printf("Input kw args: "); + dump_args(args + n_args, n_kw * 2); + + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + // allocate state for locals and stack + mp_code_state_t *code_state = NULL; + #if MICROPY_ENABLE_PYSTACK + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + #else + if (state_size > VM_MAX_STATE_ON_STACK) { + code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + if (code_state != NULL) { + memset(code_state->state, 0, state_size); + } + #endif + } + if (code_state == NULL) { + code_state = alloca(sizeof(mp_code_state_t) + state_size); + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + memset(code_state->state, 0, state_size); + #endif + state_size = 0; // indicate that we allocated using alloca + } + #endif + + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + // exception + #if MICROPY_ENABLE_PYSTACK + mp_pystack_free(code_state); + #else + // free the state if it was allocated on the heap + if (state_size != 0) { + m_del_var(mp_code_state_t, byte, state_size, code_state); + } + #endif + return MP_OBJ_NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); + mp_globals_set(code_state->old_globals); + + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + if (code_state->sp < code_state->state) { + mp_printf(MICROPY_DEBUG_PRINTER, "VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state); + assert(0); + } + } + const byte *bytecode_ptr = self->bytecode; + size_t n_state_unused, n_exc_stack_unused, scope_flags_unused; + size_t n_pos_args, n_kwonly_args, n_def_args_unused; + MP_BC_PRELUDE_SIG_DECODE_INTO(bytecode_ptr, n_state_unused, n_exc_stack_unused, + scope_flags_unused, n_pos_args, n_kwonly_args, n_def_args_unused); + // We can't check the case when an exception is returned in state[0] + // and there are no arguments, because in this case our detection slot may have + // been overwritten by the returned exception (which is allowed). + if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && n_pos_args + n_kwonly_args == 0)) { + // Just check to see that we have at least 1 null object left in the state. + bool overflow = true; + for (size_t i = 0; i < n_state - n_pos_args - n_kwonly_args; ++i) { + if (code_state->state[i] == MP_OBJ_NULL) { + overflow = false; + break; + } + } + if (overflow) { + mp_printf(MICROPY_DEBUG_PRINTER, "VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state); + assert(0); + } + } + #endif + + mp_obj_t result; + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + // return value is in *sp + result = *code_state->sp; + } else { + // must be an exception because normal functions can't yield + assert(vm_return_kind == MP_VM_RETURN_EXCEPTION); + // returned exception is in state[0] + result = code_state->state[0]; + } + + #if MICROPY_ENABLE_PYSTACK + mp_pystack_free(code_state); + #else + // free the state if it was allocated on the heap + if (state_size != 0) { + m_del_var(mp_code_state_t, byte, state_size, code_state); + } + #endif + + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + return result; + } else { // MP_VM_RETURN_EXCEPTION + return mp_raise_o(result); + } +} + +#if MICROPY_PY_FUNCTION_ATTRS +void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // not load attribute + return; + } + if (attr == MP_QSTR___name__) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(self_in)); + } +} +#endif + +const mp_obj_type_t mp_type_fun_bc = { + { &mp_type_type }, + .name = MP_QSTR_function, + #if MICROPY_CPYTHON_COMPAT + .print = fun_bc_print, + #endif + .call = fun_bc_call, + .unary_op = mp_generic_unary_op, + #if MICROPY_PY_FUNCTION_ATTRS + .attr = mp_obj_fun_bc_attr, + #endif +}; + +mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code, const mp_uint_t *const_table) { + size_t n_def_args = 0; + size_t n_extra_args = 0; + mp_obj_tuple_t *def_args = MP_OBJ_TO_PTR(def_args_in); + if (def_args_in != MP_OBJ_NULL) { + assert(mp_obj_is_type(def_args_in, &mp_type_tuple)); + n_def_args = def_args->len; + n_extra_args = def_args->len; + } + if (def_kw_args != MP_OBJ_NULL) { + n_extra_args += 1; + } + mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = &mp_type_fun_bc; + o->globals = mp_globals_get(); + o->bytecode = code; + o->const_table = const_table; + if (def_args != NULL) { + memcpy(o->extra_args, def_args->items, n_def_args * sizeof(mp_obj_t)); + } + if (def_kw_args != MP_OBJ_NULL) { + o->extra_args[n_def_args] = def_kw_args; + } + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +/* native functions */ + +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM + +STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + mp_obj_fun_bc_t *self = self_in; + mp_call_fun_t fun = MICROPY_MAKE_POINTER_CALLABLE((void *)self->bytecode); + return fun(self_in, n_args, n_kw, args); +} + +STATIC const mp_obj_type_t mp_type_fun_native = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_native_call, + .unary_op = mp_generic_unary_op, +}; + +mp_obj_t mp_obj_new_fun_native(mp_obj_t def_args_in, mp_obj_t def_kw_args, const void *fun_data, const mp_uint_t *const_table) { + mp_obj_fun_bc_t *o = mp_obj_new_fun_bc(def_args_in, def_kw_args, (const byte *)fun_data, const_table); + o->base.type = &mp_type_fun_native; + return o; +} +#else + #if defined(__EMSCRIPTEN__) + #error "wasm : need MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM for detect native fun" + #endif +#endif // MICROPY_EMIT_NATIVE + +/******************************************************************************/ +/* inline assembler functions */ + +#if MICROPY_EMIT_INLINE_ASM + +typedef struct _mp_obj_fun_asm_t { + mp_obj_base_t base; + size_t n_args; + const void *fun_data; // GC must be able to trace this pointer + mp_uint_t type_sig; +} mp_obj_fun_asm_t; + +typedef mp_uint_t (*inline_asm_fun_0_t)(void); +typedef mp_uint_t (*inline_asm_fun_1_t)(mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_2_t)(mp_uint_t, mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_3_t)(mp_uint_t, mp_uint_t, mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_4_t)(mp_uint_t, mp_uint_t, mp_uint_t, mp_uint_t); + +// convert a MicroPython object to a sensible value for inline asm +STATIC mp_uint_t convert_obj_for_inline_asm(mp_obj_t obj) { + // TODO for byte_array, pass pointer to the array + if (mp_obj_is_small_int(obj)) { + return MP_OBJ_SMALL_INT_VALUE(obj); + } else if (obj == mp_const_none) { + return 0; + } else if (obj == mp_const_false) { + return 0; + } else if (obj == mp_const_true) { + return 1; + } else if (mp_obj_is_type(obj, &mp_type_int)) { + return mp_obj_int_get_truncated(obj); + } else if (mp_obj_is_str(obj)) { + // pointer to the string (it's probably constant though!) + size_t l; + return (mp_uint_t)mp_obj_str_get_data(obj, &l); + } else { + const mp_obj_type_t *type = mp_obj_get_type(obj); + #if MICROPY_PY_BUILTINS_FLOAT + if (type == &mp_type_float) { + // convert float to int (could also pass in float registers) + return (mp_int_t)mp_obj_float_get(obj); + } + #endif + if (type == &mp_type_tuple || type == &mp_type_list) { + // pointer to start of tuple (could pass length, but then could use len(x) for that) + size_t len; + mp_obj_t *items; + mp_obj_get_array(obj, &len, &items); + return (mp_uint_t)items; + } else { + mp_buffer_info_t bufinfo; + if (mp_get_buffer(obj, &bufinfo, MP_BUFFER_READ)) { + // supports the buffer protocol, return a pointer to the data + return (mp_uint_t)bufinfo.buf; + } else { + // just pass along a pointer to the object + return (mp_uint_t)obj; + } + } + } +} + +STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_fun_asm_t *self = self_in; + + mp_arg_check_num(n_args, n_kw, self->n_args, self->n_args, false); + + const void *fun = MICROPY_MAKE_POINTER_CALLABLE(self->fun_data); + + mp_uint_t ret; + if (n_args == 0) { + ret = ((inline_asm_fun_0_t)fun)(); + } else if (n_args == 1) { + ret = ((inline_asm_fun_1_t)fun)(convert_obj_for_inline_asm(args[0])); + } else if (n_args == 2) { + ret = ((inline_asm_fun_2_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1])); + } else if (n_args == 3) { + ret = ((inline_asm_fun_3_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1]), convert_obj_for_inline_asm(args[2])); + } else { + // compiler allows at most 4 arguments + assert(n_args == 4); + ret = ((inline_asm_fun_4_t)fun)( + convert_obj_for_inline_asm(args[0]), + convert_obj_for_inline_asm(args[1]), + convert_obj_for_inline_asm(args[2]), + convert_obj_for_inline_asm(args[3]) + ); + } + + return mp_native_to_obj(ret, self->type_sig); +} + +STATIC const mp_obj_type_t mp_type_fun_asm = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_asm_call, + .unary_op = mp_generic_unary_op, +}; + +mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) { + mp_obj_fun_asm_t *o = m_new_obj(mp_obj_fun_asm_t); + o->base.type = &mp_type_fun_asm; + o->n_args = n_args; + o->fun_data = fun_data; + o->type_sig = type_sig; + return o; +} + +#endif // MICROPY_EMIT_INLINE_ASM + + + + + + + + + + + + + + + +/* + +STATIC mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + // allocate state for locals and stack + mp_code_state_t *code_state = NULL; + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + // exception + mp_pystack_free(code_state); + return MP_OBJ_NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + return code_state; +} + +// mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); + + +STATIC mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + mp_globals_set(code_state->old_globals); + + mp_obj_t result; + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + // return value is in *sp + result = *code_state->sp; + } else { + // must be an exception because normal functions can't yield + assert(vm_return_kind == MP_VM_RETURN_EXCEPTION); + // returned exception is in state[0] + result = code_state->state[0]; + } + + mp_pystack_free(code_state); + + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + return result; + } else { // MP_VM_RETURN_EXCEPTION + return mp_raise_o(result); + } +} + +*/ + diff --git a/ports/wapy/objlist_no_nlr.c b/ports/wapy/objlist_no_nlr.c new file mode 100644 index 000000000..3a45863e5 --- /dev/null +++ b/ports/wapy/objlist_no_nlr.c @@ -0,0 +1,590 @@ +/* + * 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 + +#include "py/objlist.h" +#include "py/runtime.h" +#include "py/stackctrl.h" + +STATIC mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_list_t *list_new(size_t n); +STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in); +STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); + +// TODO: Move to mpconfig.h +#define LIST_MIN_ALLOC 4 + +/******************************************************************************/ +/* list */ + +STATIC void list_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + mp_obj_list_t *o = MP_OBJ_TO_PTR(o_in); + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } + mp_print_str(print, "["); + for (size_t i = 0; i < o->len; i++) { + if (i > 0) { + mp_print_str(print, ", "); + } + mp_obj_print_helper(print, o->items[i], kind); + } + mp_print_str(print, "]"); +} + +STATIC mp_obj_t list_extend_from_iter(mp_obj_t list, mp_obj_t iterable) { + mp_obj_t iter = mp_getiter(iterable, NULL); + mp_obj_t item; + while ((item = mp_iternext2(iter)) != MP_OBJ_NULL) { + if (mp_obj_list_append(list, item) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return list; +} + +STATIC mp_obj_t list_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + switch (n_args) { + case 0: + // return a new, empty list + return mp_obj_new_list(0, NULL); + + case 1: + default: { + // make list from iterable + // TODO: optimize list/tuple + mp_obj_t list = mp_obj_new_list(0, NULL); + return list_extend_from_iter(list, args[0]); + } + } +} + +STATIC mp_obj_t list_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(self->len != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(self->len); + #if MICROPY_PY_SYS_GETSIZEOF + case MP_UNARY_OP_SIZEOF: { + size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + default: + return MP_OBJ_NULL; // op not supported + } +} + +#include +STATIC mp_obj_t list_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + mp_obj_list_t *o = MP_OBJ_TO_PTR(lhs); + switch (op) { + case MP_BINARY_OP_ADD: { + if (!mp_obj_is_type(rhs, &mp_type_list)) { + return MP_OBJ_NULL; // op not supported + } + mp_obj_list_t *p = MP_OBJ_TO_PTR(rhs); + mp_obj_list_t *s = list_new(o->len + p->len); + mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); + return MP_OBJ_FROM_PTR(s); + } + case MP_BINARY_OP_INPLACE_ADD: { + list_extend(lhs, rhs); + return lhs; + } + case MP_BINARY_OP_MULTIPLY: { + mp_int_t n; + if (!mp_obj_get_int_maybe(rhs, &n)) { + return MP_OBJ_NULL; // op not supported + } + if (n < 0) { + n = 0; + } + mp_obj_list_t *s = list_new(o->len * n); + if (s == NULL) { + return MP_OBJ_NULL; + } + mp_seq_multiply(o->items, sizeof(*o->items), o->len, n, s->items); + return MP_OBJ_FROM_PTR(s); + } + case MP_BINARY_OP_EQUAL: + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: { + if (!mp_obj_is_type(rhs, &mp_type_list)) { + if (op == MP_BINARY_OP_EQUAL) { + return mp_const_false; + } + return MP_OBJ_NULL; // op not supported + } + + mp_obj_list_t *another = MP_OBJ_TO_PTR(rhs); + bool res = mp_seq_cmp_objs(op, o->items, o->len, another->items, another->len); + return mp_obj_new_bool(res); + } + + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + mp_bound_slice_t slice; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + mp_raise_NotImplementedError(NULL); + } + + mp_int_t len_adj = slice.start - slice.stop; + assert(len_adj <= 0); + mp_seq_replace_slice_no_grow(self->items, self->len, slice.start, slice.stop, self->items /*NULL*/, 0, sizeof(*self->items)); + // Clear "freed" elements at the end of list + mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); + self->len += len_adj; + return mp_const_none; + } + #endif + mp_obj_t args[2] = {self_in, index}; + list_pop(2, args); + return mp_const_none; + } else if (value == MP_OBJ_SENTINEL) { + // load + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_bound_slice_t slice; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + return mp_seq_extract_slice(self->len, self->items, &slice); + } + mp_obj_list_t *res = list_new(slice.stop - slice.start); + if (res == NULL) { + return MP_OBJ_NULL; + } + mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); + return MP_OBJ_FROM_PTR(res); + } + #endif + size_t index_val = mp_get_index(self->base.type, self->len, index, false); + if (index_val == (size_t)-1) { + // exception + return MP_OBJ_NULL; + } + return self->items[index_val]; + } else { + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + size_t value_len; mp_obj_t *value_items; + if (mp_obj_get_array(value, &value_len, &value_items)) { + return MP_OBJ_NULL; + } + mp_bound_slice_t slice_out; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice_out); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + mp_raise_NotImplementedError(NULL); + } + mp_int_t len_adj = value_len - (slice_out.stop - slice_out.start); + if (len_adj > 0) { + if (self->len + len_adj > self->alloc) { + // TODO: Might optimize memory copies here by checking if block can + // be grown inplace or not + mp_obj_t *new_items = m_renew(mp_obj_t, self->items, self->alloc, self->len + len_adj); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->items = new_items; + self->alloc = self->len + len_adj; + } + mp_seq_replace_slice_grow_inplace(self->items, self->len, + slice_out.start, slice_out.stop, value_items, value_len, len_adj, sizeof(*self->items)); + } else { + mp_seq_replace_slice_no_grow(self->items, self->len, + slice_out.start, slice_out.stop, value_items, value_len, sizeof(*self->items)); + // Clear "freed" elements at the end of list + mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); + // TODO: apply allocation policy re: alloc_size + } + self->len += len_adj; + return mp_const_none; + } + #endif + mp_obj_list_store(self_in, index, value); + return mp_const_none; + } +} + +STATIC mp_obj_t list_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return mp_obj_new_list_iterator(o_in, 0, iter_buf); +} + +mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + if (self->len >= self->alloc) { + mp_obj_t *items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc * 2); + if (items == NULL) { + return MP_OBJ_NULL; + } + self->items = items; + self->alloc *= 2; + mp_seq_clear(self->items, self->len + 1, self->alloc, sizeof(*self->items)); + } + self->items[self->len++] = arg; + return mp_const_none; // return None, as per CPython +} + +STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + if (mp_obj_is_type(arg_in, &mp_type_list)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *arg = MP_OBJ_TO_PTR(arg_in); + + if (self->len + arg->len > self->alloc) { + // TODO: use alloc policy for "4" + mp_obj_t *items = m_renew(mp_obj_t, self->items, self->alloc, self->len + arg->len + 4); + if (items == NULL) { + return MP_OBJ_NULL; + } + self->items = items; + self->alloc = self->len + arg->len + 4; + mp_seq_clear(self->items, self->len + arg->len, self->alloc, sizeof(*self->items)); + } + + memcpy(self->items + self->len, arg->items, sizeof(mp_obj_t) * arg->len); + self->len += arg->len; + } else { + if (list_extend_from_iter(self_in, arg_in) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + return mp_const_none; // return None, as per CPython +} + +STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->len == 0) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("pop from empty list")); + } + size_t index = mp_get_index(self->base.type, self->len, n_args == 1 ? MP_OBJ_NEW_SMALL_INT(-1) : args[1], false); + mp_obj_t ret = self->items[index]; + self->len -= 1; + memmove(self->items + index, self->items + index + 1, (self->len - index) * sizeof(mp_obj_t)); + // Clear stale pointer from slot which just got freed to prevent GC issues + self->items[self->len] = MP_OBJ_NULL; + if (self->alloc > LIST_MIN_ALLOC && self->alloc > 2 * self->len) { + self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc / 2); + self->alloc /= 2; + } + return ret; +} + +STATIC void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj_t binop_less_result) { + if (MP_STACK_CHECK()) { + // TODO propagate exception + return; + } + while (head < tail) { + mp_obj_t *h = head - 1; + mp_obj_t *t = tail; + mp_obj_t v = key_fn == MP_OBJ_NULL ? tail[0] : mp_call_function_1(key_fn, tail[0]); // get pivot using key_fn + for (;;) { + do {++h; + } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, key_fn == MP_OBJ_NULL ? h[0] : mp_call_function_1(key_fn, h[0]), v) == binop_less_result); + do {--t; + } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, v, key_fn == MP_OBJ_NULL ? t[0] : mp_call_function_1(key_fn, t[0])) == binop_less_result); + if (h >= t) { + break; + } + mp_obj_t x = h[0]; + h[0] = t[0]; + t[0] = x; + } + mp_obj_t x = h[0]; + h[0] = tail[0]; + tail[0] = x; + // do the smaller recursive call first, to keep stack within O(log(N)) + if (t - head < tail - h - 1) { + mp_quicksort(head, t, key_fn, binop_less_result); + head = h + 1; + } else { + mp_quicksort(h + 1, tail, key_fn, binop_less_result); + tail = t; + } + } +} + +// TODO Python defines sort to be stable but ours is not +mp_obj_t mp_obj_list_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_reverse, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + struct { + mp_arg_val_t key, reverse; + } args; + if (mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args)) { + return MP_OBJ_NULL; + } + + mp_check_self(mp_obj_is_type(pos_args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (self->len > 1) { + mp_quicksort(self->items, self->items + self->len - 1, + args.key.u_obj == mp_const_none ? MP_OBJ_NULL : args.key.u_obj, + args.reverse.u_bool ? mp_const_false : mp_const_true); + } + + return mp_const_none; +} + +STATIC mp_obj_t list_clear(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + self->len = 0; + self->items = m_renew(mp_obj_t, self->items, self->alloc, LIST_MIN_ALLOC); + self->alloc = LIST_MIN_ALLOC; + mp_seq_clear(self->items, 0, self->alloc, sizeof(*self->items)); + return mp_const_none; +} + +STATIC mp_obj_t list_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_list(self->len, self->items); +} + +STATIC mp_obj_t list_count(mp_obj_t self_in, mp_obj_t value) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + return mp_seq_count_obj(self->items, self->len, value); +} + +STATIC mp_obj_t list_index(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); + return mp_seq_index_obj(self->items, self->len, n_args, args); +} + +STATIC mp_obj_t list_insert(mp_obj_t self_in, mp_obj_t idx, mp_obj_t obj) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + // insert has its own strange index logic + mp_int_t index = MP_OBJ_SMALL_INT_VALUE(idx); + if (index < 0) { + index += self->len; + } + if (index < 0) { + index = 0; + } + if ((size_t)index > self->len) { + index = self->len; + } + + mp_obj_list_append(self_in, mp_const_none); + + for (mp_int_t i = self->len - 1; i > index; i--) { + self->items[i] = self->items[i - 1]; + } + self->items[index] = obj; + + return mp_const_none; +} + +mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_t args[] = {self_in, value}; + args[1] = list_index(2, args); + if (args[1] == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + list_pop(2, args); + + return mp_const_none; +} + +STATIC mp_obj_t list_reverse(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + + mp_int_t len = self->len; + for (mp_int_t i = 0; i < len / 2; i++) { + mp_obj_t a = self->items[i]; + self->items[i] = self->items[len - i - 1]; + self->items[len - i - 1] = a; + } + + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_append_obj, mp_obj_list_append); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_extend_obj, list_extend); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_clear_obj, list_clear); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_copy_obj, list_copy); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_count_obj, list_count); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_index_obj, 2, 4, list_index); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(list_insert_obj, list_insert); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_pop_obj, 1, 2, list_pop); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_remove_obj, mp_obj_list_remove); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_reverse_obj, list_reverse); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(list_sort_obj, 1, mp_obj_list_sort); + +STATIC const mp_rom_map_elem_t list_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&list_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&list_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&list_copy_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&list_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&list_extend_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&list_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_insert), MP_ROM_PTR(&list_insert_obj) }, + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&list_pop_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&list_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_reverse), MP_ROM_PTR(&list_reverse_obj) }, + { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&list_sort_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(list_locals_dict, list_locals_dict_table); + +const mp_obj_type_t mp_type_list = { + { &mp_type_type }, + .name = MP_QSTR_list, + .print = list_print, + .make_new = list_make_new, + .unary_op = list_unary_op, + .binary_op = list_binary_op, + .subscr = list_subscr, + .getiter = list_getiter, + .locals_dict = (mp_obj_dict_t *)&list_locals_dict, +}; + +mp_obj_list_t *mp_obj_list_init(mp_obj_list_t *o, size_t n) { + o->base.type = &mp_type_list; + o->alloc = n < LIST_MIN_ALLOC ? LIST_MIN_ALLOC : n; + o->len = n; + o->items = m_new(mp_obj_t, o->alloc); + if (o->items == NULL) { + return NULL; + } + mp_seq_clear(o->items, n, o->alloc, sizeof(*o->items)); + return o; +} + +STATIC mp_obj_list_t *list_new(size_t n) { + mp_obj_list_t *o = m_new_obj(mp_obj_list_t); + if (o == NULL) { + return NULL; + } + return mp_obj_list_init(o, n); +} + +mp_obj_t mp_obj_new_list(size_t n, mp_obj_t *items) { + mp_obj_list_t *o = list_new(n); + if (o != NULL && items != NULL) { + for (size_t i = 0; i < n; i++) { + o->items[i] = items[i]; + } + } + return MP_OBJ_FROM_PTR(o); +} + +void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + *len = self->len; + *items = self->items; +} + +void mp_obj_list_set_len(mp_obj_t self_in, size_t len) { + // trust that the caller knows what it's doing + // TODO realloc if len got much smaller than alloc + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + self->len = len; +} + +void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + size_t i = mp_get_index(self->base.type, self->len, index, false); + self->items[i] = value; +} + +/******************************************************************************/ +/* list iterator */ + +typedef struct _mp_obj_list_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t list; + size_t cur; +} mp_obj_list_it_t; + +STATIC mp_obj_t list_it_iternext(mp_obj_t self_in) { + mp_obj_list_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + if (self->cur < list->len) { + mp_obj_t o_out = list->items[self->cur]; + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_list_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_list_it_t *o = (mp_obj_list_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = list_it_iternext; + o->list = list; + o->cur = cur; + return MP_OBJ_FROM_PTR(o); +} diff --git a/ports/wapy/objstr_no_nlr.c b/ports/wapy/objstr_no_nlr.c new file mode 100644 index 000000000..577ae9f59 --- /dev/null +++ b/ports/wapy/objstr_no_nlr.c @@ -0,0 +1,2319 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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/unicode.h" +#include "py/objstr.h" +#include "py/objlist.h" +#include "py/runtime.h" +#include "py/stackctrl.h" + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict); +#endif + +STATIC mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_t bad_implicit_conversion(mp_obj_t self_in); + +/******************************************************************************/ +/* str */ + +void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, size_t str_len, bool is_bytes) { + // this escapes characters, but it will be very slow to print (calling print many times) + bool has_single_quote = false; + bool has_double_quote = false; + for (const byte *s = str_data, *top = str_data + str_len; !has_double_quote && s < top; s++) { + if (*s == '\'') { + has_single_quote = true; + } else if (*s == '"') { + has_double_quote = true; + } + } + int quote_char = '\''; + if (has_single_quote && !has_double_quote) { + quote_char = '"'; + } + mp_printf(print, "%c", quote_char); + for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { + if (*s == quote_char) { + mp_printf(print, "\\%c", quote_char); + } else if (*s == '\\') { + mp_print_str(print, "\\\\"); + } else if (*s >= 0x20 && *s != 0x7f && (!is_bytes || *s < 0x80)) { + // In strings, anything which is not ascii control character + // is printed as is, this includes characters in range 0x80-0xff + // (which can be non-Latin letters, etc.) + mp_printf(print, "%c", *s); + } else if (*s == '\n') { + mp_print_str(print, "\\n"); + } else if (*s == '\r') { + mp_print_str(print, "\\r"); + } else if (*s == '\t') { + mp_print_str(print, "\\t"); + } else { + mp_printf(print, "\\x%02x", *s); + } + } + mp_printf(print, "%c", quote_char); +} + +#if MICROPY_PY_UJSON +void mp_str_print_json(const mp_print_t *print, const byte *str_data, size_t str_len) { + // for JSON spec, see http://www.ietf.org/rfc/rfc4627.txt + // if we are given a valid utf8-encoded string, we will print it in a JSON-conforming way + mp_print_str(print, "\""); + for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { + if (*s == '"' || *s == '\\') { + mp_printf(print, "\\%c", *s); + } else if (*s >= 32) { + // this will handle normal and utf-8 encoded chars + mp_printf(print, "%c", *s); + } else if (*s == '\n') { + mp_print_str(print, "\\n"); + } else if (*s == '\r') { + mp_print_str(print, "\\r"); + } else if (*s == '\t') { + mp_print_str(print, "\\t"); + } else { + // this will handle control chars + mp_printf(print, "\\u%04x", *s); + } + } + mp_print_str(print, "\""); +} +#endif + +STATIC void str_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + GET_STR_DATA_LEN(self_in, str_data, str_len); + #if MICROPY_PY_UJSON + if (kind == PRINT_JSON) { + mp_str_print_json(print, str_data, str_len); + return; + } + #endif + #if !MICROPY_PY_BUILTINS_STR_UNICODE + bool is_bytes = mp_obj_is_type(self_in, &mp_type_bytes); + #else + bool is_bytes = true; + #endif + if (kind == PRINT_RAW || (!MICROPY_PY_BUILTINS_STR_UNICODE && kind == PRINT_STR && !is_bytes)) { + mp_printf(print, "%.*s", str_len, str_data); + } else { + if (is_bytes) { + mp_print_str(print, "b"); + } + mp_str_print_quoted(print, str_data, str_len, is_bytes); + } +} +#include +mp_obj_t mp_obj_str_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + #if MICROPY_CPYTHON_COMPAT + if (n_kw != 0) { + mp_arg_error_unimpl_kw(); + } + #endif + + if (mp_arg_check_num(n_args, n_kw, 0, 3, false)) { + return MP_OBJ_NULL; + } + + switch (n_args) { + case 0: + return MP_OBJ_NEW_QSTR(MP_QSTR_); + + case 1: { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, args[0], PRINT_STR); + return mp_obj_new_str_from_vstr(type, &vstr); + } + + default: // 2 or 3 args +#pragma message "TODO: validate 2nd/3rd args" + if (mp_obj_is_type(args[0], &mp_type_bytes)) { + GET_STR_DATA_LEN(args[0], str_data, str_len); + GET_STR_HASH(args[0], str_hash); + if (str_hash == 0) { + str_hash = qstr_compute_hash(str_data, str_len); + } + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + if (!utf8_check(str_data, str_len)) { + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif + + // Check if a qstr with this data already exists + qstr q = qstr_find_strn((const char *)str_data, str_len); + if (q != MP_QSTRnull) { + return MP_OBJ_NEW_QSTR(q); + } + + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(type, NULL, str_len)); + o->data = str_data; + o->hash = str_hash; + return MP_OBJ_FROM_PTR(o); + } else { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + if (!utf8_check(bufinfo.buf, bufinfo.len)) { + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif + return mp_obj_new_str(bufinfo.buf, bufinfo.len); + } + } +} + +STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + #if MICROPY_CPYTHON_COMPAT + if (n_kw != 0) { + mp_arg_error_unimpl_kw(); + } + #else + (void)n_kw; + #endif + + if (n_args == 0) { + return mp_const_empty_bytes; + } + + if (mp_obj_is_str(args[0])) { + if (n_args < 2 || n_args > 3) { + goto wrong_args; + } + GET_STR_DATA_LEN(args[0], str_data, str_len); + GET_STR_HASH(args[0], str_hash); + if (str_hash == 0) { + str_hash = qstr_compute_hash(str_data, str_len); + } + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(&mp_type_bytes, NULL, str_len)); + o->data = str_data; + o->hash = str_hash; + return MP_OBJ_FROM_PTR(o); + } + + if (n_args > 1) { + goto wrong_args; + } + + if (mp_obj_is_small_int(args[0])) { + mp_int_t len = MP_OBJ_SMALL_INT_VALUE(args[0]); + if (len < 0) { + mp_raise_ValueError(NULL); + } + vstr_t vstr; + vstr_init_len(&vstr, len); + memset(vstr.buf, 0, len); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); + } + + // check if argument has the buffer protocol + mp_buffer_info_t bufinfo; + if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) { + return mp_obj_new_bytes(bufinfo.buf, bufinfo.len); + } + + vstr_t vstr; + // Try to create array of exact len if initializer len is known + mp_obj_t len_in = mp_obj_len_maybe(args[0]); + if (len_in == MP_OBJ_NULL) { + vstr_init(&vstr, 16); + } else { + mp_int_t len = MP_OBJ_SMALL_INT_VALUE(len_in); + vstr_init(&vstr, len); + } + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + mp_int_t val = mp_obj_get_int(item); + #if MICROPY_FULL_CHECKS + if (val < 0 || val > 255) { + mp_raise_ValueError("bytes value out of range"); + } + #endif + vstr_add_byte(&vstr, val); + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); + +wrong_args: + mp_raise_TypeError(MP_ERROR_TEXT("wrong number of arguments")); +} + +// like strstr but with specified length and allows \0 bytes +// TODO replace with something more efficient/standard +const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction) { + if (hlen >= nlen) { + size_t str_index, str_index_end; + if (direction > 0) { + str_index = 0; + str_index_end = hlen - nlen; + } else { + str_index = hlen - nlen; + str_index_end = 0; + } + for (;;) { + if (memcmp(&haystack[str_index], needle, nlen) == 0) { + //found + return haystack + str_index; + } + if (str_index == str_index_end) { + //not found + break; + } + str_index += direction; + } + } + return NULL; +} + +// Note: this function is used to check if an object is a str or bytes, which +// works because both those types use it as their binary_op method. Revisit +// mp_obj_is_str_or_bytes if this fact changes. +mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // check for modulo + if (op == MP_BINARY_OP_MODULO) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + mp_obj_t *args = &rhs_in; + size_t n_args = 1; + mp_obj_t dict = MP_OBJ_NULL; + if (mp_obj_is_type(rhs_in, &mp_type_tuple)) { + // TODO: Support tuple subclasses? + mp_obj_tuple_get(rhs_in, &n_args, &args); + } else if (mp_obj_is_type(rhs_in, &mp_type_dict)) { + dict = rhs_in; + } + return str_modulo_format(lhs_in, n_args, args, dict); + #else + return MP_OBJ_NULL; + #endif + } + + // from now on we need lhs type and data, so extract them + const mp_obj_type_t *lhs_type = mp_obj_get_type(lhs_in); + GET_STR_DATA_LEN(lhs_in, lhs_data, lhs_len); + + // check for multiply + if (op == MP_BINARY_OP_MULTIPLY) { + mp_int_t n; + if (!mp_obj_get_int_maybe(rhs_in, &n)) { + return MP_OBJ_NULL; // op not supported + } + if (n <= 0) { + if (lhs_type == &mp_type_str) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); // empty str + } else { + return mp_const_empty_bytes; + } + } + vstr_t vstr; + vstr_init_len(&vstr, lhs_len * n); + mp_seq_multiply(lhs_data, sizeof(*lhs_data), lhs_len, n, vstr.buf); + return mp_obj_new_str_from_vstr(lhs_type, &vstr); + } + + // From now on all operations allow: + // - str with str + // - bytes with bytes + // - bytes with bytearray + // - bytes with array.array + // To do this efficiently we use the buffer protocol to extract the raw + // data for the rhs, but only if the lhs is a bytes object. + // + // NOTE: CPython does not allow comparison between bytes ard array.array + // (even if the array is of type 'b'), even though it allows addition of + // such types. We are not compatible with this (we do allow comparison + // of bytes with anything that has the buffer protocol). It would be + // easy to "fix" this with a bit of extra logic below, but it costs code + // size and execution time so we don't. + + const byte *rhs_data; + size_t rhs_len; + if (lhs_type == mp_obj_get_type(rhs_in)) { + GET_STR_DATA_LEN(rhs_in, rhs_data_, rhs_len_); + rhs_data = rhs_data_; + rhs_len = rhs_len_; + } else if (lhs_type == &mp_type_bytes) { + mp_buffer_info_t bufinfo; + if (!mp_get_buffer(rhs_in, &bufinfo, MP_BUFFER_READ)) { + return MP_OBJ_NULL; // op not supported + } + rhs_data = bufinfo.buf; + rhs_len = bufinfo.len; + } else { + // LHS is str and RHS has an incompatible type + // (except if operation is EQUAL, but that's handled by mp_obj_equal) + return bad_implicit_conversion(rhs_in); + } + + switch (op) { + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_INPLACE_ADD: { + if (lhs_len == 0 && mp_obj_get_type(rhs_in) == lhs_type) { + return rhs_in; + } + if (rhs_len == 0) { + return lhs_in; + } + + vstr_t vstr; + vstr_init_len(&vstr, lhs_len + rhs_len); + memcpy(vstr.buf, lhs_data, lhs_len); + memcpy(vstr.buf + lhs_len, rhs_data, rhs_len); + return mp_obj_new_str_from_vstr(lhs_type, &vstr); + } + + case MP_BINARY_OP_CONTAINS: + return mp_obj_new_bool(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL); + + //case MP_BINARY_OP_NOT_EQUAL: // This is never passed here + case MP_BINARY_OP_EQUAL: // This will be passed only for bytes, str is dealt with in mp_obj_equal() + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: + return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_data, lhs_len, rhs_data, rhs_len)); + + default: + return MP_OBJ_NULL; // op not supported + } +} + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +// objstrunicode defines own version +const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, size_t self_len, + mp_obj_t index, bool is_slice) { + size_t index_val = mp_get_index(type, self_len, index, is_slice); + return self_data + index_val; +} +#endif + +// This is used for both bytes and 8-bit strings. This is not used for unicode strings. +STATIC mp_obj_t bytes_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + const mp_obj_type_t *type = mp_obj_get_type(self_in); + GET_STR_DATA_LEN(self_in, self_data, self_len); + if (value == MP_OBJ_SENTINEL) { + // load + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self_len, index, &slice)) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + return mp_obj_new_str_of_type(type, self_data + slice.start, slice.stop - slice.start); + } + #endif + size_t index_val = mp_get_index(type, self_len, index, false); + if (index_val == (size_t)-1) { + return MP_OBJ_NULL; // exception + } + // If we have unicode enabled the type will always be bytes, so take the short cut. + if (MICROPY_PY_BUILTINS_STR_UNICODE || type == &mp_type_bytes) { + return MP_OBJ_NEW_SMALL_INT(self_data[index_val]); + } else { + return mp_obj_new_str_via_qstr((char *)&self_data[index_val], 1); + } + } else { + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) { + mp_check_self(mp_obj_is_str_or_bytes(self_in)); + const mp_obj_type_t *self_type = mp_obj_get_type(self_in); + + // get separation string + GET_STR_DATA_LEN(self_in, sep_str, sep_len); + + // process args + size_t seq_len; + mp_obj_t *seq_items; + + if (!mp_obj_is_type(arg, &mp_type_list) && !mp_obj_is_type(arg, &mp_type_tuple)) { + // arg is not a list nor a tuple, try to convert it to a list + // TODO: Try to optimize? + arg = mp_type_list.make_new(&mp_type_list, 1, 0, &arg); + if (arg == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + mp_obj_get_array(arg, &seq_len, &seq_items); + + // count required length + size_t required_len = 0; + for (size_t i = 0; i < seq_len; i++) { + if (mp_obj_get_type(seq_items[i]) != self_type) { + mp_raise_TypeError( + MP_ERROR_TEXT("join expects a list of str/bytes objects consistent with self object")); + } + if (i > 0) { + required_len += sep_len; + } + GET_STR_LEN(seq_items[i], l); + required_len += l; + } + + // make joined string + vstr_t vstr; + vstr_init_len(&vstr, required_len); + byte *data = (byte *)vstr.buf; + for (size_t i = 0; i < seq_len; i++) { + if (i > 0) { + memcpy(data, sep_str, sep_len); + data += sep_len; + } + GET_STR_DATA_LEN(seq_items[i], s, l); + memcpy(data, s, l); + data += l; + } + + // return joined string + return mp_obj_new_str_from_vstr(self_type, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_join_obj, str_join); + +mp_obj_t mp_obj_str_split(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_int_t splits = -1; + mp_obj_t sep = mp_const_none; + if (n_args > 1) { + sep = args[1]; + if (n_args > 2) { + splits = mp_obj_get_int(args[2]); + } + } + + mp_obj_t res = mp_obj_new_list(0, NULL); + GET_STR_DATA_LEN(args[0], s, len); + const byte *top = s + len; + + if (sep == mp_const_none) { + // sep not given, so separate on whitespace + + // Initial whitespace is not counted as split, so we pre-do it + while (s < top && unichar_isspace(*s)) { + s++; + } + while (s < top && splits != 0) { + const byte *start = s; + while (s < top && !unichar_isspace(*s)) { + s++; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); + if (s >= top) { + break; + } + while (s < top && unichar_isspace(*s)) { + s++; + } + if (splits > 0) { + splits--; + } + } + + if (s < top) { + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, s, top - s)); + } + + } else { + // sep given + if (mp_obj_get_type(sep) != self_type) { + return bad_implicit_conversion(sep); + } + + size_t sep_len; + const char *sep_str = mp_obj_str_get_data(sep, &sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + for (;;) { + const byte *start = s; + for (;;) { + if (splits == 0 || s + sep_len > top) { + s = top; + break; + } else if (memcmp(s, sep_str, sep_len) == 0) { + break; + } + s++; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); + if (s >= top) { + break; + } + s += sep_len; + if (splits > 0) { + splits--; + } + } + } + + return res; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_split_obj, 1, 3, mp_obj_str_split); + +#if MICROPY_PY_BUILTINS_STR_SPLITLINES +STATIC mp_obj_t str_splitlines(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_keepends }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_keepends, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mp_obj_type_t *self_type = mp_obj_get_type(pos_args[0]); + mp_obj_t res = mp_obj_new_list(0, NULL); + + GET_STR_DATA_LEN(pos_args[0], s, len); + const byte *top = s + len; + + while (s < top) { + const byte *start = s; + size_t match = 0; + while (s < top) { + if (*s == '\n') { + match = 1; + break; + } else if (*s == '\r') { + if (s[1] == '\n') { + match = 2; + } else { + match = 1; + } + break; + } + s++; + } + size_t sub_len = s - start; + if (args[ARG_keepends].u_bool) { + sub_len += match; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, sub_len)); + s += match; + } + + return res; +} +MP_DEFINE_CONST_FUN_OBJ_KW(str_splitlines_obj, 1, str_splitlines); +#endif + +STATIC mp_obj_t str_rsplit(size_t n_args, const mp_obj_t *args) { + if (n_args < 3) { + // If we don't have split limit, it doesn't matter from which side + // we split. + return mp_obj_str_split(n_args, args); + } + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_obj_t sep = args[1]; + GET_STR_DATA_LEN(args[0], s, len); + + mp_int_t splits = mp_obj_get_int(args[2]); + if (splits < 0) { + // Negative limit means no limit, so delegate to split(). + return mp_obj_str_split(n_args, args); + } + + mp_int_t org_splits = splits; + // Preallocate list to the max expected # of elements, as we + // will fill it from the end. + mp_obj_list_t *res = MP_OBJ_TO_PTR(mp_obj_new_list(splits + 1, NULL)); + mp_int_t idx = splits; + + if (sep == mp_const_none) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("rsplit(None,n)")); + } else { + size_t sep_len; + const char *sep_str = mp_obj_str_get_data(sep, &sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + const byte *beg = s; + const byte *last = s + len; + for (;;) { + s = last - sep_len; + for (;;) { + if (splits == 0 || s < beg) { + break; + } else if (memcmp(s, sep_str, sep_len) == 0) { + break; + } + s--; + } + if (s < beg || splits == 0) { + res->items[idx] = mp_obj_new_str_of_type(self_type, beg, last - beg); + break; + } + res->items[idx--] = mp_obj_new_str_of_type(self_type, s + sep_len, last - s - sep_len); + last = s; + splits--; + } + if (idx != 0) { + // We split less parts than split limit, now go cleanup surplus + size_t used = org_splits + 1 - idx; + memmove(res->items, &res->items[idx], used * sizeof(mp_obj_t)); + mp_seq_clear(res->items, used, res->alloc, sizeof(*res->items)); + res->len = used; + } + } + + return MP_OBJ_FROM_PTR(res); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rsplit_obj, 1, 3, str_rsplit); + +STATIC mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, bool is_index) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + // check argument type + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + GET_STR_DATA_LEN(args[0], haystack, haystack_len); + GET_STR_DATA_LEN(args[1], needle, needle_len); + + const byte *start = haystack; + const byte *end = haystack + haystack_len; + if (n_args >= 3 && args[2] != mp_const_none) { + start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); + } + if (n_args >= 4 && args[3] != mp_const_none) { + end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); + } + + if (end < start) { + goto out_error; + } + + const byte *p = find_subbytes(start, end - start, needle, needle_len, direction); + if (p == NULL) { + out_error: + // not found + if (is_index) { + mp_raise_ValueError(MP_ERROR_TEXT("substring not found")); + } else { + return MP_OBJ_NEW_SMALL_INT(-1); + } + } else { + // found + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (self_type == &mp_type_str) { + return MP_OBJ_NEW_SMALL_INT(utf8_ptr_to_index(haystack, p)); + } + #endif + return MP_OBJ_NEW_SMALL_INT(p - haystack); + } +} + +STATIC mp_obj_t str_find(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, 1, false); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_find_obj, 2, 4, str_find); + +STATIC mp_obj_t str_rfind(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, -1, false); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rfind_obj, 2, 4, str_rfind); + +STATIC mp_obj_t str_index(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, 1, true); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_index_obj, 2, 4, str_index); + +STATIC mp_obj_t str_rindex(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, -1, true); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rindex_obj, 2, 4, str_rindex); + +// TODO: (Much) more variety in args +STATIC mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + GET_STR_DATA_LEN(args[0], str, str_len); + size_t prefix_len; + const char *prefix = mp_obj_str_get_data(args[1], &prefix_len); + if (prefix == NULL) { + // exception + return MP_OBJ_NULL; + } + const byte *start = str; + if (n_args > 2) { + start = str_index_to_ptr(self_type, str, str_len, args[2], true); + } + if (prefix_len + (start - str) > str_len) { + return mp_const_false; + } + return mp_obj_new_bool(memcmp(start, prefix, prefix_len) == 0); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 3, str_startswith); + +STATIC mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { + GET_STR_DATA_LEN(args[0], str, str_len); + size_t suffix_len; + const char *suffix = mp_obj_str_get_data(args[1], &suffix_len); + if (suffix == NULL) { + return MP_OBJ_NULL; + } + if (n_args > 2) { + return mp_raise_NotImplementedError_o("start/end indices"); + } + + if (suffix_len > str_len) { + return mp_const_false; + } + return mp_obj_new_bool(memcmp(str + (str_len - suffix_len), suffix, suffix_len) == 0); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 3, str_endswith); + +enum { LSTRIP, RSTRIP, STRIP }; + +STATIC mp_obj_t str_uni_strip(int type, size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + + const byte *chars_to_del; + uint chars_to_del_len; + static const byte whitespace[] = " \t\n\r\v\f"; + + if (n_args == 1) { + chars_to_del = whitespace; + chars_to_del_len = sizeof(whitespace) - 1; + } else { + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + GET_STR_DATA_LEN(args[1], s, l); + chars_to_del = s; + chars_to_del_len = l; + } + + GET_STR_DATA_LEN(args[0], orig_str, orig_str_len); + + size_t first_good_char_pos = 0; + bool first_good_char_pos_set = false; + size_t last_good_char_pos = 0; + size_t i = 0; + int delta = 1; + if (type == RSTRIP) { + i = orig_str_len - 1; + delta = -1; + } + for (size_t len = orig_str_len; len > 0; len--) { + if (find_subbytes(chars_to_del, chars_to_del_len, &orig_str[i], 1, 1) == NULL) { + if (!first_good_char_pos_set) { + first_good_char_pos_set = true; + first_good_char_pos = i; + if (type == LSTRIP) { + last_good_char_pos = orig_str_len - 1; + break; + } else if (type == RSTRIP) { + first_good_char_pos = 0; + last_good_char_pos = i; + break; + } + } + last_good_char_pos = i; + } + i += delta; + } + + if (!first_good_char_pos_set) { + // string is all whitespace, return '' + if (self_type == &mp_type_str) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } else { + return mp_const_empty_bytes; + } + } + + assert(last_good_char_pos >= first_good_char_pos); + // +1 to accommodate the last character + size_t stripped_len = last_good_char_pos - first_good_char_pos + 1; + if (stripped_len == orig_str_len) { + // If nothing was stripped, don't bother to dup original string + // TODO: watch out for this case when we'll get to bytearray.strip() + assert(first_good_char_pos == 0); + return args[0]; + } + return mp_obj_new_str_of_type(self_type, orig_str + first_good_char_pos, stripped_len); +} + +STATIC mp_obj_t str_strip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(STRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_strip_obj, 1, 2, str_strip); + +STATIC mp_obj_t str_lstrip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(LSTRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_lstrip_obj, 1, 2, str_lstrip); + +STATIC mp_obj_t str_rstrip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(RSTRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rstrip_obj, 1, 2, str_rstrip); + +#if MICROPY_PY_BUILTINS_STR_CENTER +STATIC mp_obj_t str_center(mp_obj_t str_in, mp_obj_t width_in) { + GET_STR_DATA_LEN(str_in, str, str_len); + mp_uint_t width = mp_obj_get_int(width_in); + if (str_len >= width) { + return str_in; + } + + vstr_t vstr; + vstr_init_len(&vstr, width); + memset(vstr.buf, ' ', width); + int left = (width - str_len) / 2; + memcpy(vstr.buf + left, str, str_len); + return mp_obj_new_str_from_vstr(mp_obj_get_type(str_in), &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_center_obj, str_center); +#endif + +// Takes an int arg, but only parses unsigned numbers, and only changes +// *num if at least one digit was parsed. +STATIC const char *str_to_int(const char *str, const char *top, int *num) { + if (str < top && '0' <= *str && *str <= '9') { + *num = 0; + do { + *num = *num * 10 + (*str - '0'); + str++; + } + while (str < top && '0' <= *str && *str <= '9'); + } + return str; +} + +STATIC bool isalignment(char ch) { + return ch && strchr("<>=^", ch) != NULL; +} + +STATIC bool istype(char ch) { + return ch && strchr("bcdeEfFgGnosxX%", ch) != NULL; +} + +STATIC bool arg_looks_integer(mp_obj_t arg) { + return mp_obj_is_bool(arg) || mp_obj_is_int(arg); +} + +STATIC bool arg_looks_numeric(mp_obj_t arg) { + return arg_looks_integer(arg) + #if MICROPY_PY_BUILTINS_FLOAT + || mp_obj_is_float(arg) + #endif + ; +} + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t arg_as_int(mp_obj_t arg) { + #if MICROPY_PY_BUILTINS_FLOAT + if (mp_obj_is_float(arg)) { + return mp_obj_new_int_from_float(mp_obj_float_get(arg)); + } + #endif + return arg; +} +#endif + +#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE +STATIC mp_obj_t terse_str_format_value_error(void) { + mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); +} +#else +// define to nothing to improve coverage +/* +static inline mp_obj_t terse_str_format_value_error(void) { + return MP_OBJ_NULL; +}*/ +#endif + +STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *arg_i, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + + for (; str < top; str++) { + if (*str == '}') { + str++; + if (str < top && *str == '}') { + vstr_add_byte(&vstr, '}'); + continue; + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("single '}' encountered in format string"))); + vstr.buf = NULL; + return vstr; + #endif + } + if (*str != '{') { + vstr_add_byte(&vstr, *str); + continue; + } + + str++; + if (str < top && *str == '{') { + vstr_add_byte(&vstr, '{'); + continue; + } + + // replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}" + + const char *field_name = NULL; + const char *field_name_top = NULL; + char conversion = '\0'; + const char *format_spec = NULL; + + if (str < top && *str != '}' && *str != '!' && *str != ':') { + field_name = (const char *)str; + while (str < top && *str != '}' && *str != '!' && *str != ':') { + ++str; + } + field_name_top = (const char *)str; + } + + // conversion ::= "r" | "s" + + if (str < top && *str == '!') { + str++; + if (str < top && (*str == 'r' || *str == 's')) { + conversion = *str++; + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL + mp_raise_ValueError_o(MP_ERROR_TEXT("bad conversion specifier")); + #else + + if (str >= top) { + mp_raise_ValueError_o( + MP_ERROR_TEXT("end of format while looking for conversion specifier")); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown conversion specifier %c"), *str)); + } + #endif + vstr.buf = NULL; + return vstr; + } + } + + if (str < top && *str == ':') { + str++; + // {:} is the same as {}, which is the same as {!s} + // This makes a difference when passing in a True or False + // '{}'.format(True) returns 'True' + // '{:d}'.format(True) returns '1' + // So we treat {:} as {} and this later gets treated to be {!s} + if (*str != '}') { + format_spec = str; + for (int nest = 1; str < top;) { + if (*str == '{') { + ++nest; + } else if (*str == '}') { + if (--nest == 0) { + break; + } + } + ++str; + } + } + } + if (str >= top) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("unmatched '{' in format")); + #endif + vstr.buf = NULL; + return vstr; + } + if (*str != '}') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("expected ':' after format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + + mp_obj_t arg = mp_const_none; + + if (field_name) { + int index = 0; + if (MP_LIKELY(unichar_isdigit(*field_name))) { + if (*arg_i > 0) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("can't switch from automatic field numbering to manual field specification")); + #endif + vstr.buf = NULL; + return vstr; + } + field_name = str_to_int(field_name, field_name_top, &index); + if ((uint)index >= n_args - 1) { + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range") ); + vstr.buf = NULL; + return vstr; + } + arg = args[index + 1]; + *arg_i = -1; + } else { + const char *lookup; + for (lookup = field_name; lookup < field_name_top && *lookup != '.' && *lookup != '['; lookup++) {; + } + mp_obj_t field_q = mp_obj_new_str_via_qstr(field_name, lookup - field_name); // should it be via qstr? + field_name = lookup; + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, field_q, MP_MAP_LOOKUP); + if (key_elem == NULL) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, field_q)); + vstr.buf = NULL; + return vstr; + } + arg = key_elem->value; + } + if (field_name < field_name_top) { + mp_raise_NotImplementedError_o( MP_ERROR_TEXT("attributes not supported yet") ); + vstr.buf = NULL; + return vstr; + } + } else { + if (*arg_i < 0) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("can't switch from manual field specification to automatic field numbering") ); + #endif + vstr.buf = NULL; + return vstr; + } + if ((uint)*arg_i >= n_args - 1) { + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range") ); + vstr.buf = NULL; + return vstr; + } + arg = args[(*arg_i) + 1]; + (*arg_i)++; + } + if (!format_spec && !conversion) { + conversion = 's'; + } + if (conversion) { + mp_print_kind_t print_kind; + if (conversion == 's') { + print_kind = PRINT_STR; + } else { + assert(conversion == 'r'); + print_kind = PRINT_REPR; + } + vstr_t arg_vstr; + mp_print_t arg_print; + vstr_init_print(&arg_vstr, 16, &arg_print); + mp_obj_print_helper(&arg_print, arg, print_kind); + arg = mp_obj_new_str_from_vstr(&mp_type_str, &arg_vstr); + } + + char fill = '\0'; + char align = '\0'; + int width = -1; + int precision = -1; + char type = '\0'; + int flags = 0; + + if (format_spec) { + // The format specifier (from http://docs.python.org/2/library/string.html#formatspec) + // + // [[fill]align][sign][#][0][width][,][.precision][type] + // fill ::= + // align ::= "<" | ">" | "=" | "^" + // sign ::= "+" | "-" | " " + // width ::= integer + // precision ::= integer + // type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" + + // recursively call the formatter to format any nested specifiers + if (MP_STACK_CHECK()) { + vstr.buf = NULL; + return vstr; + } + vstr_t format_spec_vstr = mp_obj_str_format_helper(format_spec, str, arg_i, n_args, args, kwargs); + if (format_spec_vstr.buf == NULL) { + return format_spec_vstr; + } + const char *s = vstr_null_terminated_str(&format_spec_vstr); + const char *stop = s + format_spec_vstr.len; + if (isalignment(*s)) { + align = *s++; + } else if (*s && isalignment(s[1])) { + fill = *s++; + align = *s++; + } + if (*s == '+' || *s == '-' || *s == ' ') { + if (*s == '+') { + flags |= PF_FLAG_SHOW_SIGN; + } else if (*s == ' ') { + flags |= PF_FLAG_SPACE_SIGN; + } + s++; + } + if (*s == '#') { + flags |= PF_FLAG_SHOW_PREFIX; + s++; + } + if (*s == '0') { + if (!align) { + align = '='; + } + if (!fill) { + fill = '0'; + } + } + s = str_to_int(s, stop, &width); + if (*s == ',') { + flags |= PF_FLAG_SHOW_COMMA; + s++; + } + if (*s == '.') { + s++; + s = str_to_int(s, stop, &precision); + } + if (istype(*s)) { + type = *s++; + } + if (*s) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("invalid format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + vstr_clear(&format_spec_vstr); + } + if (!align) { + if (arg_looks_numeric(arg)) { + align = '>'; + } else { + align = '<'; + } + } + if (!fill) { + fill = ' '; + } + + if (flags & (PF_FLAG_SHOW_SIGN | PF_FLAG_SPACE_SIGN)) { + if (type == 's') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("sign not allowed in string format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + if (type == 'c') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("sign not allowed with integer format specifier 'c'")); + #endif + vstr.buf = NULL; + return vstr; + } + } + + switch (align) { + case '<': + flags |= PF_FLAG_LEFT_ADJUST; + break; + case '=': + flags |= PF_FLAG_PAD_AFTER_SIGN; + break; + case '^': + flags |= PF_FLAG_CENTER_ADJUST; + break; + } + + if (arg_looks_integer(arg)) { + switch (type) { + case 'b': + mp_print_mp_int(&print, arg, 2, 'a', flags, fill, width, 0); + continue; + + case 'c': { + char ch = mp_obj_get_int(arg); + mp_print_strn(&print, &ch, 1, flags, fill, width); + continue; + } + + case '\0': // No explicit format type implies 'd' + case 'n': // I don't think we support locales in uPy so use 'd' + case 'd': + mp_print_mp_int(&print, arg, 10, 'a', flags, fill, width, 0); + continue; + + case 'o': + if (flags & PF_FLAG_SHOW_PREFIX) { + flags |= PF_FLAG_SHOW_OCTAL_LETTER; + } + + mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, 0); + continue; + + case 'X': + case 'x': + mp_print_mp_int(&print, arg, 16, type - ('X' - 'A'), flags, fill, width, 0); + continue; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case '%': + // The floating point formatters all work with anything that + // looks like an integer + break; + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } + + // NOTE: no else here. We need the e, f, g etc formats for integer + // arguments (from above if) to take this if. + if (arg_looks_numeric(arg)) { + if (!type) { + + // Even though the docs say that an unspecified type is the same + // as 'g', there is one subtle difference, when the exponent + // is one less than the precision. + // + // '{:10.1}'.format(0.0) ==> '0e+00' + // '{:10.1g}'.format(0.0) ==> '0' + // + // TODO: Figure out how to deal with this. + // + // A proper solution would involve adding a special flag + // or something to format_float, and create a format_double + // to deal with doubles. In order to fix this when using + // sprintf, we'd need to use the e format and tweak the + // returned result to strip trailing zeros like the g format + // does. + // + // {:10.3} and {:10.2e} with 1.23e2 both produce 1.23e+02 + // but with 1.e2 you get 1e+02 and 1.00e+02 + // + // Stripping the trailing 0's (like g) does would make the + // e format give us the right format. + // + // CPython sources say: + // Omitted type specifier. Behaves in the same way as repr(x) + // and str(x) if no precision is given, else like 'g', but with + // at least one digit after the decimal point. */ + + type = 'g'; + } + if (type == 'n') { + type = 'g'; + } + + switch (type) { + #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + mp_print_float(&print, mp_obj_get_float(arg), type, flags, fill, width, precision); + break; + + case '%': + flags |= PF_FLAG_ADD_PERCENT; + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + #define F100 100.0F + #else + #define F100 100.0 + #endif + mp_print_float(&print, mp_obj_get_float(arg) * F100, 'f', flags, fill, width, precision); +#undef F100 + break; + #endif + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } else { + // arg doesn't look like a number + + if (align == '=') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("'=' alignment not allowed in string format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + + switch (type) { + case '\0': // no explicit format type implies 's' + case 's': { + size_t slen; + const char *s = mp_obj_str_get_data(arg, &slen); + if (precision < 0) { + precision = slen; + } + if (slen > (size_t)precision) { + slen = precision; + } + mp_print_strn(&print, s, slen, flags, fill, width); + break; + } + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } + } + + return vstr; +} + +mp_obj_t mp_obj_str_format(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + GET_STR_DATA_LEN(args[0], str, len); + int arg_i = 0; + vstr_t vstr = mp_obj_str_format_helper((const char *)str, (const char *)str + len, &arg_i, n_args, args, kwargs); + if (vstr.buf == NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_str_from_vstr(mp_obj_get_type(args[0]), &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_KW(str_format_obj, 1, mp_obj_str_format); + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict) { + mp_check_self(mp_obj_is_str_or_bytes(pattern)); + + GET_STR_DATA_LEN(pattern, str, len); + const byte *start_str = str; + bool is_bytes = mp_obj_is_type(pattern, &mp_type_bytes); + size_t arg_i = 0; + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + + for (const byte *top = str + len; str < top; str++) { + mp_obj_t arg = MP_OBJ_NULL; + if (*str != '%') { + vstr_add_byte(&vstr, *str); + continue; + } + if (++str >= top) { + goto incomplete_format; + } + if (*str == '%') { + vstr_add_byte(&vstr, '%'); + continue; + } + + // Dictionary value lookup + if (*str == '(') { + if (dict == MP_OBJ_NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("format needs a dict")); + } + arg_i = 1; // we used up the single dict argument + const byte *key = ++str; + while (*str != ')') { + if (str >= top) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format key")); + #endif + } + ++str; + } + mp_obj_t k_obj = mp_obj_new_str_via_qstr((const char *)key, str - key); + arg = mp_obj_dict_get(dict, k_obj); + if (arg == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + str++; + } + + int flags = 0; + char fill = ' '; + int alt = 0; + while (str < top) { + if (*str == '-') { + flags |= PF_FLAG_LEFT_ADJUST; + } else if (*str == '+') { + flags |= PF_FLAG_SHOW_SIGN; + } else if (*str == ' ') { + flags |= PF_FLAG_SPACE_SIGN; + } else if (*str == '#') { + alt = PF_FLAG_SHOW_PREFIX; + } else if (*str == '0') { + flags |= PF_FLAG_PAD_AFTER_SIGN; + fill = '0'; + } else { + break; + } + str++; + } + // parse width, if it exists + int width = 0; + if (str < top) { + if (*str == '*') { + if (arg_i >= n_args) { + goto not_enough_args; + } + width = mp_obj_get_int(args[arg_i++]); + str++; + } else { + str = (const byte *)str_to_int((const char *)str, (const char *)top, &width); + } + } + int prec = -1; + if (str < top && *str == '.') { + if (++str < top) { + if (*str == '*') { + if (arg_i >= n_args) { + goto not_enough_args; + } + prec = mp_obj_get_int(args[arg_i++]); + str++; + } else { + prec = 0; + str = (const byte *)str_to_int((const char *)str, (const char *)top, &prec); + } + } + } + + if (str >= top) { + incomplete_format: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format")); + #endif + } + + // Tuple value lookup + if (arg == MP_OBJ_NULL) { + if (arg_i >= n_args) { +not_enough_args: + mp_raise_TypeError(MP_ERROR_TEXT("format string needs more arguments")); + } + arg = args[arg_i++]; + } + switch (*str) { + case 'c': + if (mp_obj_is_str(arg)) { + size_t slen; + const char *s = mp_obj_str_get_data(arg, &slen); + if (slen != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("%%c needs int or char") ); + } + mp_print_strn(&print, s, 1, flags, ' ', width); + } else if (arg_looks_integer(arg)) { + char ch = mp_obj_get_int(arg); + mp_print_strn(&print, &ch, 1, flags, ' ', width); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("integer needed")); + } + break; + + case 'd': + case 'i': + case 'u': + mp_print_mp_int(&print, arg_as_int(arg), 10, 'a', flags, fill, width, prec); + break; + + #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + mp_print_float(&print, mp_obj_get_float(arg), *str, flags, fill, width, prec); + break; + #endif + + case 'o': + if (alt) { + flags |= (PF_FLAG_SHOW_PREFIX | PF_FLAG_SHOW_OCTAL_LETTER); + } + mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, prec); + break; + + case 'r': + case 's': { + vstr_t arg_vstr; + mp_print_t arg_print; + vstr_init_print(&arg_vstr, 16, &arg_print); + mp_print_kind_t print_kind = (*str == 'r' ? PRINT_REPR : PRINT_STR); + if (print_kind == PRINT_STR && is_bytes && mp_obj_is_type(arg, &mp_type_bytes)) { + // If we have something like b"%s" % b"1", bytes arg should be + // printed undecorated. + print_kind = PRINT_RAW; + } + mp_obj_print_helper(&arg_print, arg, print_kind); + uint vlen = arg_vstr.len; + if (prec < 0) { + prec = vlen; + } + if (vlen > (uint)prec) { + vlen = prec; + } + mp_print_strn(&print, arg_vstr.buf, vlen, flags, ' ', width); + vstr_clear(&arg_vstr); + break; + } + + case 'X': + case 'x': + mp_print_mp_int(&print, arg, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec); + break; + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unsupported format character '%c' (0x%x) at index %d"), + *str, *str, str - start_str)); + #endif + } + } + + if (arg_i != n_args) { + mp_raise_TypeError(MP_ERROR_TEXT("format string didn't convert all arguments")); + } + + return mp_obj_new_str_from_vstr(is_bytes ? &mp_type_bytes : &mp_type_str, &vstr); +} +#endif + +// The implementation is optimized, returning the original string if there's +// nothing to replace. +STATIC mp_obj_t str_replace(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + mp_int_t max_rep = -1; + if (n_args == 4) { + max_rep = mp_obj_get_int(args[3]); + if (max_rep == 0) { + return args[0]; + } else if (max_rep < 0) { + max_rep = -1; + } + } + + // if max_rep is still -1 by this point we will need to do all possible replacements + + // check argument types + + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + if (mp_obj_get_type(args[2]) != self_type) { + return bad_implicit_conversion(args[2]); + } + + // extract string data + + GET_STR_DATA_LEN(args[0], str, str_len); + GET_STR_DATA_LEN(args[1], old, old_len); + GET_STR_DATA_LEN(args[2], new, new_len); + + // old won't exist in str if it's longer, so nothing to replace + if (old_len > str_len) { + return args[0]; + } + + // data for the replaced string + byte *data = NULL; + vstr_t vstr; + + // do 2 passes over the string: + // first pass computes the required length of the replaced string + // second pass does the replacements + for (;;) { + size_t replaced_str_index = 0; + size_t num_replacements_done = 0; + const byte *old_occurrence; + const byte *offset_ptr = str; + size_t str_len_remain = str_len; + if (old_len == 0) { + // if old_str is empty, copy new_str to start of replaced string + // copy the replacement string + if (data != NULL) { + memcpy(data, new, new_len); + } + replaced_str_index += new_len; + num_replacements_done++; + } + while (num_replacements_done != (size_t)max_rep && str_len_remain > 0 && (old_occurrence = find_subbytes(offset_ptr, str_len_remain, old, old_len, 1)) != NULL) { + if (old_len == 0) { + old_occurrence += 1; + } + // copy from just after end of last occurrence of to-be-replaced string to right before start of next occurrence + if (data != NULL) { + memcpy(data + replaced_str_index, offset_ptr, old_occurrence - offset_ptr); + } + replaced_str_index += old_occurrence - offset_ptr; + // copy the replacement string + if (data != NULL) { + memcpy(data + replaced_str_index, new, new_len); + } + replaced_str_index += new_len; + offset_ptr = old_occurrence + old_len; + str_len_remain = str + str_len - offset_ptr; + num_replacements_done++; + } + + // copy from just after end of last occurrence of to-be-replaced string to end of old string + if (data != NULL) { + memcpy(data + replaced_str_index, offset_ptr, str_len_remain); + } + replaced_str_index += str_len_remain; + + if (data == NULL) { + // first pass + if (num_replacements_done == 0) { + // no substr found, return original string + return args[0]; + } else { + // substr found, allocate new string + vstr_init_len(&vstr, replaced_str_index); + data = (byte *)vstr.buf; + assert(data != NULL); + } + } else { + // second pass, we are done + break; + } + } + + return mp_obj_new_str_from_vstr(self_type, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_replace_obj, 3, 4, str_replace); + +#if MICROPY_PY_BUILTINS_STR_COUNT +STATIC mp_obj_t str_count(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + // check argument type + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + GET_STR_DATA_LEN(args[0], haystack, haystack_len); + GET_STR_DATA_LEN(args[1], needle, needle_len); + + const byte *start = haystack; + const byte *end = haystack + haystack_len; + if (n_args >= 3 && args[2] != mp_const_none) { + start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); + } + if (n_args >= 4 && args[3] != mp_const_none) { + end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); + } + + // if needle_len is zero then we count each gap between characters as an occurrence + if (needle_len == 0) { + return MP_OBJ_NEW_SMALL_INT(utf8_charlen(start, end - start) + 1); + } + + // count the occurrences + mp_int_t num_occurrences = 0; + for (const byte *haystack_ptr = start; haystack_ptr + needle_len <= end;) { + if (memcmp(haystack_ptr, needle, needle_len) == 0) { + num_occurrences++; + haystack_ptr += needle_len; + } else { + haystack_ptr = utf8_next_char(haystack_ptr); + } + } + + return MP_OBJ_NEW_SMALL_INT(num_occurrences); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_count_obj, 2, 4, str_count); +#endif + +#if MICROPY_PY_BUILTINS_STR_PARTITION +STATIC mp_obj_t str_partitioner(mp_obj_t self_in, mp_obj_t arg, int direction) { + mp_check_self(mp_obj_is_str_or_bytes(self_in)); + const mp_obj_type_t *self_type = mp_obj_get_type(self_in); + if (self_type != mp_obj_get_type(arg)) { + return bad_implicit_conversion(arg); + } + + GET_STR_DATA_LEN(self_in, str, str_len); + GET_STR_DATA_LEN(arg, sep, sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + mp_obj_t result[3]; + if (self_type == &mp_type_str) { + result[0] = MP_OBJ_NEW_QSTR(MP_QSTR_); + result[1] = MP_OBJ_NEW_QSTR(MP_QSTR_); + result[2] = MP_OBJ_NEW_QSTR(MP_QSTR_); + } else { + result[0] = mp_const_empty_bytes; + result[1] = mp_const_empty_bytes; + result[2] = mp_const_empty_bytes; + } + + if (direction > 0) { + result[0] = self_in; + } else { + result[2] = self_in; + } + + const byte *position_ptr = find_subbytes(str, str_len, sep, sep_len, direction); + if (position_ptr != NULL) { + size_t position = position_ptr - str; + result[0] = mp_obj_new_str_of_type(self_type, str, position); + result[1] = arg; + result[2] = mp_obj_new_str_of_type(self_type, str + position + sep_len, str_len - position - sep_len); + } + + return mp_obj_new_tuple(3, result); +} + +STATIC mp_obj_t str_partition(mp_obj_t self_in, mp_obj_t arg) { + return str_partitioner(self_in, arg, 1); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_partition_obj, str_partition); + +STATIC mp_obj_t str_rpartition(mp_obj_t self_in, mp_obj_t arg) { + return str_partitioner(self_in, arg, -1); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_rpartition_obj, str_rpartition); +#endif + +// Supposedly not too critical operations, so optimize for code size +STATIC mp_obj_t str_caseconv(unichar (*op)(unichar), mp_obj_t self_in) { + GET_STR_DATA_LEN(self_in, self_data, self_len); + vstr_t vstr; + vstr_init_len(&vstr, self_len); + byte *data = (byte *)vstr.buf; + for (size_t i = 0; i < self_len; i++) { + *data++ = op(*self_data++); + } + return mp_obj_new_str_from_vstr(mp_obj_get_type(self_in), &vstr); +} + +STATIC mp_obj_t str_lower(mp_obj_t self_in) { + return str_caseconv(unichar_tolower, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_lower_obj, str_lower); + +STATIC mp_obj_t str_upper(mp_obj_t self_in) { + return str_caseconv(unichar_toupper, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_upper_obj, str_upper); + +STATIC mp_obj_t str_uni_istype(bool (*f)(unichar), mp_obj_t self_in) { + GET_STR_DATA_LEN(self_in, self_data, self_len); + + if (self_len == 0) { + return mp_const_false; // default to False for empty str + } + + if (f != unichar_isupper && f != unichar_islower) { + for (size_t i = 0; i < self_len; i++) { + if (!f(*self_data++)) { + return mp_const_false; + } + } + } else { + bool contains_alpha = false; + + for (size_t i = 0; i < self_len; i++) { // only check alphanumeric characters + if (unichar_isalpha(*self_data++)) { + contains_alpha = true; + if (!f(*(self_data - 1))) { // -1 because we already incremented above + return mp_const_false; + } + } + } + + if (!contains_alpha) { + return mp_const_false; + } + } + + return mp_const_true; +} + +STATIC mp_obj_t str_isspace(mp_obj_t self_in) { + return str_uni_istype(unichar_isspace, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isspace_obj, str_isspace); + +STATIC mp_obj_t str_isalpha(mp_obj_t self_in) { + return str_uni_istype(unichar_isalpha, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isalpha_obj, str_isalpha); + +STATIC mp_obj_t str_isdigit(mp_obj_t self_in) { + return str_uni_istype(unichar_isdigit, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isdigit_obj, str_isdigit); + +STATIC mp_obj_t str_isupper(mp_obj_t self_in) { + return str_uni_istype(unichar_isupper, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isupper_obj, str_isupper); + +STATIC mp_obj_t str_islower(mp_obj_t self_in) { + return str_uni_istype(unichar_islower, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_islower_obj, str_islower); + +#if MICROPY_CPYTHON_COMPAT +// These methods are superfluous in the presence of str() and bytes() +// constructors. +// TODO: should accept kwargs too +STATIC mp_obj_t bytes_decode(size_t n_args, const mp_obj_t *args) { + mp_obj_t new_args[2]; + if (n_args == 1) { + new_args[0] = args[0]; + new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); + args = new_args; + n_args++; + } + return mp_obj_str_make_new(&mp_type_str, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bytes_decode_obj, 1, 3, bytes_decode); + +// TODO: should accept kwargs too +STATIC mp_obj_t str_encode(size_t n_args, const mp_obj_t *args) { + mp_obj_t new_args[2]; + if (n_args == 1) { + new_args[0] = args[0]; + new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); + args = new_args; + n_args++; + } + return bytes_make_new(NULL, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_encode_obj, 1, 3, str_encode); +#endif + +mp_int_t mp_obj_str_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + if (flags == MP_BUFFER_READ) { + GET_STR_DATA_LEN(self_in, str_data, str_len); + bufinfo->buf = (void *)str_data; + bufinfo->len = str_len; + bufinfo->typecode = 'B'; // bytes should be unsigned, so should unicode byte-access + return 0; + } else { + // can't write to a string + return 1; + } +} + +STATIC const mp_rom_map_elem_t str8_locals_dict_table[] = { + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, + #if !MICROPY_PY_BUILTINS_STR_UNICODE + // If we have separate unicode type, then here we have methods only + // for bytes type, and it should not have encode() methods. Otherwise, + // we have non-compliant-but-practical bytestring type, which shares + // method table with bytes, so they both have encode() and decode() + // methods (which should do type checking at runtime). + { MP_ROM_QSTR(MP_QSTR_encode), MP_ROM_PTR(&str_encode_obj) }, + #endif + #endif + { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&str_find_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfind), MP_ROM_PTR(&str_rfind_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&str_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_rindex), MP_ROM_PTR(&str_rindex_obj) }, + { MP_ROM_QSTR(MP_QSTR_join), MP_ROM_PTR(&str_join_obj) }, + { MP_ROM_QSTR(MP_QSTR_split), MP_ROM_PTR(&str_split_obj) }, + #if MICROPY_PY_BUILTINS_STR_SPLITLINES + { MP_ROM_QSTR(MP_QSTR_splitlines), MP_ROM_PTR(&str_splitlines_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_rsplit), MP_ROM_PTR(&str_rsplit_obj) }, + { MP_ROM_QSTR(MP_QSTR_startswith), MP_ROM_PTR(&str_startswith_obj) }, + { MP_ROM_QSTR(MP_QSTR_endswith), MP_ROM_PTR(&str_endswith_obj) }, + { MP_ROM_QSTR(MP_QSTR_strip), MP_ROM_PTR(&str_strip_obj) }, + { MP_ROM_QSTR(MP_QSTR_lstrip), MP_ROM_PTR(&str_lstrip_obj) }, + { MP_ROM_QSTR(MP_QSTR_rstrip), MP_ROM_PTR(&str_rstrip_obj) }, + { MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&str_format_obj) }, + { MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&str_replace_obj) }, + #if MICROPY_PY_BUILTINS_STR_COUNT + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&str_count_obj) }, + #endif + #if MICROPY_PY_BUILTINS_STR_PARTITION + { MP_ROM_QSTR(MP_QSTR_partition), MP_ROM_PTR(&str_partition_obj) }, + { MP_ROM_QSTR(MP_QSTR_rpartition), MP_ROM_PTR(&str_rpartition_obj) }, + #endif + #if MICROPY_PY_BUILTINS_STR_CENTER + { MP_ROM_QSTR(MP_QSTR_center), MP_ROM_PTR(&str_center_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_lower), MP_ROM_PTR(&str_lower_obj) }, + { MP_ROM_QSTR(MP_QSTR_upper), MP_ROM_PTR(&str_upper_obj) }, + { MP_ROM_QSTR(MP_QSTR_isspace), MP_ROM_PTR(&str_isspace_obj) }, + { MP_ROM_QSTR(MP_QSTR_isalpha), MP_ROM_PTR(&str_isalpha_obj) }, + { MP_ROM_QSTR(MP_QSTR_isdigit), MP_ROM_PTR(&str_isdigit_obj) }, + { MP_ROM_QSTR(MP_QSTR_isupper), MP_ROM_PTR(&str_isupper_obj) }, + { MP_ROM_QSTR(MP_QSTR_islower), MP_ROM_PTR(&str_islower_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(str8_locals_dict, str8_locals_dict_table); + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); + +const mp_obj_type_t mp_type_str = { + { &mp_type_type }, + .name = MP_QSTR_str, + .print = str_print, + .make_new = mp_obj_str_make_new, + .binary_op = mp_obj_str_binary_op, + .subscr = bytes_subscr, + .getiter = mp_obj_new_str_iterator, + .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, +}; +#endif + +// Reuses most of methods from str +const mp_obj_type_t mp_type_bytes = { + { &mp_type_type }, + .name = MP_QSTR_bytes, + .print = str_print, + .make_new = bytes_make_new, + .binary_op = mp_obj_str_binary_op, + .subscr = bytes_subscr, + .getiter = mp_obj_new_bytes_iterator, + .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, +}; + +// The zero-length bytes object, with data that includes a null-terminating byte +const mp_obj_str_t mp_const_empty_bytes_obj = {{&mp_type_bytes}, 0, 0, (const byte *)""}; + +// Create a str/bytes object using the given data. New memory is allocated and +// the data is copied across. This function should only be used if the type is bytes, +// or if the type is str and the string data is known to be not interned. +mp_obj_t mp_obj_new_str_copy(const mp_obj_type_t *type, const byte *data, size_t len) { + mp_obj_str_t *o = m_new_obj(mp_obj_str_t); + o->base.type = type; + o->len = len; + if (data) { + o->hash = qstr_compute_hash(data, len); + byte *p = m_new(byte, len + 1); + o->data = p; + memcpy(p, data, len * sizeof(byte)); + p[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings + } + return MP_OBJ_FROM_PTR(o); +} + +// Create a str/bytes object using the given data. If the type is str and the string +// data is already interned, then a qstr object is returned. Otherwise new memory is +// allocated for the object and the data is copied across. +mp_obj_t mp_obj_new_str_of_type(const mp_obj_type_t *type, const byte *data, size_t len) { + if (type == &mp_type_str) { + return mp_obj_new_str((const char *)data, len); + } else { + return mp_obj_new_bytes(data, len); + } +} + +// Create a str using a qstr to store the data; may use existing or new qstr. +mp_obj_t mp_obj_new_str_via_qstr(const char *data, size_t len) { + return MP_OBJ_NEW_QSTR(qstr_from_strn(data, len)); +} + +// Create a str/bytes object from the given vstr. The vstr buffer is resized to +// the exact length required and then reused for the str/bytes object. The vstr +// is cleared and can safely be passed to vstr_free if it was heap allocated. +mp_obj_t mp_obj_new_str_from_vstr(const mp_obj_type_t *type, vstr_t *vstr) { + // if not a bytes object, look if a qstr with this data already exists + if (type == &mp_type_str) { + qstr q = qstr_find_strn(vstr->buf, vstr->len); + if (q != MP_QSTRnull) { + vstr_clear(vstr); + vstr->alloc = 0; + return MP_OBJ_NEW_QSTR(q); + } + } + + // make a new str/bytes object + mp_obj_str_t *o = m_new_obj(mp_obj_str_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = type; + o->len = vstr->len; + o->hash = qstr_compute_hash((byte*)vstr->buf, vstr->len); + if (vstr->len + 1 == vstr->alloc) { + o->data = (byte*)vstr->buf; + } else { + o->data = (byte*)m_renew(char, vstr->buf, vstr->alloc, vstr->len + 1); + if (o->data == NULL) { + return MP_OBJ_NULL; + } + } + ((byte*)o->data)[o->len] = '\0'; // add null byte + vstr->buf = NULL; + vstr->alloc = 0; + return MP_OBJ_FROM_PTR(o); +} + +mp_obj_t mp_obj_new_str(const char * data, size_t len) { + qstr q = qstr_find_strn(data, len); + if (q != MP_QSTRnull) { + // qstr with this data already exists + return MP_OBJ_NEW_QSTR(q); + } else { + // no existing qstr, don't make one + return mp_obj_new_str_copy(&mp_type_str, (const byte*)data, len); + } +} + +mp_obj_t mp_obj_str_intern(mp_obj_t str) { + GET_STR_DATA_LEN(str, data, len); + return mp_obj_new_str_via_qstr((const char *)data, len); +} + +mp_obj_t mp_obj_str_intern_checked(mp_obj_t obj) { + size_t len; + const char *data = mp_obj_str_get_data(obj, &len); + if (data == NULL) { + // exception + return MP_OBJ_NULL; + } + return mp_obj_new_str_via_qstr((const char *)data, len); +} + +mp_obj_t mp_obj_new_bytes(const byte* data, size_t len) { + return mp_obj_new_str_copy(&mp_type_bytes, data, len); +} + +bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { + if (mp_obj_is_qstr(s1) && mp_obj_is_qstr(s2)) { + return s1 == s2; + } else { + GET_STR_HASH(s1, h1); + GET_STR_HASH(s2, h2); + // If any of hashes is 0, it means it's not valid + if (h1 != 0 && h2 != 0 && h1 != h2) { + return false; + } + GET_STR_DATA_LEN(s1, d1, l1); + GET_STR_DATA_LEN(s2, d2, l2); + if (l1 != l2) { + return false; + } + return memcmp(d1, d2, l1) == 0; + } +} + +STATIC mp_obj_t bad_implicit_conversion(mp_obj_t self_in) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); + #else + const qstr src_name = mp_obj_get_type(self_in)->name; + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert '%q' object to %q implicitly"), + src_name, src_name == MP_QSTR_str ? MP_QSTR_bytes : MP_QSTR_str)); + #endif +} + +// use this if you will anyway convert the string to a qstr +// will be more efficient for the case where it's already a qstr +qstr mp_obj_str_get_qstr(mp_obj_t self_in) { + if (mp_obj_is_qstr(self_in)) { + return MP_OBJ_QSTR_VALUE(self_in); + } else if (mp_obj_is_type(self_in, &mp_type_str)) { + mp_obj_str_t *self = MP_OBJ_TO_PTR(self_in); + return qstr_from_strn((char *)self->data, self->len); + } else { + bad_implicit_conversion(self_in); + return MP_QSTR_NULL; // TODO callers should handle this case + } +} + +// only use this function if you need the str data to be zero terminated +// at the moment all strings are zero terminated to help with C ASCIIZ compatibility +const char *mp_obj_str_get_str(mp_obj_t self_in) { + if (mp_obj_is_str_or_bytes(self_in)) { + GET_STR_DATA_LEN(self_in, s, l); + (void)l; // len unused + return (const char *)s; + } else { + bad_implicit_conversion(self_in); + return NULL; // TODO callers should handle this case + } +} + +const char *mp_obj_str_get_data(mp_obj_t self_in, size_t *len) { + if (mp_obj_is_str_or_bytes(self_in)) { + GET_STR_DATA_LEN(self_in, s, l); + *len = l; + return (const char *)s; + } else { + bad_implicit_conversion(self_in); + return NULL; // TODO callers should handle this case + } +} + +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +const byte *mp_obj_str_get_data_no_check(mp_obj_t self_in, size_t *len) { + if (mp_obj_is_qstr(self_in)) { + return qstr_data(MP_OBJ_QSTR_VALUE(self_in), len); + } else { + *len = ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->len; + return ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->data; + } +} +#endif + +/******************************************************************************/ +/* str iterator */ + +typedef struct _mp_obj_str8_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t str; + size_t cur; +} mp_obj_str8_it_t; + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) { + mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); + GET_STR_DATA_LEN(self->str, str, len); + if (self->cur < len) { + mp_obj_t o_out = mp_obj_new_str_via_qstr((const char *)str + self->cur, 1); + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = str_it_iternext; + o->str = str; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} +#endif + +STATIC mp_obj_t bytes_it_iternext(mp_obj_t self_in) { + mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); + GET_STR_DATA_LEN(self->str, str, len); + if (self->cur < len) { + mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(str[self->cur]); + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = bytes_it_iternext; + o->str = str; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} diff --git a/ports/wapy/qstrdefsport.h b/ports/wapy/qstrdefsport.h new file mode 100644 index 000000000..8326cb082 --- /dev/null +++ b/ports/wapy/qstrdefsport.h @@ -0,0 +1,50 @@ +// qstrs specific to this port +Q(\r\n) +Q(__aenter__) +Q(__aexit__) +Q(__nonzero__) +Q(bound_method) +Q(pend_throw) +Q(__bool__) +Q(__pos__) +Q(__invert_) +Q(__abs__) +Q(__sizeof__) +Q(__eq__) +Q(__neg__) +Q(__lt__) +Q(__gt__) +Q(__le__) +Q(__ge__) +Q(__iadd__) +Q(__isub__) +Q(file) +Q(mode) +Q(encoding) +Q(r) +Q(buffering) +Q(heap_lock) +Q(heap_unlock) +Q(ld) +Q(unref) +Q(ptr) +Q(use) +Q(srv) +Q(config) +Q(oref) +Q(µO|%s|%s) +Q(android) +Q(emscripten) +Q(window) +Q(document) +Q(canvas) +Q(width) +Q(height) +Q(title) +Q(crt) +Q(set_x) +Q(set_z) +Q(set_text) + + + diff --git a/ports/wapy/runtime_no_nlr.c b/ports/wapy/runtime_no_nlr.c new file mode 100644 index 000000000..f885cfa34 --- /dev/null +++ b/ports/wapy/runtime_no_nlr.c @@ -0,0 +1,1683 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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 "py/parsenum.h" +#include "py/compile.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objtype.h" +#include "py/objmodule.h" +#include "py/objgenerator.h" +#include "py/smallint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stackctrl.h" +#include "py/gc.h" + +#include "upython.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#define DEBUG_OP_printf(...) DEBUG_printf(__VA_ARGS__) +#else // don't print debugging info +#define DEBUG_printf(...) (void)0 +#define DEBUG_OP_printf(...) (void)0 +#endif + +const mp_obj_module_t mp_module___main__ = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&MP_STATE_VM(dict_main), +}; + +void mp_init(void) { + MP_STATE_THREAD(active_exception) = NULL; + + qstr_init(); + + // no pending exceptions to start with + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + #if MICROPY_ENABLE_SCHEDULER + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + MP_STATE_VM(sched_idx) = 0; + MP_STATE_VM(sched_len) = 0; + #endif + + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF + mp_init_emergency_exception_buf(); + #endif + + #if MICROPY_KBD_EXCEPTION + // initialise the exception object for raising KeyboardInterrupt + MP_STATE_VM(mp_kbd_exception).base.type = &mp_type_KeyboardInterrupt; + MP_STATE_VM(mp_kbd_exception).traceback_alloc = 0; + MP_STATE_VM(mp_kbd_exception).traceback_len = 0; + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + MP_STATE_VM(mp_kbd_exception).args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; + #endif + + #if MICROPY_ENABLE_COMPILER + // optimization disabled by default + MP_STATE_VM(mp_optimise_value) = 0; + #if MICROPY_EMIT_NATIVE + MP_STATE_VM(default_emit_opt) = MP_EMIT_OPT_NONE; + #endif + #endif + + // init global module dict + mp_obj_dict_init(&MP_STATE_VM(mp_loaded_modules_dict), 3); + if (MP_STATE_THREAD(active_exception) != NULL) { + return; + } + + // initialise the __main__ module + mp_obj_dict_init(&MP_STATE_VM(dict_main), 1); + if (MP_STATE_THREAD(active_exception) != NULL) { + return; + } + mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(dict_main)), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + + // locals = globals for outer module (see Objects/frameobject.c/PyFrame_New()) + mp_locals_set(&MP_STATE_VM(dict_main)); + mp_globals_set(&MP_STATE_VM(dict_main)); + + #if MICROPY_CAN_OVERRIDE_BUILTINS + // start with no extensions to builtins + MP_STATE_VM(mp_module_builtins_override_dict) = NULL; + #endif + + #if MICROPY_PY_OS_DUPTERM + for (size_t i = 0; i < MICROPY_PY_OS_DUPTERM; ++i) { + MP_STATE_VM(dupterm_objs[i]) = MP_OBJ_NULL; + } + #endif + + #if MICROPY_VFS + // initialise the VFS sub-system + MP_STATE_VM(vfs_cur) = NULL; + MP_STATE_VM(vfs_mount_table) = NULL; + #endif + + #if MICROPY_PY_SYS_ATEXIT + MP_STATE_VM(sys_exitfunc) = mp_const_none; + #endif + + #if MICROPY_PY_SYS_SETTRACE + MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; + MP_STATE_THREAD(prof_callback_is_executing) = false; + MP_STATE_THREAD(current_code_state) = NULL; + #endif + + #if MICROPY_PY_BLUETOOTH + MP_STATE_VM(bluetooth) = MP_OBJ_NULL; + #endif + + #if MICROPY_PY_THREAD_GIL + mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); + #endif + + // call port specific initialization if any + #ifdef MICROPY_PORT_INIT_FUNC + MICROPY_PORT_INIT_FUNC; + #endif + + MP_THREAD_GIL_ENTER(); +} + +void mp_deinit(void) { + MP_THREAD_GIL_EXIT(); + + // call port specific deinitialization if any + #ifdef MICROPY_PORT_DEINIT_FUNC + MICROPY_PORT_DEINIT_FUNC; + #endif + + //mp_obj_dict_free(&dict_main); + //mp_map_deinit(&MP_STATE_VM(mp_loaded_modules_map)); +} + +mp_obj_t mp_load_name(qstr qst) { + // logic: search locals, globals, builtins + DEBUG_OP_printf("load name %s\n", qstr_str(qst)); + // If we're at the outer scope (locals == globals), dispatch to load_global right away + if (mp_locals_get() != mp_globals_get()) { + mp_map_elem_t *elem = mp_map_lookup(&mp_locals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + return mp_load_global(qst); +} + +mp_obj_t mp_load_global(qstr qst) { + // logic: search globals, builtins + DEBUG_OP_printf("load global %s\n", qstr_str(qst)); + mp_map_elem_t *elem = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem == NULL) { + #if MICROPY_CAN_OVERRIDE_BUILTINS + if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { + // lookup in additional dynamic table of builtins first + elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + #endif + elem = mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem == NULL) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_msg(&mp_type_NameError, MP_ERROR_TEXT("name not defined")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_NameError, + MP_ERROR_TEXT("name '%q' isn't defined"), qst)); + #endif + } + } + return elem->value; +} + +mp_obj_t mp_load_build_class(void) { + DEBUG_OP_printf("load_build_class\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { + // lookup in additional dynamic table of builtins first + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(MP_QSTR___build_class__), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + #endif + return MP_OBJ_FROM_PTR(&mp_builtin___build_class___obj); +} + +void mp_store_name(qstr qst, mp_obj_t obj) { + DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qst), obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst), obj); +} + +mp_obj_t mp_delete_name(qstr qst) { + DEBUG_OP_printf("delete name %s\n", qstr_str(qst)); + // TODO convert KeyError to NameError if qst not found + return mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst)); +} + +void mp_store_global(qstr qst, mp_obj_t obj) { + DEBUG_OP_printf("store global %s <- %p\n", qstr_str(qst), obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst), obj); +} + +mp_obj_t mp_delete_global(qstr qst) { + DEBUG_OP_printf("delete global %s\n", qstr_str(qst)); + // TODO convert KeyError to NameError if qst not found + return mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst)); +} + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { + DEBUG_OP_printf("unary " UINT_FMT " %q %p\n", op, mp_unary_op_method_name[op], arg); + + if (op == MP_UNARY_OP_NOT) { + // "not x" is the negative of whether "x" is true per Python semantics + return mp_obj_new_bool(mp_obj_is_true(arg) == 0); + } else if (mp_obj_is_small_int(arg)) { + mp_int_t val = MP_OBJ_SMALL_INT_VALUE(arg); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(val != 0); + case MP_UNARY_OP_HASH: + return arg; + case MP_UNARY_OP_POSITIVE: + case MP_UNARY_OP_INT: + return arg; + case MP_UNARY_OP_NEGATIVE: + // check for overflow + if (val == MP_SMALL_INT_MIN) { + return mp_obj_new_int(-val); + } else { + return MP_OBJ_NEW_SMALL_INT(-val); + } + case MP_UNARY_OP_ABS: + if (val >= 0) { + return arg; + } else if (val == MP_SMALL_INT_MIN) { + // check for overflow + return mp_obj_new_int(-val); + } else { + return MP_OBJ_NEW_SMALL_INT(-val); + } + default: + assert(op == MP_UNARY_OP_INVERT); + return MP_OBJ_NEW_SMALL_INT(~val); + } + } else if (op == MP_UNARY_OP_HASH && mp_obj_is_str_or_bytes(arg)) { + // fast path for hashing str/bytes + GET_STR_HASH(arg, h); + if (h == 0) { + GET_STR_DATA_LEN(arg, data, len); + h = qstr_compute_hash(data, len); + } + return MP_OBJ_NEW_SMALL_INT(h); + } else { + const mp_obj_type_t *type = mp_obj_get_type(arg); + if (type->unary_op != NULL) { + mp_obj_t result = type->unary_op(op, arg); + if (result != MP_OBJ_NULL) { + return result; + } + } + // With MP_UNARY_OP_INT, mp_unary_op() becomes a fallback for mp_obj_get_int(). + // In this case provide a more focused error message to not confuse, e.g. chr(1.0) + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + if (op == MP_UNARY_OP_INT) { + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); + } + } else { + if (op == MP_UNARY_OP_INT) { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert %s to int"), mp_obj_get_type_str(arg))); + } else { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unsupported type for %q: '%s'"), + mp_unary_op_method_name[op], mp_obj_get_type_str(arg))); + } + } + } +} + +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + DEBUG_OP_printf("binary " UINT_FMT " %q %p %p\n", op, mp_binary_op_method_name[op], lhs, rhs); + + // TODO correctly distinguish inplace operators for mutable objects + // lookup logic that CPython uses for +=: + // check for implemented += + // then check for implemented + + // then check for implemented seq.inplace_concat + // then check for implemented seq.concat + // then fail + // note that list does not implement + or +=, so that inplace_concat is reached first for += + + // deal with is + if (op == MP_BINARY_OP_IS) { + return mp_obj_new_bool(lhs == rhs); + } + + // deal with == and != for all types + if (op == MP_BINARY_OP_EQUAL || op == MP_BINARY_OP_NOT_EQUAL) { + // mp_obj_equal_not_equal supports a bunch of shortcuts + return mp_obj_equal_not_equal(op, lhs, rhs); + } + + // deal with exception_match for all types + if (op == MP_BINARY_OP_EXCEPTION_MATCH) { + // rhs must be issubclass(rhs, BaseException) + if (mp_obj_is_exception_type(rhs)) { + if (mp_obj_exception_match(lhs, rhs)) { + return mp_const_true; + } else { + return mp_const_false; + } + } else if (mp_obj_is_type(rhs, &mp_type_tuple)) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(rhs); + for (size_t i = 0; i < tuple->len; i++) { + rhs = tuple->items[i]; + if (!mp_obj_is_exception_type(rhs)) { + goto unsupported_op; + } + if (mp_obj_exception_match(lhs, rhs)) { + return mp_const_true; + } + } + return mp_const_false; + } + goto unsupported_op; + } + + if (mp_obj_is_small_int(lhs)) { + mp_int_t lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs); + if (mp_obj_is_small_int(rhs)) { + mp_int_t rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs); + // This is a binary operation: lhs_val op rhs_val + // We need to be careful to handle overflow; see CERT INT32-C + // Operations that can overflow: + // + result always fits in mp_int_t, then handled by SMALL_INT check + // - result always fits in mp_int_t, then handled by SMALL_INT check + // * checked explicitly + // / if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check + // % if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check + // << checked explicitly + switch (op) { + case MP_BINARY_OP_OR: + case MP_BINARY_OP_INPLACE_OR: + lhs_val |= rhs_val; + break; + case MP_BINARY_OP_XOR: + case MP_BINARY_OP_INPLACE_XOR: + lhs_val ^= rhs_val; + break; + case MP_BINARY_OP_AND: + case MP_BINARY_OP_INPLACE_AND: + lhs_val &= rhs_val; + break; + case MP_BINARY_OP_LSHIFT: + case MP_BINARY_OP_INPLACE_LSHIFT: { + if (rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } else if (rhs_val >= (mp_int_t)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); + goto generic_binary_op; + } else { + // use standard precision + lhs_val <<= rhs_val; + } + break; + } + case MP_BINARY_OP_RSHIFT: + case MP_BINARY_OP_INPLACE_RSHIFT: + if (rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } else { + // standard precision is enough for right-shift + if (rhs_val >= (mp_int_t)BITS_PER_WORD) { + // Shifting to big amounts is underfined behavior + // in C and is CPU-dependent; propagate sign bit. + rhs_val = BITS_PER_WORD - 1; + } + lhs_val >>= rhs_val; + } + break; + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_INPLACE_ADD: + lhs_val += rhs_val; + break; + case MP_BINARY_OP_SUBTRACT: + case MP_BINARY_OP_INPLACE_SUBTRACT: + lhs_val -= rhs_val; + break; + case MP_BINARY_OP_MULTIPLY: + case MP_BINARY_OP_INPLACE_MULTIPLY: { + + // If long long type exists and is larger than mp_int_t, then + // we can use the following code to perform overflow-checked multiplication. + // Otherwise (eg in x64 case) we must use mp_small_int_mul_overflow. + #if 0 + // compute result using long long precision + long long res = (long long)lhs_val * (long long)rhs_val; + if (res > MP_SMALL_INT_MAX || res < MP_SMALL_INT_MIN) { + // result overflowed SMALL_INT, so return higher precision integer + return mp_obj_new_int_from_ll(res); + } else { + // use standard precision + lhs_val = (mp_int_t)res; + } + #endif + + if (mp_small_int_mul_overflow(lhs_val, rhs_val)) { + // use higher precision + lhs = mp_obj_new_int_from_ll(lhs_val); + goto generic_binary_op; + } else { + // use standard precision + return MP_OBJ_NEW_SMALL_INT(lhs_val * rhs_val); + } + } + case MP_BINARY_OP_FLOOR_DIVIDE: + case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: + if (rhs_val == 0) { + goto zero_division; + } + lhs_val = mp_small_int_floor_divide(lhs_val, rhs_val); + break; + + #if MICROPY_PY_BUILTINS_FLOAT + case MP_BINARY_OP_TRUE_DIVIDE: + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + if (rhs_val == 0) { + goto zero_division; + } + return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); + #endif + + case MP_BINARY_OP_MODULO: + case MP_BINARY_OP_INPLACE_MODULO: { + if (rhs_val == 0) { + goto zero_division; + } + lhs_val = mp_small_int_modulo(lhs_val, rhs_val); + break; + } + + case MP_BINARY_OP_POWER: + case MP_BINARY_OP_INPLACE_POWER: + if (rhs_val < 0) { + #if MICROPY_PY_BUILTINS_FLOAT + return mp_obj_float_binary_op(op, lhs_val, rhs); + #else + mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); + #endif + } else { + mp_int_t ans = 1; + while (rhs_val > 0) { + if (rhs_val & 1) { + if (mp_small_int_mul_overflow(ans, lhs_val)) { + goto power_overflow; + } + ans *= lhs_val; + } + if (rhs_val == 1) { + break; + } + rhs_val /= 2; + if (mp_small_int_mul_overflow(lhs_val, lhs_val)) { + goto power_overflow; + } + lhs_val *= lhs_val; + } + lhs_val = ans; + } + break; + + power_overflow: + // use higher precision + lhs = mp_obj_new_int_from_ll(MP_OBJ_SMALL_INT_VALUE(lhs)); + goto generic_binary_op; + + case MP_BINARY_OP_DIVMOD: { + if (rhs_val == 0) { + goto zero_division; + } + // to reduce stack usage we don't pass a temp array of the 2 items + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(mp_small_int_floor_divide(lhs_val, rhs_val)); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(mp_small_int_modulo(lhs_val, rhs_val)); + return MP_OBJ_FROM_PTR(tuple); + } + + case MP_BINARY_OP_LESS: + return mp_obj_new_bool(lhs_val < rhs_val); + case MP_BINARY_OP_MORE: + return mp_obj_new_bool(lhs_val > rhs_val); + case MP_BINARY_OP_LESS_EQUAL: + return mp_obj_new_bool(lhs_val <= rhs_val); + case MP_BINARY_OP_MORE_EQUAL: + return mp_obj_new_bool(lhs_val >= rhs_val); + + default: + goto unsupported_op; + } + // This is an inlined version of mp_obj_new_int, for speed + if (MP_SMALL_INT_FITS(lhs_val)) { + return MP_OBJ_NEW_SMALL_INT(lhs_val); + } else { + return mp_obj_new_int_from_ll(lhs_val); + } + #if MICROPY_PY_BUILTINS_FLOAT + } else if (mp_obj_is_float(rhs)) { + mp_obj_t res = mp_obj_float_binary_op(op, 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, lhs_val, 0, rhs); + if (res == MP_OBJ_NULL) { + goto unsupported_op; + } else { + return res; + } + #endif + } + } + + // Convert MP_BINARY_OP_IN to MP_BINARY_OP_CONTAINS with swapped args. + if (op == MP_BINARY_OP_IN) { + op = MP_BINARY_OP_CONTAINS; + mp_obj_t temp = lhs; + lhs = rhs; + rhs = temp; + } + + // generic binary_op supplied by type + const mp_obj_type_t *type; +generic_binary_op: + type = mp_obj_get_type(lhs); + if (type->binary_op != NULL) { + mp_obj_t result = type->binary_op(op, lhs, rhs); + if (result != MP_OBJ_NULL) { + return result; + } + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + } + + #if MICROPY_PY_REVERSE_SPECIAL_METHODS + if (op >= MP_BINARY_OP_OR && op <= MP_BINARY_OP_POWER) { + mp_obj_t t = rhs; + rhs = lhs; + lhs = t; + op += MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; + goto generic_binary_op; + } else if (op >= MP_BINARY_OP_REVERSE_OR) { + // Convert __rop__ back to __op__ for error message + mp_obj_t t = rhs; + rhs = lhs; + lhs = t; + op -= MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; + } + #endif + + if (op == MP_BINARY_OP_CONTAINS) { + // If type didn't support containment then explicitly walk the iterator. + // mp_getiter will raise the appropriate exception if lhs is not iterable. + mp_obj_iter_buf_t iter_buf; + mp_obj_t iter = mp_getiter(lhs, &iter_buf); + if (iter == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + mp_obj_t next; + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + if (mp_obj_equal(next, rhs)) { + return mp_const_true; + } + } + return mp_const_false; + } + +unsupported_op: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unsupported types for %q: '%s', '%s'"), + mp_binary_op_method_name[op], mp_obj_get_type_str(lhs), mp_obj_get_type_str(rhs))); + #endif + +zero_division: + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); +} + +mp_obj_t mp_call_function_0(mp_obj_t fun) { + return mp_call_function_n_kw(fun, 0, 0, NULL); +} + +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg) { + return mp_call_function_n_kw(fun, 1, 0, &arg); +} + +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { + mp_obj_t args[2]; + args[0] = arg1; + args[1] = arg2; + return mp_call_function_n_kw(fun, 2, 0, args); +} + +// args contains, eg: arg0 arg1 key0 value0 key1 value1 +mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // TODO improve this: fun object can specify its type and we parse here the arguments, + // passing to the function arrays of fixed and keyword arguments + + DEBUG_OP_printf("calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, n_args, n_kw, args); + + if (!fun_in) { + cdbg("660:calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, n_args, n_kw, args); + } else { + // get the type + const mp_obj_type_t *type = mp_obj_get_type(fun_in); + + // do the call + if (type->call != NULL) { + return type->call(fun_in, n_args, n_kw, args); + } + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return mp_raise_TypeError_o("object not callable"); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't callable", mp_obj_get_type_str(fun_in))); + #endif +} + +// args contains: fun self/NULL arg(0) ... arg(n_args-2) arg(n_args-1) kw_key(0) kw_val(0) ... kw_key(n_kw-1) kw_val(n_kw-1) +// if n_args==0 and n_kw==0 then there are only fun and self/NULL +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args) { + DEBUG_OP_printf("call method (fun=%p, self=%p, n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", args[0], args[1], n_args, n_kw, args); + int adjust = (args[1] == MP_OBJ_NULL) ? 0 : 1; + return mp_call_function_n_kw(args[0], n_args + adjust, n_kw, args + 2 - adjust); +} + +// This function only needs to be exposed externally when in stackless mode. +#if !MICROPY_STACKLESS +STATIC +#endif +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args) { + mp_obj_t fun = *args++; + mp_obj_t self = MP_OBJ_NULL; + if (have_self) { + self = *args++; // may be MP_OBJ_NULL + } + uint n_args = n_args_n_kw & 0xff; + uint n_kw = (n_args_n_kw >> 8) & 0xff; + mp_obj_t pos_seq = args[n_args + 2 * n_kw]; // may be MP_OBJ_NULL + mp_obj_t kw_dict = args[n_args + 2 * n_kw + 1]; // may be MP_OBJ_NULL + + DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict); + + // We need to create the following array of objects: + // args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict) + // TODO: optimize one day to avoid constructing new arg array? Will be hard. + + // The new args array + mp_obj_t *args2; + uint args2_alloc; + uint args2_len = 0; + + // Try to get a hint for the size of the kw_dict + uint kw_dict_len = 0; + if (kw_dict != MP_OBJ_NULL && mp_obj_is_type(kw_dict, &mp_type_dict)) { + kw_dict_len = mp_obj_dict_len(kw_dict); + } + + // Extract the pos_seq sequence to the new args array. + // Note that it can be arbitrary iterator. + if (pos_seq == MP_OBJ_NULL) { + // no sequence + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len); + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed pos args + mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); + args2_len += n_args; + + } else if (mp_obj_is_type(pos_seq, &mp_type_tuple) || mp_obj_is_type(pos_seq, &mp_type_list)) { + // optimise the case of a tuple and list + + // get the items + size_t len; + mp_obj_t *items; + mp_obj_get_array(pos_seq, &len, &items); + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len); + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed and variable position args + mp_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t); + args2_len += n_args + len; + + } else { + // generic iterator + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3; + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed position args + mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); + args2_len += n_args; + + // extract the variable position args from the iterator + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(pos_seq, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (args2_len >= args2_alloc) { + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), args2_alloc * 2 * sizeof(mp_obj_t)); + args2_alloc *= 2; + } + args2[args2_len++] = item; + } + } + + // The size of the args2 array now is the number of positional args. + uint pos_args_len = args2_len; + + // Copy the fixed kw args. + mp_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t); + args2_len += 2 * n_kw; + + // Extract (key,value) pairs from kw_dict dictionary and append to args2. + // Note that it can be arbitrary iterator. + if (kw_dict == MP_OBJ_NULL) { + // pass + } else if (mp_obj_is_type(kw_dict, &mp_type_dict)) { + // dictionary + mp_map_t *map = mp_obj_dict_get_map(kw_dict); + assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above + for (size_t i = 0; i < map->alloc; i++) { + if (mp_map_slot_is_filled(map, i)) { + // the key must be a qstr, so intern it if it's a string + mp_obj_t key = map->table[i].key; + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + if (key == MP_OBJ_NULL) { + // exception + return 1; + } + } + args2[args2_len++] = key; + args2[args2_len++] = map->table[i].value; + } + } + } else { + // generic mapping: + // - call keys() to get an iterable of all keys in the mapping + // - call __getitem__ for each key to get the corresponding value + + // get the keys iterable + mp_obj_t dest[3]; + mp_load_method(kw_dict, MP_QSTR_keys, dest); + mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL); + + mp_obj_t key; + while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + // expand size of args array if needed + if (args2_len + 1 >= args2_alloc) { + uint new_alloc = args2_alloc * 2; + if (new_alloc < 4) { + new_alloc = 4; + } + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t)); + args2_alloc = new_alloc; + } + + // the key must be a qstr, so intern it if it's a string + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + } + + // get the value corresponding to the key + mp_load_method(kw_dict, MP_QSTR___getitem__, dest); + dest[2] = key; + mp_obj_t value = mp_call_method_n_kw(1, 0, dest); + + // store the key/value pair in the argument array + args2[args2_len++] = key; + args2[args2_len++] = value; + } + } + + out_args->fun = fun; + out_args->args = args2; + out_args->n_args = pos_args_len; + out_args->n_kw = (args2_len - pos_args_len) / 2; + out_args->n_alloc = args2_alloc; + + return 0; +} + +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args) { + mp_call_args_t out_args; + if (mp_call_prepare_args_n_kw_var(have_self, n_args_n_kw, args, &out_args)) { + return MP_OBJ_NULL; + } + + mp_obj_t res = mp_call_function_n_kw(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + + return res; +} + +// unpacked items are stored in reverse order into the array pointed to by items +mp_obj_t mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) { + size_t seq_len; + if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { + mp_obj_t *seq_items; + mp_obj_get_array(seq_in, &seq_len, &seq_items); + if (seq_len < num) { + goto too_short; + } else if (seq_len > num) { + goto too_long; + } + for (size_t i = 0; i < num; i++) { + items[i] = seq_items[num - 1 - i]; + } + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(seq_in, &iter_buf); + + for (seq_len = 0; seq_len < num; seq_len++) { + mp_obj_t el = mp_iternext(iterable); + if (el == MP_OBJ_STOP_ITERATION) { + goto too_short; + } + items[num - 1 - seq_len] = el; + } + if (mp_iternext(iterable) != MP_OBJ_STOP_ITERATION) { + goto too_long; + } + } + return MP_OBJ_SENTINEL; // success + +too_short: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len)); + #endif +too_long: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError_o(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("too many values to unpack (expected %d)"), (int)num)); + #endif +} + +// unpacked items are stored in reverse order into the array pointed to by items +mp_obj_t mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) { + size_t num_left = num_in & 0xff; + size_t num_right = (num_in >> 8) & 0xff; + DEBUG_OP_printf("unpack ex " UINT_FMT " " UINT_FMT "\n", num_left, num_right); + size_t seq_len; + if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { + // Make the seq variable volatile so the compiler keeps a reference to it, + // since if it's a tuple then seq_items points to the interior of the GC cell + // and mp_obj_new_list may trigger a GC which doesn't trace this and reclaims seq. + volatile mp_obj_t seq = seq_in; + mp_obj_t *seq_items; + mp_obj_get_array(seq, &seq_len, &seq_items); + if (seq_len < num_left + num_right) { + goto too_short; + } + for (size_t i = 0; i < num_right; i++) { + items[i] = seq_items[seq_len - 1 - i]; + } + items[num_right] = mp_obj_new_list(seq_len - num_left - num_right, seq_items + num_left); + for (size_t i = 0; i < num_left; i++) { + items[num_right + 1 + i] = seq_items[num_left - 1 - i]; + } + seq = MP_OBJ_NULL; + } else { + // Generic iterable; this gets a bit messy: we unpack known left length to the + // items destination array, then the rest to a dynamically created list. Once the + // iterable is exhausted, we take from this list for the right part of the items. + // TODO Improve to waste less memory in the dynamically created list. + mp_obj_t iterable = mp_getiter(seq_in, NULL); + mp_obj_t item; + for (seq_len = 0; seq_len < num_left; seq_len++) { + item = mp_iternext(iterable); + if (item == MP_OBJ_STOP_ITERATION) { + goto too_short; + } + items[num_left + num_right + 1 - 1 - seq_len] = item; + } + mp_obj_list_t *rest = MP_OBJ_TO_PTR(mp_obj_new_list(0, NULL)); + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + mp_obj_list_append(MP_OBJ_FROM_PTR(rest), item); + } + if (rest->len < num_right) { + goto too_short; + } + items[num_right] = MP_OBJ_FROM_PTR(rest); + for (size_t i = 0; i < num_right; i++) { + items[num_right - 1 - i] = rest->items[rest->len - num_right + i]; + } + mp_obj_list_set_len(MP_OBJ_FROM_PTR(rest), rest->len - num_right); + } + return MP_OBJ_SENTINEL; // success + +too_short: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len)); + #endif + return MP_OBJ_NULL; +} + +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) { + DEBUG_OP_printf("load attr %p.%s\n", base, qstr_str(attr)); + // use load_method + mp_obj_t dest[2]; + if (mp_load_method(base, attr, dest) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + if (dest[1] == MP_OBJ_NULL) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + +// The following "checked fun" type is local to the mp_convert_member_lookup +// function, and serves to check that the first argument to a builtin function +// has the correct type. + +typedef struct _mp_obj_checked_fun_t { + mp_obj_base_t base; + const mp_obj_type_t *type; + mp_obj_t fun; +} mp_obj_checked_fun_t; + +STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_checked_fun_t *self = MP_OBJ_TO_PTR(self_in); + if (n_args > 0) { + const mp_obj_type_t *arg0_type = mp_obj_get_type(args[0]); + if (arg0_type != self->type) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("argument has wrong type")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("argument should be a '%q' not a '%q'"), self->type->name, arg0_type->name)); + #endif + } + } + return mp_call_function_n_kw(self->fun, n_args, n_kw, args); +} + +STATIC const mp_obj_type_t mp_type_checked_fun = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = checked_fun_call, +}; + +STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) { + mp_obj_checked_fun_t *o = m_new_obj(mp_obj_checked_fun_t); + o->base.type = &mp_type_checked_fun; + o->type = type; + o->fun = fun; + return MP_OBJ_FROM_PTR(o); +} + +#endif // MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + +// Given a member that was extracted from an instance, convert it correctly +// and put the result in the dest[] array for a possible method call. +// Conversion means dealing with static/class methods, callables, and values. +// see http://docs.python.org/3/howto/descriptor.html +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 + 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) + && type != &mp_type_object) { + // we extracted a builtin method without a first argument, so we must + // wrap this function in a type checker + // Note that object will do its own checking so shouldn't be wrapped. + dest[0] = mp_obj_new_checked_fun(type, member); + } else + #endif + { + // return a bound method, with self being this object + dest[0] = member; + dest[1] = self; + } + } else { + // class member is a value, so just return that value + dest[0] = member; + } +} + +// no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL +// normal attribute found, returns: dest[0] == , dest[1] == MP_OBJ_NULL +// method attribute found, returns: dest[0] == , dest[1] == +mp_obj_t mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { + // clear output to indicate no attribute/method found yet + dest[0] = MP_OBJ_NULL; + dest[1] = MP_OBJ_NULL; + + // get the type + const mp_obj_type_t *type = mp_obj_get_type(obj); + + // look for built-in names + #if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___class__) { + // a.__class__ is equivalent to type(a) + dest[0] = MP_OBJ_FROM_PTR(type); + return MP_OBJ_SENTINEL; // success + } + if (attr == MP_QSTR___base__) { // PMPP https://github.com/micropython/micropython/pull/4368 + const mp_obj_type_t *t = MP_OBJ_TO_PTR(obj); + if (mp_obj_is_instance_type(t)) { + dest[0] = MP_OBJ_FROM_PTR(t->parent); + return MP_OBJ_SENTINEL; // success + } + } + #endif + + if (attr == MP_QSTR___next__ && type->iternext != NULL) { + dest[0] = MP_OBJ_FROM_PTR(&mp_builtin_next_obj); + dest[1] = obj; + + } else if (type->attr != NULL) { + // this type can do its own load, so call it + type->attr(obj, attr, dest); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + } else if (type->locals_dict != NULL) { + // generic method lookup + // this is a lookup in the object (ie not class or type) + assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &type->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + mp_convert_member_lookup(obj, type, elem->value, dest); + } + } + #if MICROPY_PY_FUNCTION_ATTRS //PMPP + else if (attr == MP_QSTR___name__) { + if ( type->name == MP_QSTR_function) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(obj)); + } + } + #endif + return MP_OBJ_SENTINEL; // success +} + +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { + DEBUG_OP_printf("load method %p.%s\n", base, qstr_str(attr)); + if (!base){ + cdbg("1146:load method %p.%s\n", base, qstr_str(attr)); + return MP_OBJ_NULL; + } + if (mp_load_method_maybe(base, attr, dest) == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + + if (dest[0] == MP_OBJ_NULL) { + // no attribute/method called attr + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_AttributeError, "no such attribute"); + } else { + // following CPython, we give a more detailed error message for type objects + if (mp_obj_is_type(base, &mp_type_type)) { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("type object '%q' has no attribute '%q'"), + ((mp_obj_type_t *)MP_OBJ_TO_PTR(base))->name, attr)); + } else { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("'%s' object has no attribute '%q'"), + mp_obj_get_type_str(base), attr)); + } + } + } + + return MP_OBJ_SENTINEL; // success +} + +// Acts like mp_load_method_maybe but catches AttributeError, and all other exceptions if requested +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc) { + if (mp_load_method_maybe(obj, attr, dest) == MP_OBJ_NULL) { + // exception + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + if (!catch_all_exc + && !mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), + MP_OBJ_FROM_PTR(&mp_type_AttributeError))) { + // Re-raise the exception + return mp_raise_o(MP_OBJ_FROM_PTR(exc)); + } + } + return MP_OBJ_SENTINEL; // success +} + +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) { + DEBUG_OP_printf("store attr %p.%s <- %p\n", base, qstr_str(attr), value); + const mp_obj_type_t *type = mp_obj_get_type(base); + if (type->attr != NULL) { + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, value}; + type->attr(base, attr, dest); + if (MP_STATE_THREAD(active_exception) != NULL) { + // exception + return MP_OBJ_NULL; + } + if (dest[0] == MP_OBJ_NULL) { + // success + return MP_OBJ_SENTINEL; + } + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); + #else + mp_raise_or_return(mp_obj_new_exception_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) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1219:object not iterable")); + const mp_obj_type_t *type = mp_obj_get_type(o_in); + + // Check for native getiter which is the identity. We handle this case explicitly + // so we don't unnecessarily allocate any RAM for the iter_buf, which won't be used. + if (type->getiter == mp_identity_getiter) { + return o_in; + } + + // check for native getiter (corresponds to __iter__) + if (type->getiter != NULL) { + if (iter_buf == NULL && type->getiter != mp_obj_instance_getiter) { + // if caller did not provide a buffer then allocate one on the heap + // mp_obj_instance_getiter is special, it will allocate only if needed + iter_buf = m_new_obj(mp_obj_iter_buf_t); + } + mp_obj_t iter = type->getiter(o_in, iter_buf); + if (iter != MP_OBJ_NULL) { + return iter; + } + } + + // check for __getitem__ + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___getitem__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __getitem__ exists, create and return an iterator + if (iter_buf == NULL) { + // if caller did not provide a buffer then allocate one on the heap + iter_buf = m_new_obj(mp_obj_iter_buf_t); + } + return mp_obj_new_getitem_iter(dest, iter_buf); + } + // object not iterable + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not iterable")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't iterable"), mp_obj_get_type_str(o_in))); + #endif + +} + +// may return MP_OBJ_STOP_ITERATION as an optimisation instead of raise StopIteration() +// may also raise StopIteration() +mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1265:NPE:object not an iterator")); + const mp_obj_type_t *type = mp_obj_get_type(o_in); + if (type->iternext != NULL) { + return type->iternext(o_in); + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result + return mp_call_method_n_kw(0, 0, dest); + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in))); + #endif + } + } +} + +// will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration() (or any subclass thereof) +// may raise other exceptions +mp_obj_t mp_iternext(mp_obj_t o_in) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1290:NPE:object not an iterator")); + if (MP_STACK_CHECK()) { // enumerate, filter, map and zip can recursively call mp_iternext + return MP_OBJ_NULL; + } + const mp_obj_type_t *type = mp_obj_get_type(o_in); + if (type->iternext != NULL) { + return type->iternext(o_in); + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result + mp_obj_t ret = mp_call_method_n_kw(0, 0, dest); + if (ret != MP_OBJ_NULL) { + return ret; + } else { + // exception + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + MP_STATE_THREAD(active_exception) = NULL; + return MP_OBJ_STOP_ITERATION; + } else { + // reraise + return MP_OBJ_NULL; + } + } + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in))); + #endif + } + } +} + +mp_obj_t mp_iternext2(mp_obj_t o) { + if (o == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + o = mp_iternext(o); + if (o == MP_OBJ_STOP_ITERATION) { + MP_STATE_THREAD(active_exception) = (void*)1; + return MP_OBJ_NULL; + } + return o; +} + +bool mp_iternext_had_exc(void) { + if (MP_STATE_THREAD(active_exception) == (void*)1) { + MP_STATE_THREAD(active_exception) = NULL; + return false; + } + return MP_STATE_THREAD(active_exception) != NULL; +} + +#pragma message "TODO: Unclear what to do with StopIteration 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)); + const mp_obj_type_t *type = mp_obj_get_type(self_in); + + if (type == &mp_type_gen_instance) { + return mp_obj_gen_resume(self_in, send_value, throw_value, ret_val); + } + + if (type->iternext != NULL && send_value == mp_const_none) { + mp_obj_t ret = type->iternext(self_in); + if (ret == MP_OBJ_NULL) { + // exception + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } + *ret_val = ret; + if (ret != MP_OBJ_STOP_ITERATION) { + return MP_VM_RETURN_YIELD; + } else { + // Emulate raise StopIteration() + // Special case, handled in vm.c + return MP_VM_RETURN_NORMAL; + } + } + + mp_obj_t dest[3]; // Reserve slot for send() arg + + // Python instance iterator protocol + if (send_value == mp_const_none) { + mp_load_method_maybe(self_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + *ret_val = mp_call_method_n_kw(0, 0, dest); + if (*ret_val == MP_OBJ_NULL) { + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } + return MP_VM_RETURN_YIELD; + } + } + + // Either python instance generator protocol, or native object + // generator protocol. + if (send_value != MP_OBJ_NULL) { + mp_load_method(self_in, MP_QSTR_send, dest); + dest[2] = send_value; + *ret_val = mp_call_method_n_kw(1, 0, dest); + return MP_VM_RETURN_YIELD; + } + + assert(throw_value != MP_OBJ_NULL); + { + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(throw_value)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { + mp_load_method_maybe(self_in, MP_QSTR_close, dest); + if (dest[0] != MP_OBJ_NULL) { + // TODO: Exceptions raised in close() are not propagated, + // printed to sys.stderr + *ret_val = mp_call_method_n_kw(0, 0, dest); + // We assume one can't "yield" from close() + return MP_VM_RETURN_NORMAL; + } + } else { + mp_load_method_maybe(self_in, MP_QSTR_throw, dest); + if (dest[0] != MP_OBJ_NULL) { + dest[2] = throw_value; + *ret_val = mp_call_method_n_kw(1, 0, dest); + // If .throw() method returned, we assume it's value to yield + // - any exception would be thrown with nlr_raise(). + return MP_VM_RETURN_YIELD; + } + } + // If there's nowhere to throw exception into, then we assume that object + // is just incapable to handle it, so any exception thrown into it + // will be propagated up. This behavior is approved by test_pep380.py + // test_delegation_of_close_to_non_generator(), + // test_delegating_throw_to_non_generator() + if (mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError + *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, "generator raised StopIteration"); + } else { + *ret_val = mp_make_raise_obj(throw_value); + } + return MP_VM_RETURN_EXCEPTION; + } +} + +mp_obj_t mp_make_raise_obj(mp_obj_t o) { + DEBUG_printf("raise %p\n", o); + if (mp_obj_is_exception_type(o)) { + // o is an exception type (it is derived from BaseException (or is BaseException)) + // create and return a new exception instance by calling o + // TODO could have an option to disable traceback, then builtin exceptions (eg TypeError) + // could have const instances in ROM which we return here instead + return mp_call_function_n_kw(o, 0, 0, NULL); + } else if (mp_obj_is_exception_instance(o)) { + // o is an instance of an exception, so use it as the exception + return o; + } else { + // o cannot be used as an exception, so return a type error (which will be raised by the caller) + return mp_obj_new_exception_msg(&mp_type_TypeError, "exceptions must derive from BaseException"); + } +} + +mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { + DEBUG_printf("import name '%s' level=%d\n", qstr_str(name), MP_OBJ_SMALL_INT_VALUE(level)); + + // build args array + mp_obj_t args[5]; + args[0] = MP_OBJ_NEW_QSTR(name); + args[1] = mp_const_none; // TODO should be globals + args[2] = mp_const_none; // TODO should be locals + args[3] = fromlist; + args[4] = level; + + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Lookup __import__ and call that if it exists + mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); + if (bo_dict != NULL) { + mp_map_elem_t *import = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); + if (import != NULL) { + return mp_call_function_n_kw(import->value, 5, 0, args); + } + } + #endif + + return mp_builtin___import__(5, args); +} + +mp_obj_t mp_import_from(mp_obj_t module, qstr name) { + DEBUG_printf("import from %p %s\n", module, qstr_str(name)); + + mp_obj_t dest[2]; + + mp_load_method_maybe(module, name, dest); + + if (dest[1] != MP_OBJ_NULL) { + // Hopefully we can't import bound method from an object +import_error: + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("cannot import name %q"), name)); + } + + if (dest[0] != MP_OBJ_NULL) { + return dest[0]; + } + + #if MICROPY_ENABLE_EXTERNAL_IMPORT + + // See if it's a package, then can try FS import + if (!mp_obj_is_package(module)) { + goto import_error; + } + + mp_load_method_maybe(module, MP_QSTR___name__, dest); + size_t pkg_name_len; + const char *pkg_name = mp_obj_str_get_data(dest[0], &pkg_name_len); + + const uint dot_name_len = pkg_name_len + 1 + qstr_len(name); + char *dot_name = mp_local_alloc(dot_name_len); + memcpy(dot_name, pkg_name, pkg_name_len); + dot_name[pkg_name_len] = '.'; + memcpy(dot_name + pkg_name_len + 1, qstr_str(name), qstr_len(name)); + qstr dot_name_q = qstr_from_strn(dot_name, dot_name_len); + mp_local_free(dot_name); + + // For fromlist, pass sentinel "non empty" value to force returning of leaf module + return mp_import_name(dot_name_q, mp_const_true, MP_OBJ_NEW_SMALL_INT(0)); + + #else + + // Package import not supported with external imports disabled + goto import_error; + + #endif +} + +void mp_import_all(mp_obj_t module) { + DEBUG_printf("import all %p\n", module); + + // TODO: Support __all__ + mp_map_t *map = &mp_obj_module_get_globals(module)->map; + for (size_t i = 0; i < map->alloc; i++) { + if (mp_map_slot_is_filled(map, i)) { + // Entry in module global scope may be generated programmatically + // (and thus be not a qstr for longer names). Avoid turning it in + // qstr if it has '_' and was used exactly to save memory. + const char *name = mp_obj_str_get_str(map->table[i].key); + if (*name != '_') { + qstr qname = mp_obj_str_get_qstr(map->table[i].key); + mp_store_name(qname, map->table[i].value); + } + } + } +} + +#if MICROPY_ENABLE_COMPILER + +mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_input_kind, mp_obj_dict_t *globals, mp_obj_dict_t *locals) { + // save context + mp_obj_dict_t *volatile old_globals = mp_globals_get(); + mp_obj_dict_t *volatile old_locals = mp_locals_get(); + + // set new context + mp_globals_set(globals); + mp_locals_set(locals); + + 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 ret = MP_OBJ_NULL; + if (module_fun != MP_OBJ_NULL) { + if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { + // for compile only, return value is the module function + ret = module_fun; + } else { + // execute module function and get return value + ret = mp_call_function_0(module_fun); + } + } + + // restore context and return value + mp_globals_set(old_globals); + mp_locals_set(old_locals); + return ret; +} + +#endif // MICROPY_ENABLE_COMPILER + +#if NO_NLR +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_or_return_value( mp_obj_new_exception_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")), NULL); + } + #endif + mp_raise_or_return_value(mp_obj_new_exception_msg_varg(&mp_type_MemoryError, + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes), NULL); +} +#else +NORETURN void m_malloc_fail(size_t num_bytes) { + DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); + #if MICROPY_ENABLE_GC + if (gc_is_locked()) { + mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")); + } + #endif + mp_raise_msg_varg(&mp_type_MemoryError, + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes); + for(;;){} +} +#endif + +#if NO_NLR +#else +#error "not valid for nlr" +void mp_raise_msg(const mp_obj_type_t *exc_type, const char *msg) { + if (msg == NULL) { + mp_raise_o(mp_obj_new_exception(exc_type)); + } else { + mp_raise_o(mp_obj_new_exception_msg(exc_type, msg)); + } +} + +void mp_raise_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); + va_end(args); + nlr_raise(exc); +} + +void mp_raise_ValueError(const char *msg) { + mp_raise_msg(&mp_type_ValueError, msg); +} + +void mp_raise_TypeError(const char *msg) { + mp_raise_msg(&mp_type_TypeError, msg); +} + +void mp_raise_OSError(int errno_) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_))); +} + +void mp_raise_NotImplementedError(const char *msg) { + mp_raise_msg(&mp_type_NotImplementedError, msg); +} +#endif + +#if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK +void mp_raise_recursion_depth(void) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_RuntimeError, + MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded))); +} +#endif + +mp_obj_t mp_raise_o(mp_obj_t exc) { + //printf("mp_raise_o(%p)\n", MP_OBJ_TO_PTR(exc)); + // don't overwrite an existing exception + if (MP_STATE_THREAD(active_exception) == NULL) { + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(exc); + } + return MP_OBJ_NULL; +} + +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg) { + if (msg == NULL) { + return mp_raise_o(mp_obj_new_exception(exc_type)); + } else { + return mp_raise_o(mp_obj_new_exception_msg(exc_type, msg)); + } +} + +mp_obj_t mp_raise_ValueError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_ValueError, msg); +} + +mp_obj_t mp_raise_TypeError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_TypeError, msg); +} + +mp_obj_t mp_raise_NotImplementedError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_NotImplementedError, msg); +} + +mp_obj_t mp_raise_OSError_o(int errno_) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_))); +} diff --git a/ports/wapy/runtime_no_nlr.h b/ports/wapy/runtime_no_nlr.h new file mode 100644 index 000000000..fadb7e6cc --- /dev/null +++ b/ports/wapy/runtime_no_nlr.h @@ -0,0 +1,223 @@ +/* + * 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. + */ +#ifndef MICROPY_INCLUDED_PY_RUNTIME_H +#define MICROPY_INCLUDED_PY_RUNTIME_H + +#include "py/mpstate.h" +#include "py/pystack.h" + +typedef enum { + MP_VM_RETURN_NORMAL, + MP_VM_RETURN_YIELD, + MP_VM_RETURN_EXCEPTION, +} mp_vm_return_kind_t; + +typedef enum { + MP_ARG_BOOL = 0x001, + MP_ARG_INT = 0x002, + MP_ARG_OBJ = 0x003, + MP_ARG_KIND_MASK = 0x0ff, + MP_ARG_REQUIRED = 0x100, + MP_ARG_KW_ONLY = 0x200, +} mp_arg_flag_t; + +typedef union _mp_arg_val_t { + bool u_bool; + mp_int_t u_int; + mp_obj_t u_obj; + mp_rom_obj_t u_rom_obj; +} mp_arg_val_t; + +typedef struct _mp_arg_t { + uint16_t qst; + uint16_t flags; + mp_arg_val_t defval; +} mp_arg_t; + +// Tables mapping operator enums to qstrs, defined in objtype.c +extern const byte mp_unary_op_method_name[]; +extern const byte mp_binary_op_method_name[]; + +void mp_init(void); +void mp_deinit(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); +} +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +#endif + +// extra printing method specifically for mp_obj_t's which are integral type +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); + +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); +static inline int mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { + return mp_arg_check_num_sig(n_args, n_kw, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw)); +} +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +void mp_arg_error_terse_mismatch(void); +void mp_arg_error_unimpl_kw(void); + +static inline mp_obj_dict_t *mp_locals_get(void) { + return MP_STATE_THREAD(dict_locals); +} +static inline void mp_locals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_locals) = d; +} +static inline mp_obj_dict_t *mp_globals_get(void) { + return MP_STATE_THREAD(dict_globals); +} +static inline void mp_globals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_globals) = d; +} + +mp_obj_t mp_load_name(qstr qst); +mp_obj_t mp_load_global(qstr qst); +mp_obj_t mp_load_build_class(void); +void mp_store_name(qstr qst, mp_obj_t obj); +void mp_store_global(qstr qst, mp_obj_t obj); +mp_obj_t mp_delete_name(qstr qst); +mp_obj_t mp_delete_global(qstr qst); + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg); +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs); + +mp_obj_t mp_call_function_0(mp_obj_t fun); +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); +mp_obj_t mp_call_function_n_kw(mp_obj_t fun, size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_self_n_kw(mp_obj_t meth, mp_obj_t self, size_t n_args, size_t n_kw, const mp_obj_t *args); +// Call function and catch/dump exception - for Python callbacks from C code +// (return MP_OBJ_NULL in case of exception). +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); + +typedef struct _mp_call_args_t { + mp_obj_t fun; + size_t n_args, n_kw, n_alloc; + mp_obj_t *args; +} mp_call_args_t; + +#if MICROPY_STACKLESS +// Takes arguments which are the most general mix of Python arg types, and +// prepares argument array suitable for passing to ->call() method of a +// function object (and mp_call_function_n_kw()). +// (Only needed in stackless mode.) +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); +#endif + +mp_obj_t mp_unpack_sequence(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_unpack_ex(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value); +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr); +void mp_convert_member_lookup(mp_obj_t obj, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest); +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc); +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest); +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val); + +mp_obj_t mp_getiter(mp_obj_t o, mp_obj_iter_buf_t *iter_buf); +mp_obj_t mp_iternext_allow_raise(mp_obj_t o); // may return MP_OBJ_STOP_ITERATION instead of raising StopIteration() +mp_obj_t mp_iternext(mp_obj_t o); // will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration(...) +mp_obj_t mp_iternext2(mp_obj_t o); +bool mp_iternext_had_exc(void); +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); + +mp_obj_t mp_make_raise_obj(mp_obj_t o); + +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) +void mp_raise_msg(const mp_obj_type_t *exc_type, const char *msg); +void mp_raise_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); +void mp_raise_ValueError(const char *msg); +void mp_raise_TypeError(const char *msg); +void mp_raise_NotImplementedError(const char *msg); +void mp_raise_OSError(int errno_); +void mp_raise_recursion_depth(void); + +mp_obj_t mp_raise_o(mp_obj_t exc); +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg); +mp_obj_t mp_raise_ValueError_o(const char *msg); +mp_obj_t mp_raise_TypeError_o(const char *msg); +mp_obj_t mp_raise_NotImplementedError_o(const char *msg); +mp_obj_t mp_raise_OSError_o(int errno_); + + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG +#undef mp_check_self +#define mp_check_self(pred) +#else +// A port may define to raise TypeError for example +#ifndef mp_check_self +#define mp_check_self(pred) assert(pred) +#endif +#endif + +// helper functions for native/viper code +int mp_native_type_from_qstr(qstr qst); +mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type); +mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type); + +#define mp_sys_path (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_path_obj))) +#define mp_sys_argv (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_argv_obj))) + +#if MICROPY_WARNINGS +#ifndef mp_warning +void mp_warning(const char *category, const char *msg, ...); +#endif +#else +#define mp_warning(...) +#endif + +#define mp_raise_or_return(exc) { return mp_raise_o(exc) } +#define mp_raise_or_return_value(exc, retval) { mp_raise_o(exc); return retval; } +#define mp_raise_OSError(errno) { return mp_raise_OSError_o(errno); } +#define mp_raise_OSError_or_return(errno, retval) { mp_raise_OSError_o(errno); return retval; } +#define mp_raise_type(extype) { return mp_raise_o(mp_obj_new_exception(extype)); } +#define mp_raise_type_or_return(extype, retval) { mp_raise_o(mp_obj_new_exception(extype)); return retval } +#define mp_raise_ValueError(v) { return mp_raise_ValueError_o(v); } +#define mp_raise_msg(extype, mpt) { return mp_raise_msg_o( mp_obj_new_exception(extype), mpt ); } +#define mp_raise_TypeError(mpt) { return mp_raise_TypeError_o(mpt); } +#define mp_raise_TypeError_or_return(mpt, retval) { mp_raise_TypeError_o(mpt); return retval } +#define mp_raise_NotImplementedError(mpt) { return mp_raise_NotImplementedError_o(mpt); } + + + +#endif // MICROPY_INCLUDED_PY_RUNTIME_H diff --git a/ports/wapy/stubs/emscripten.h b/ports/wapy/stubs/emscripten.h new file mode 100644 index 000000000..6749a8124 --- /dev/null +++ b/ports/wapy/stubs/emscripten.h @@ -0,0 +1,47 @@ +#ifndef EMSCRIPTEN_H + +#define EM_ASM_INT(...) 0 +#define EM_ASM(...) + +typedef void (*funcptr)(); + +#if __MAIN__ + +int emscripten_GetProcAddress(const char * name) { + return 0; +} + +int emscripten_run_preload_plugins(const char* file, char * onload, char * onerror) { + return 0; +} + +void emscripten_sleep(int s) { +} + + +static int emscripten_loop_run = 1; + +void emscripten_cancel_main_loop(){ + emscripten_loop_run = 0; +}; + +void emscripten_set_main_loop(funcptr emfunc, int a, int b){ + while(emscripten_loop_run){ + emfunc(); + } +} + +#else + +extern int emscripten_GetProcAddress(const char * name); +extern int emscripten_run_preload_plugins(const char* file, char * onload, char * onerror); +extern void emscripten_sleep(int s); +//extern int emscripten_loop_run; +extern void emscripten_cancel_main_loop(); +extern void emscripten_set_main_loop(funcptr emfunc, int a, int b); + +#endif + +#define EMSCRIPTEN_H + +#endif diff --git a/ports/wapy/upython.c b/ports/wapy/upython.c new file mode 100644 index 000000000..38a32bdfa --- /dev/null +++ b/ports/wapy/upython.c @@ -0,0 +1,288 @@ +// +// core/main.c +// core/vfs.c + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +#include "../wapy/upython.h" + +#include "../wapy/core/ringbuf_o.h" +#include "../wapy/core/ringbuf_b.h" + +//static +struct wPyInterpreterState i_main; +//static +struct wPyThreadState i_state; + +RBB_T(out_rbb, 16384); + + + +EMSCRIPTEN_KEEPALIVE int +show_os_loop(int state) { + int last = SHOW_OS_LOOP; + if (state >= 0) { + SHOW_OS_LOOP = state; + if (state > 0) { + + fprintf( +#if __WASM__ + stderr, +#else + stdout, +#endif +"------------- showing os loop / starting repl --------------\n"); + //repl_started = 1; + } else { + if (last != state) + fprintf(stdout, "------------- hiding os loop --------------\n"); + } + } + return (last > 0); +} + +EMSCRIPTEN_KEEPALIVE int +state_os_loop(int state) { + static int last_state = -666; + + if (!state) { + last_state = SHOW_OS_LOOP; + SHOW_OS_LOOP = 0; + } else { + if (last_state != -666) + SHOW_OS_LOOP = last_state; + last_state = -666; + } + return last_state; +} + +// ---- + + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + +char * +shm_get_ptr(int major, int minor) { + // keyboards + if (major == IO_KBD) { + if (minor == 0) + return &i_main.shm_stdio[IO_KBD]; + } + return NULL; +} + + + +char * +wPy_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + //pyexec_event_repl_init(); + return shm_ptr(); +} + +void +wPy_Initialize() { + int stack_dummy; + stack_top = (char *) &stack_dummy; + +#if MICROPY_ENABLE_GC + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); +#endif + +//#if MICROPY_ENABLE_PYSTACK + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); +//#endif + + mp_init(); + +#if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; +#else + //(void)emit_opt; +#endif + + + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + +// TODO py/stackctrl.c#L40 check stack way + + +#if !MICROPY_ENABLE_FINALISER +#error requires MICROPY_ENABLE_FINALISER (1) +#endif + + +#undef gc_collect + + + + +//extern void gc_collect_root(void **ptrs, size_t len); + +#include "py/gc.h" + +#undef gc_collect + + +// GC boundaries + + +// The address of the top of the stack when we first saw it +// This approximates a pointer to the bottom of the stack, but +// may not necessarily _be_ the exact bottom. This is set by +// the entry point of the application +static void *stack_initial = NULL; +// Pointer to the end of the stack +static uintptr_t stack_max = (uintptr_t) NULL; +// The current stack pointer +static uintptr_t stack_ptr_val = (uintptr_t) NULL; +// The amount of stack remaining +static ptrdiff_t stack_left = 0; + +// Maximum stack size of 248k +// This limit is arbitrary, but is what works for me under Node.js when compiled with emscripten +static size_t stack_limit = 1024 * 248 * 1; + + +void +gc_collect(void) { + + gc_dump_info(); + + stack_ptr_val = (uintptr_t) __builtin_frame_address(0); + + clog("gc_collect_start"); + + gc_collect_start(); + + size_t bottom = (uintptr_t) stack_ptr_val; + + void **ptrs = (void **) (void *) stack_ptr_val; + + size_t len = ((uintptr_t) stack_initial - bottom) / sizeof(uintptr_t); + + clog("gc_collect stack_initial=%p bottom=%zu len=%zu", stack_initial, bottom, len); + +//343 + + gc_collect_root(ptrs, len); +//343 + +#if MICROPY_PY_THREAD + mp_thread_gc_others(); +#endif +#if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); +#endif + + clog("gc_collect_end"); + gc_collect_end(); +} + +// #endif // gc + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stdout, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + 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; + mp_raise_o(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stdout, "150:NULL FUNCTION %s[%s]\n", qstr_str(source_name), src); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +#if defined(__EMSCRIPTEN__) || defined(__WASM__) + #include "../wapy-wasm/wasm_mphal.c" +#endif + +#if __ANDROID__ + #include "../wapy-unix/aosp_mphal.c" +#endif + +int +PyRun_IO_CODE() { + return pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + +/* +EMSCRIPTEN_KEEPALIVE int +repl_run(int warmup) { + if (warmup == 1) + return MP_IO_SHM_SIZE; + //wPy_NewInterpreter(); + repl_started = MP_IO_SHM_SIZE; + return 1; +} +*/ diff --git a/ports/wapy/upython.h b/ports/wapy/upython.h new file mode 100644 index 000000000..af7e1002d --- /dev/null +++ b/ports/wapy/upython.h @@ -0,0 +1,89 @@ +#ifndef UPYTHON_H + +#include + +#include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/repl.h" +#include "py/gc.h" +#include "lib/utils/pyexec.h" +#include "py/mphal.h" + +#define IS_FILE 1 +#define IS_STR 0 + +#ifdef __EMSCRIPTEN__ + #include "emscripten.h" +#else + #define EMSCRIPTEN_KEEPALIVE +#endif +extern size_t bsd_strlen(const char *str); + +//EMSCRIPTEN_KEEPALIVE void Py_InitializeEx(int param); +//EMSCRIPTEN_KEEPALIVE void PyRun_SimpleString(const char * code); + +#if WASM_FILE_API +EMSCRIPTEN_KEEPALIVE void PyRun_SimpleFile(FILE *fp, const char *filename); +EMSCRIPTEN_KEEPALIVE void PyRun_VerySimpleFile(const char *filename); +#endif + +int do_code(const char *src, int is_file); + +#define PATHLIST_SEP_CHAR ':' + +#if WAPY + + #if MICROPY_ENABLE_GC + static char heap[32*1024*1024]; + #endif +static int repl_started = -100; + +#endif + +#define REPL_INPUT_SIZE 16384 +#define REPL_INPUT_MAX REPL_INPUT_SIZE-1 + + +#define nullptr NULL +#define Py_RETURN_NONE return nullptr; +#define PyObject mp_obj_t + + +#include "core/ringbuf_b.h" +#include "core/ringbuf_o.h" + + +int PyArg_ParseTuple(PyObject *argv, const char *fmt, ...); + +extern int VMFLAGS_IF; +extern int SHOW_OS_LOOP; +extern int show_os_loop(int state); + +struct +wPyInterpreterState { + char *shm_stdio ; + char *shm_input_event_0; +}; + + +struct +wPyThreadState { + struct wPyInterpreterState *interp; +}; + +extern struct wPyInterpreterState i_main ; +extern struct wPyThreadState i_state ; + + + +#define cdbg(...) if (1){ fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } +#define clog(...) if (show_os_loop(-1)){ fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } + + + + +#define UPYTHON_H 1 + +#endif //UPYTHON_H + diff --git a/ports/wapy/vmsl/vm_loop.c b/ports/wapy/vmsl/vm_loop.c new file mode 100644 index 000000000..782d88449 --- /dev/null +++ b/ports/wapy/vmsl/vm_loop.c @@ -0,0 +1,449 @@ + static size_t iolen; + + if (VMOP>= VMOP_PAUSE) { + VMOP--; + if ( (iolen = has_io()) ) { + // we have a chance to process pending Inputs, so ... + cdbg("syscall/pause WITH IO=%lu", iolen ); + noint_aio_fsync(); + } else { + clog("syscall/pause -> io flush"); + // else just jump to flush Outputs + } + goto VM_syscall; + } + +// TODO: is it usefull ? + if ( (ENTRY_POINT != JMP_NONE) && !JUMPED_IN) { + clog("re-enter-on-entry %d => %d\n", ctx_current, CTX.pointer); + void* jump_entry; + jump_entry = ENTRY_POINT; + // Never to re-enter as this point. can only use the previous exit point. + JUMPED_IN = 1; + goto *jump_entry; + } + +// ========================================================================================== + // this call the async loop , no preemption should be allowed in there. + noint_aio_fsync(); +// ========================================================================================== + + // return to where we were going just before giving hand to host + if ( (EXIT_POINT != JMP_NONE) && JUMPED_IN) { + //cdbg("re-enter-on-exit IO=%lu", strlen(io_stdin) ); + + // was it gosub + if (JUMP_TYPE == TYPE_SUB) + RETURN; + + // was it branching + if (JUMP_TYPE == TYPE_JUMP) + COME_FROM; + } + + +// All I/O stuff IS WRONG and should use a circular buffer. + //fprintf(stdout," runloop\n"); + + if (io_stdin[0]) { + //then it is toplevel or TODO: it's sync top level ( tranpiled by aio on the heap) + def_PyRun_SimpleString_is_repl = true; +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + JUMP( def_PyRun_SimpleString, "main_loop_or_step_repl"); +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + // mark done + IO_CODE_DONE; + pyexec_repl_repl_restart(0); + def_PyRun_SimpleString_is_repl = false; + } + + + + //now flush kbd port + char *keybuf; + keybuf = shm_get_ptr( IO_KBD, 0); + // only when scripting interface is idle and repl ready + while (!KPANIC) { + // should give a way here to discard repl events feeding "await input()" instead + int rx = keybuf[0] ; //& 0xFF; + if (rx) { + // if (rx==12) fprintf(stdout,"IO(12): Form Feed "); //clear screen + //if (rx>127) cdbg("FIXME:stdin-utf8:%u", rx ); + //pyexec_event_repl_process_char(rx); + if (pyexec_friendly_repl_process_char(rx)<0) + cdbg("REPL[%s]", io_stdin); + *keybuf++ = 0; + } else break; + } + + // always reset io buffer no matter what since io_stdin can overwrite it in script mode + keybuf[0]=0; + + + if (VMOP==VM_HCF) { +VM_stackmess:; + puts("no guru meditation, bye"); + #if !ASYNCIFY + emscripten_cancel_main_loop(); + #endif + } +goto VM_syscall; + +//================================================================== + + +// def PyRun_SimpleString(const_char_p src) -> int; +def_PyRun_SimpleString: { +//return: + // should set a global integer + //int ret = 0; +//args: + char* src = i_main.shm_stdio; + mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT; + +//vars + +//code + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + + if (lex == NULL) { + clog("syntax error"); + handle_uncaught_exception(); + } else { + qstr source_name = lex->source_name; + + mp_obj_t exret = MP_OBJ_NULL; + + #if MICROPY_PY___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, def_PyRun_SimpleString_is_repl); + + + if ( module_fun != MP_OBJ_NULL) { + //STACKLESS STARTS HERE + + // unlock I/O buffer + IO_CODE_DONE; + + CTX.self_in = module_fun; + CTX.n_args = 0; + CTX.n_kw = 0; + CTX.args = NULL ; + + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + exret = MP_OBJ_NULL; + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + exret = SUBVAL; //CTX.sub_value; + } else { + exret = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + } + + if ( exret != MP_OBJ_NULL ) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + clog("645: PENDING EXCEPTION CLEARED AND RAISED"); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + } + } + + } + + // ex check + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("656: uncaught exception") + //mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + //handle_uncaught_exception(); + if (uncaught_exception_handler()) { + clog("689:SystemExit"); + } else { + clog("691: exception done"); + } + } + RETVAL = exret ; + } + + COME_FROM; +} // PyRun_SimpleString + + +#define VM_CANCEL_ACTIVE_FINALLY(sptr) do { \ + if (mp_obj_is_small_int(sptr[-1])) { \ + /* Stack: (..., prev_dest_ip, prev_cause, dest_ip) */ \ + /* Cancel the unwind through the previous finally, replace with current one */ \ + sptr[-2] = sptr[0]; \ + sptr -= 2; \ + } else { \ + assert(sptr[-1] == mp_const_none || mp_obj_is_exception_instance(sptr[-1])); \ + /* Stack: (..., None/exception, dest_ip) */ \ + /* Silence the finally's exception value (may be None or an exception) */ \ + sptr[-1] = sptr[0]; \ + --sptr; \ + } \ +} while (0) + + +#if MICROPY_PY_SYS_SETTRACE + +#define FRAME_SETUP() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_ENTER() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + CTX.code_state->prev_state = MP_STATE_THREAD(current_code_state); \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + if (!mp_prof_is_executing) { \ + mp_prof_frame_enter(CTX.code_state); \ + } \ +} while(0) + +#define FRAME_LEAVE() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state->prev_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_UPDATE() do { \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing) { \ + CTX.code_state->frame = MP_OBJ_TO_PTR(mp_prof_frame_update(CTX.code_state)); \ + } \ +} while(0) + +#define TRACE_TICK(current_ip, current_sp, is_exception) do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing && CTX.code_state->frame && MP_STATE_THREAD(prof_trace_callback)) { \ + MP_PROF_INSTR_DEBUG_PRINT(code_state->ip); \ + } \ + if (!mp_prof_is_executing && CTX.code_state->frame && CTX.code_state->frame->callback) { \ + mp_prof_instr_tick(CTX.code_state, is_exception); \ + } \ +} while(0) + +#else // MICROPY_PY_SYS_SETTRACE + +#define FRAME_SETUP() +#define FRAME_ENTER() +#define FRAME_LEAVE() +#define FRAME_UPDATE() +#define TRACE_TICK(current_ip, current_sp, is_exception) + +#endif // MICROPY_PY_SYS_SETTRACE + + +def_mp_call_function_n_kw: { + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + RETVAL = SUBVAL; //CTX.sub_value; + } else { +#if VMTRACE +clog(" 899: native call"); +#endif + RETVAL = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + clog("919:def_mp_call_function_n_kw ex!"); + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + RETVAL = MP_OBJ_NULL; + } + + RETURN; +} + + +#define VM_DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ + { \ + n_state_out_var = mp_decode_uint_value(bytecode); \ + size_t n_exc_stack = mp_decode_uint_value(mp_decode_uint_skip(bytecode)); \ + \ + state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + + n_exc_stack * sizeof(mp_exc_stack_t); \ + } + + + + +/* NOT OK + + when a function has tuple with default value init style the state_size calculated by + VM_DECODE_CODESTATE_SIZE is just huge and wrong. + +*/ + +//266:objfun.c +def_func_bc_call: { + + RETVAL = MP_OBJ_NULL; + + if (MP_STACK_CHECK()) { + clog("974:def_func_bc_call: MP_STACK_CHECK ex!"); + goto def_func_bc_call_ret; + } + + CTX.self_fun = MP_OBJ_TO_PTR(CTX.self_in); + + VM_DECODE_CODESTATE_SIZE(CTX.self_fun->bytecode, CTX.n_state, CTX.state_size); + + + // allocate state for locals and stack + // new frame == new code state. + if ( CTX.state_size > 41360 ) { +#define TRACE_ON (1) + #include "vmsl/vmbc_trace.c" +#undef TRACE_ON + cdbg("882:BUG: =======> start=%p cur=%p end=%p state_size=%ld", + MP_STATE_THREAD(pystack_start), MP_STATE_THREAD(pystack_cur), MP_STATE_THREAD(pystack_end) + , CTX.state_size); + +#pragma message "Silly workaround for func with tuple init" +#if 0 + if (CTX.code_state = fun_bc_call_pre(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args) ){ +#if 1 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, MP_OBJ_NULL); +#else + ctx_get_next(CTX_COPY); + NEXT.inject_exc = MP_OBJ_NULL; + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; +#endif + RETVAL = fun_bc_call_past(CTX.code_state, CTX.vm_return_kind, CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); +#endif + RETVAL = fun_bc_call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + goto def_func_bc_call_ret; + } + + CTX.code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + CTX.state_size); + + if (!CTX.code_state) { + clog("908:def_func_bc_call: MP_PYSTACK_ALLOC ex!"); + goto def_func_bc_call_ret; + } + + CTX.inject_exc = MP_OBJ_NULL ; + CTX.code_state->fun_bc = CTX.self_fun; + CTX.code_state->ip = 0; + CTX.code_state->n_state = CTX.n_state; + + clog("917:TODO: can we save old_globals before this call ?"); + + mp_obj_t ret = mp_setup_code_state(CTX.code_state, CTX.n_args, CTX.n_kw, CTX.args); + + //? + CTX.code_state->old_globals = mp_globals_get(); + + + if ( ret == MP_OBJ_NULL) { + clog("999:def_func_bc_call: INIT_CODESTATE ex!"); + mp_nonlocal_free(CTX.code_state, sizeof(mp_code_state_t)); + goto def_func_bc_call_ret; + } + + ctx_get_next(CTX_COPY); + + // execute the byte code with the correct globals context + mp_globals_set(NEXT.self_fun->globals); + + + if (VMFLAGS_IF>0) { // FIXED ! + clog("132:unwrap.c ALLOWINT def_func_bc_call->def_mp_execute_bytecode"); + + // ip sp would not be set on NEXT + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; + + } else { + clog("136:unwrap.c NOINTERRUPT"); + ctx_abort(); //128 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, CTX.inject_exc); + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + CTX.sub_value= *CTX.code_state->sp; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + CTX.sub_value= CTX.code_state->state[0]; + else + clog("1031:unwrap.c unrouted .vm_return_kind") + } + + } + + mp_globals_set(CTX.code_state->old_globals); + mp_pystack_free(CTX.code_state); + + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + #error "[...]" + #endif + +//353:objfun.c + // work in done juste before def_mp_execute_bytecode RETURN + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + RETVAL = CTX.sub_value; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + RETVAL = mp_raise_o(CTX.sub_value); + else + clog("1050:unwrap.c unrouted .sub_vm_return_kind") + } + + +def_func_bc_call_ret: + RETURN; +} + + +#include "vmsl/unwrap.c" + + +//================================================================== +// VM_syscall_verbose:; +// puts("-syscall-"); + +VM_syscall:; +// TODO: flush all at once + // STDOUT flush before eventually filling it again + if (!rbb_is_empty(&out_rbb)) { + // flush stdout + unsigned char out_c = 0; + fprintf(cc, "{\"%c\":\"", 48+1); + //TODO put a 0 at end and printf buffer directly + while (rbb_pop(&out_rbb, &out_c)) + fprintf(cc, "%c", out_c ); + fprintf(cc, "\"}\n"); + } diff --git a/ports/wapy/wapy.mk b/ports/wapy/wapy.mk new file mode 100644 index 000000000..417114076 --- /dev/null +++ b/ports/wapy/wapy.mk @@ -0,0 +1,130 @@ + +ifdef NDK + CC=$(NDK_CC) -march=armv7-a -mthumb --target=armv7-none-linux-androideabi19 --gcc-toolchain=$(NDK)/toolchains/llvm/prebuilt/linux-x86_64 --sysroot=$(NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 + + LD=$(NDK_LD) -L$(NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/19 + CFLAGS += -DANDROID_PLATFORM=android-19 -DAPP_PLATFORM=android-19 -D__ANDROID_API__=19 + LD_EXTRA += -fuse-ld=lld -L/data/cross/pydk/aosp/apkroot-armeabi-v7a/usr/lib -lffi -ldl -lm -lc -landroid + INC += -I/data/cross/pydk/aosp/apkroot-armeabi-v7a/usr/include -I../wapy/stubs + OG = -O0 -g3 + LD_SHARED = -shared +else + LD_EXTRA += -s FORCE_FILESYSTEM=1 --use-preload-plugins + LD_EXTRA += -L/data/cross/pydk/wasm/apkroot-wasm/usr/lib -lSDL2 -logg +endif + + +# py object files +PY_CORE_O_BASENAME = $(addprefix py/,\ + mpstate.o \ + malloc.o \ + gc.o \ + pystack.o \ + qstr.o \ + vstr.o \ + mpprint.o \ + unicode.o \ + mpz.o \ + reader.o \ + lexer.o \ + parse.o \ + scope.o \ + compile.o \ + emitcommon.o \ + emitbc.o \ + asmbase.o \ + asmx64.o \ + emitnx64.o \ + asmx86.o \ + emitnx86.o \ + asmthumb.o \ + emitnthumb.o \ + emitinlinethumb.o \ + asmarm.o \ + emitnarm.o \ + asmxtensa.o \ + emitnxtensa.o \ + emitinlinextensa.o \ + emitnxtensawin.o \ + formatfloat.o \ + parsenumbase.o \ + parsenum.o \ + emitglue.o \ + persistentcode.o \ + ../ports/wapy/runtime_no_nlr.o \ + runtime_utils.o \ + scheduler.o \ + nativeglue.o \ + pairheap.o \ + ringbuf.o \ + stackctrl.o \ + ../ports/wapy/argcheck_no_nlr.o \ + warning.o \ + profile.o \ + map.o \ + obj.o \ + ../ports/wapy/objarray_no_nlr.o \ + objattrtuple.o \ + objbool.o \ + objboundmeth.o \ + objcell.o \ + objclosure.o \ + objcomplex.o \ + objdeque.o \ + ../ports/wapy/objdict_no_nlr.o \ + objenumerate.o \ + objexcept.o \ + objfilter.o \ + objfloat.o \ + ../ports/wapy/objfun_no_nlr.o \ + objgenerator.o \ + objgetitemiter.o \ + objint.o \ + objint_longlong.o \ + objint_mpz.o \ + ../ports/wapy/objlist_no_nlr.o \ + objmap.o \ + objmodule.o \ + objobject.o \ + objpolyiter.o \ + objproperty.o \ + objnone.o \ + objnamedtuple.o \ + objrange.o \ + objreversed.o \ + objset.o \ + objsingleton.o \ + objslice.o \ + ../ports/wapy/objstr_no_nlr.o \ + objstrunicode.o \ + objstringio.o \ + objtuple.o \ + objtype.o \ + objzip.o \ + opmethods.o \ + sequence.o \ + stream.o \ + binary.o \ + ../ports/wapy/builtinimport_no_nlr.o \ + builtinevex.o \ + builtinhelp.o \ + modarray.o \ + ../ports/wapy/modbuiltins_no_nlr.o \ + modcollections.o \ + modgc.o \ + modio.o \ + modmath.o \ + modcmath.o \ + modmicropython.o \ + modstruct.o \ + modsys.o \ + moduerrno.o \ + modthread.o \ + vm.o \ + bc.o \ + showbc.o \ + repl.o \ + smallint.o \ + frozenmod.o \ + ) + diff --git a/ports/wapy/wasm_mphal.c b/ports/wapy/wasm_mphal.c new file mode 100644 index 000000000..a8306f8b5 --- /dev/null +++ b/ports/wapy/wasm_mphal.c @@ -0,0 +1,157 @@ +#include +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#elif __CPP__ + #define EMSCRIPTEN_KEEPALIVE +#endif + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif +} + + + + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;idev->config->name); + mp_printf(print, "%s", self->dev->name); } mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index ad5f54baa..79d759eb2 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -35,10 +35,50 @@ #include "py/runtime.h" #include "py/gc.h" #include "py/mphal.h" +#include "lib/utils/mpirq.h" #include "modmachine.h" +#if MICROPY_PY_MACHINE + +typedef struct _machine_pin_irq_obj_t { + mp_irq_obj_t base; + struct _machine_pin_irq_obj_t *next; + struct gpio_callback callback; +} machine_pin_irq_obj_t; + +STATIC const mp_irq_methods_t machine_pin_irq_methods; const mp_obj_base_t machine_pin_obj_template = {&machine_pin_type}; +void machine_pin_deinit(void) { + for (machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_list); irq != NULL; irq = irq->next) { + machine_pin_obj_t *pin = MP_OBJ_TO_PTR(irq->base.parent); + gpio_pin_interrupt_configure(pin->port, pin->pin, GPIO_INT_DISABLE); + gpio_remove_callback(pin->port, &irq->callback); + } + MP_STATE_PORT(machine_pin_irq_list) = NULL; +} + +STATIC void gpio_callback_handler(struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { + machine_pin_irq_obj_t *irq = CONTAINER_OF(cb, machine_pin_irq_obj_t, callback); + + #if MICROPY_STACK_CHECK + // This callback executes in an ISR context so the stack-limit check must be changed to + // use the ISR stack for the duration of this function (so that hard IRQ callbacks work). + char *orig_stack_top = MP_STATE_THREAD(stack_top); + size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); + MP_STATE_THREAD(stack_top) = (void *)&irq; + MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; + #endif + + mp_irq_handler(&irq->base); + + #if MICROPY_STACK_CHECK + // Restore original stack-limit checking values. + MP_STATE_THREAD(stack_top) = orig_stack_top; + MP_STATE_THREAD(stack_limit) = orig_stack_limit; + #endif +} + STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pin_obj_t *self = self_in; mp_printf(print, "", self->port, self->pin); @@ -151,6 +191,66 @@ STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) +STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_INT_EDGE_BOTH} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (self->irq == NULL) { + machine_pin_irq_obj_t *irq; + for (irq = MP_STATE_PORT(machine_pin_irq_list); irq != NULL; irq = irq->next) { + machine_pin_obj_t *irq_pin = MP_OBJ_TO_PTR(irq->base.parent); + if (irq_pin->port == self->port && irq_pin->pin == self->pin) { + break; + } + } + if (irq == NULL) { + irq = m_new_obj(machine_pin_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + irq->next = MP_STATE_PORT(machine_pin_irq_list); + gpio_init_callback(&irq->callback, gpio_callback_handler, BIT(self->pin)); + int ret = gpio_add_callback(self->port, &irq->callback); + if (ret != 0) { + mp_raise_OSError(-ret); + } + MP_STATE_PORT(machine_pin_irq_list) = irq; + } + self->irq = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + // configure irq + int ret = gpio_pin_interrupt_configure(self->port, self->pin, GPIO_INT_DISABLE); + if (ret != 0) { + mp_raise_OSError(-ret); + } + + self->irq->base.handler = args[ARG_handler].u_obj; + self->irq->base.ishard = args[ARG_hard].u_bool; + + if (args[ARG_handler].u_obj != mp_const_none) { + ret = gpio_pin_interrupt_configure(self->port, self->pin, args[ARG_trigger].u_int); + if (ret != 0) { + mp_raise_OSError(-ret); + } + } + } + + return MP_OBJ_FROM_PTR(self->irq); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); + STATIC mp_uint_t machine_pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { (void)errcode; machine_pin_obj_t *self = self_in; @@ -172,12 +272,15 @@ STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, // class constants { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_INPUT) }, { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_OUTPUT) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_INT_EDGE_RISING) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_INT_EDGE_FALLING) }, }; STATIC MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); @@ -195,3 +298,33 @@ const mp_obj_type_t machine_pin_type = { .protocol = &machine_pin_pin_p, .locals_dict = (mp_obj_t)&machine_pin_locals_dict, }; + +STATIC mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (new_trigger == 0) { + new_trigger = GPIO_INT_DISABLE; + } + int ret = gpio_pin_interrupt_configure(self->port, self->pin, new_trigger); + if (ret != 0) { + mp_raise_OSError(-ret); + } + return 0; +} + +STATIC mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return gpio_get_pending_int(self->port); + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return 0; // TODO + } + return 0; +} + +STATIC const mp_irq_methods_t machine_pin_irq_methods = { + .init = machine_pin_irq, + .trigger = machine_pin_irq_trigger, + .info = machine_pin_irq_info, +}; + +#endif // MICROPY_PY_MACHINE diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index e304dfd25..78b947d7a 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -38,6 +38,10 @@ #include #endif +#ifdef CONFIG_FLASH_MAP +#include +#endif + #include "py/mperrno.h" #include "py/compile.h" #include "py/runtime.h" @@ -51,6 +55,7 @@ #include "extmod/vfs.h" #endif +#include "modmachine.h" #include "modzephyr.h" #ifdef TEST @@ -100,8 +105,8 @@ STATIC void vfs_init(void) { mp_obj_t args[] = { mp_obj_new_str(CONFIG_DISK_SDHC_VOLUME_NAME, strlen(CONFIG_DISK_SDHC_VOLUME_NAME)) }; bdev = zephyr_disk_access_type.make_new(&zephyr_disk_access_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/sd"; - #elif defined(CONFIG_FLASH_MAP) && defined(DT_FLASH_AREA_STORAGE_ID) - mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(DT_FLASH_AREA_STORAGE_ID), MP_OBJ_NEW_SMALL_INT(4096) }; + #elif defined(CONFIG_FLASH_MAP) && FLASH_AREA_LABEL_EXISTS(storage) + mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(FLASH_AREA_ID(storage)), MP_OBJ_NEW_SMALL_INT(4096) }; bdev = zephyr_flash_area_type.make_new(&zephyr_flash_area_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/flash"; #endif @@ -162,6 +167,11 @@ int real_main(void) { } printf("soft reboot\n"); + + #if MICROPY_PY_MACHINE + machine_pin_deinit(); + #endif + goto soft_reset; return 0; diff --git a/ports/zephyr/make-minimal b/ports/zephyr/make-minimal index 1fc143e4d..d7dddc334 100755 --- a/ports/zephyr/make-minimal +++ b/ports/zephyr/make-minimal @@ -11,6 +11,8 @@ make \ CONF_FILE=prj_minimal.conf \ CFLAGS_EXTRA='-DMP_CONFIGFILE=""' \ + MICROPY_VFS_FAT=0 \ + MICROPY_VFS_LFS2=0 \ FROZEN_DIR= \ QEMU_NET=0 \ "$@" diff --git a/ports/zephyr/makeprj.py b/ports/zephyr/makeprj.py new file mode 100644 index 000000000..239c877cd --- /dev/null +++ b/ports/zephyr/makeprj.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys +import os +import hashlib + + +def hash_file(fname): + if not os.path.exists(fname): + return b"" + hasher = hashlib.md5() + with open(fname, "rb") as f: + hasher.update(f.read()) + return hasher.digest() + + +old_digest = hash_file(sys.argv[3]) + +with open(sys.argv[3] + ".tmp", "wb") as f: + f.write(open(sys.argv[1], "rb").read()) + if os.path.exists(sys.argv[2]): + f.write(open(sys.argv[2], "rb").read()) + +new_digest = hash_file(sys.argv[3] + ".tmp") + +if new_digest != old_digest: + print("Replacing") + os.rename(sys.argv[3] + ".tmp", sys.argv[3]) +else: + os.remove(sys.argv[3] + ".tmp") diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index 84e4d10a8..766a9d7ea 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -11,6 +11,9 @@ typedef struct _machine_pin_obj_t { mp_obj_base_t base; struct device *port; uint32_t pin; + struct _machine_pin_irq_obj_t *irq; } machine_pin_obj_t; +void machine_pin_deinit(void); + #endif // MICROPY_INCLUDED_ZEPHYR_MODMACHINE_H diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index d4ee610b2..71b44d7df 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include "modzephyr.h" #include "py/runtime.h" @@ -45,27 +45,12 @@ STATIC mp_obj_t mod_current_tid(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_current_tid_obj, mod_current_tid); -#ifdef CONFIG_THREAD_STACK_INFO -extern k_tid_t const _main_thread; -extern k_tid_t const _idle_thread; - -static void thread_stack_dump(const struct k_thread *thread, void *user_data) { - const char *th_name = k_thread_name_get((k_tid_t)thread); - - if (th_name == NULL) { - static char tid[9]; - snprintf(tid, sizeof(tid), "%08x", (int)thread); - th_name = tid; - } - - stack_analyze(th_name, (char *)thread->stack_info.start, thread->stack_info.size); -} - -STATIC mp_obj_t mod_stacks_analyze(void) { - k_thread_foreach(thread_stack_dump, NULL); +#ifdef CONFIG_THREAD_ANALYZER +STATIC mp_obj_t mod_thread_analyze(void) { + thread_analyzer_print(); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_stacks_analyze_obj, mod_stacks_analyze); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_analyze_obj, mod_thread_analyze); #endif #ifdef CONFIG_NET_SHELL @@ -84,8 +69,8 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_zephyr) }, { MP_ROM_QSTR(MP_QSTR_is_preempt_thread), MP_ROM_PTR(&mod_is_preempt_thread_obj) }, { MP_ROM_QSTR(MP_QSTR_current_tid), MP_ROM_PTR(&mod_current_tid_obj) }, - #ifdef CONFIG_THREAD_STACK_INFO - { MP_ROM_QSTR(MP_QSTR_stacks_analyze), MP_ROM_PTR(&mod_stacks_analyze_obj) }, + #ifdef CONFIG_THREAD_ANALYZER + { MP_ROM_QSTR(MP_QSTR_thread_analyze), MP_ROM_PTR(&mod_thread_analyze_obj) }, #endif #ifdef CONFIG_NET_SHELL diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 7044c175d..f94ee7270 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -81,6 +81,7 @@ #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_PY_BUILTINS_COMPLEX (0) +#define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_VFS (1) #define MICROPY_READER_VFS (MICROPY_VFS) @@ -119,7 +120,8 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM #define MICROPY_PORT_ROOT_POINTERS \ - const char *readline_hist[8]; + const char *readline_hist[8]; \ + void *machine_pin_irq_list; /* Linked list of pin irq objects */ extern const struct _mp_obj_module_t mp_module_machine; extern const struct _mp_obj_module_t mp_module_time; @@ -169,3 +171,6 @@ extern const struct _mp_obj_module_t mp_module_zsensor; // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +#define MICROPY_BEGIN_ATOMIC_SECTION irq_lock +#define MICROPY_END_ATOMIC_SECTION irq_unlock diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 594f6a1f6..8434a388b 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -21,7 +21,7 @@ static inline void mp_hal_delay_us(mp_uint_t delay) { } static inline void mp_hal_delay_ms(mp_uint_t delay) { - k_sleep(delay); + k_msleep(delay); } #define mp_hal_delay_us_fast(us) (mp_hal_delay_us(us)) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 993dfdc26..50cfa0050 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -11,7 +11,7 @@ CONFIG_CONSOLE_GETCHAR_BUFSIZE=128 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=128 CONFIG_NEWLIB_LIBC=y -CONFIG_FLOAT=y +CONFIG_FPU=y CONFIG_MAIN_STACK_SIZE=4736 # Enable sensor subsystem (doesn't add code if not used). @@ -50,10 +50,9 @@ CONFIG_NET_DHCPV4=y # Diagnostics and debugging # Required for zephyr.stack_analyze() -CONFIG_INIT_STACKS=y -CONFIG_THREAD_MONITOR=y +CONFIG_THREAD_ANALYZER=y +CONFIG_THREAD_ANALYZER_USE_PRINTK=y CONFIG_THREAD_NAME=y -CONFIG_THREAD_STACK_INFO=y # Required for usocket.pkt_get_info() CONFIG_NET_BUF_POOL_USAGE=y diff --git a/ports/zephyr/prj_96b_carbon.conf b/ports/zephyr/prj_96b_carbon.conf new file mode 100644 index 000000000..40b57e69c --- /dev/null +++ b/ports/zephyr/prj_96b_carbon.conf @@ -0,0 +1,2 @@ +# TODO: Enable networking +CONFIG_NETWORKING=y diff --git a/ports/zephyr/prj_base.conf b/ports/zephyr/prj_base.conf new file mode 100644 index 000000000..993dfdc26 --- /dev/null +++ b/ports/zephyr/prj_base.conf @@ -0,0 +1,68 @@ +CONFIG_BUILD_OUTPUT_BIN=y +CONFIG_REBOOT=y + +CONFIG_STDOUT_CONSOLE=y +CONFIG_CONSOLE_HANDLER=y +CONFIG_UART_CONSOLE_DEBUG_SERVER_HOOKS=y + +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_GETCHAR_BUFSIZE=128 +CONFIG_CONSOLE_PUTCHAR_BUFSIZE=128 + +CONFIG_NEWLIB_LIBC=y +CONFIG_FLOAT=y +CONFIG_MAIN_STACK_SIZE=4736 + +# Enable sensor subsystem (doesn't add code if not used). +# Specific sensors should be enabled per-board. +CONFIG_SENSOR=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_INIT_TIMEOUT=3 +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_NEED_IPV4=y + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_RESOLVER_ADDITIONAL_QUERIES=2 +CONFIG_DNS_SERVER_IP_ADDRESSES=y + +# Static IP addresses +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +CONFIG_DNS_SERVER1="192.0.2.2" + +# DHCP configuration. Until DHCP address is assigned, +# static configuration above is used instead. +CONFIG_NET_DHCPV4=y + +# Diagnostics and debugging + +# Required for zephyr.stack_analyze() +CONFIG_INIT_STACKS=y +CONFIG_THREAD_MONITOR=y +CONFIG_THREAD_NAME=y +CONFIG_THREAD_STACK_INFO=y + +# Required for usocket.pkt_get_info() +CONFIG_NET_BUF_POOL_USAGE=y + +# Required for usocket.shell_*() +#CONFIG_NET_SHELL=y + +# Uncomment to enable "INFO" level net_buf logging +#CONFIG_NET_LOG=y +#CONFIG_NET_DEBUG_NET_BUF=y +# Change to 4 for "DEBUG" level +#CONFIG_SYS_LOG_NET_LEVEL=3 diff --git a/ports/zephyr/prj_disco_l475_iot1.conf b/ports/zephyr/prj_disco_l475_iot1.conf new file mode 100644 index 000000000..36c8b99dd --- /dev/null +++ b/ports/zephyr/prj_disco_l475_iot1.conf @@ -0,0 +1,5 @@ +# Sensors +CONFIG_HTS221=y +CONFIG_LIS3MDL=y +CONFIG_LPS22HB=y +CONFIG_LSM6DSL=y diff --git a/ports/zephyr/prj_frdm_k64f.conf b/ports/zephyr/prj_frdm_k64f.conf new file mode 100644 index 000000000..483e9a29b --- /dev/null +++ b/ports/zephyr/prj_frdm_k64f.conf @@ -0,0 +1,15 @@ +# Networking drivers +CONFIG_NET_L2_ETHERNET=y + +# Hardware features +CONFIG_I2C=y + +# Sensor drivers +CONFIG_FXOS8700=y +CONFIG_FXOS8700_MODE_HYBRID=y +CONFIG_FXOS8700_TEMP=y + +# Flash drivers +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_MPU_ALLOW_FLASH_WRITE=y diff --git a/ports/zephyr/prj_frdm_kw41z.conf b/ports/zephyr/prj_frdm_kw41z.conf new file mode 100644 index 000000000..ff7b7887e --- /dev/null +++ b/ports/zephyr/prj_frdm_kw41z.conf @@ -0,0 +1,7 @@ +# Hardware features +CONFIG_I2C=y + +# Sensor drivers +CONFIG_FXOS8700=y +CONFIG_FXOS8700_MODE_HYBRID=y +CONFIG_FXOS8700_TEMP=y diff --git a/ports/zephyr/prj_mimxrt1050_evk.conf b/ports/zephyr/prj_mimxrt1050_evk.conf new file mode 100644 index 000000000..051ab7e60 --- /dev/null +++ b/ports/zephyr/prj_mimxrt1050_evk.conf @@ -0,0 +1,9 @@ +# Required for zephyr.DiskAccess block devices +CONFIG_DISK_ACCESS=y +CONFIG_DISK_ACCESS_SDHC=y + +CONFIG_USB=y +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Zephyr MicroPython" +CONFIG_USB_MASS_STORAGE=y +CONFIG_MASS_STORAGE_DISK_NAME="SDHC" diff --git a/ports/zephyr/prj_minimal.conf b/ports/zephyr/prj_minimal.conf index d9c2894e6..8b2104925 100644 --- a/ports/zephyr/prj_minimal.conf +++ b/ports/zephyr/prj_minimal.conf @@ -1,5 +1,5 @@ CONFIG_NEWLIB_LIBC=y -CONFIG_FLOAT=y +CONFIG_FPU=y CONFIG_MAIN_STACK_SIZE=4096 CONFIG_UART_INTERRUPT_DRIVEN=y diff --git a/ports/zephyr/prj_qemu_cortex_m3.conf b/ports/zephyr/prj_qemu_cortex_m3.conf new file mode 100644 index 000000000..dac0c358d --- /dev/null +++ b/ports/zephyr/prj_qemu_cortex_m3.conf @@ -0,0 +1,7 @@ +# Interrupt-driven UART console has emulation artifacts under QEMU, +# disable it +CONFIG_CONSOLE_SUBSYS=n + +# Networking drivers +# SLIP driver for QEMU +CONFIG_NET_SLIP_TAP=y diff --git a/ports/zephyr/prj_qemu_x86.conf b/ports/zephyr/prj_qemu_x86.conf new file mode 100644 index 000000000..dac0c358d --- /dev/null +++ b/ports/zephyr/prj_qemu_x86.conf @@ -0,0 +1,7 @@ +# Interrupt-driven UART console has emulation artifacts under QEMU, +# disable it +CONFIG_CONSOLE_SUBSYS=n + +# Networking drivers +# SLIP driver for QEMU +CONFIG_NET_SLIP_TAP=y diff --git a/ports/zephyr/prj_reel_board.conf b/ports/zephyr/prj_reel_board.conf new file mode 100644 index 000000000..a7ed230d1 --- /dev/null +++ b/ports/zephyr/prj_reel_board.conf @@ -0,0 +1,4 @@ +# Flash drivers +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_MPU_ALLOW_FLASH_WRITE=y diff --git a/ports/zephyr/prj_rv32m1_vega_ri5cy.conf b/ports/zephyr/prj_rv32m1_vega_ri5cy.conf new file mode 100644 index 000000000..dcd6d7a08 --- /dev/null +++ b/ports/zephyr/prj_rv32m1_vega_ri5cy.conf @@ -0,0 +1,3 @@ +# Flash drivers +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index fef9f00ab..63ecc8289 100644 --- a/ports/zephyr/uart_core.c +++ b/ports/zephyr/uart_core.c @@ -49,7 +49,7 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { while (len--) { char c = *str++; while (console_putchar(c) == -1) { - k_sleep(1); + k_msleep(1); } } #else diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index c533cb8fd..83f19a8fe 100644 --- a/ports/zephyr/zephyr_storage.c +++ b/ports/zephyr/zephyr_storage.c @@ -245,8 +245,8 @@ STATIC const mp_rom_map_elem_t zephyr_flash_area_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&zephyr_flash_area_readblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&zephyr_flash_area_writeblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&zephyr_flash_area_ioctl_obj) }, - #ifdef DT_FLASH_AREA_STORAGE_ID - { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(DT_FLASH_AREA_STORAGE_ID) }, + #if FLASH_AREA_LABEL_EXISTS(storage) + { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(FLASH_AREA_ID(storage)) }, #endif }; STATIC MP_DEFINE_CONST_DICT(zephyr_flash_area_locals_dict, zephyr_flash_area_locals_dict_table); diff --git a/py/argcheck.c b/py/argcheck.c index c333ead05..bc6a8c4e5 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -28,7 +28,9 @@ #include #include "py/runtime.h" - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { // TODO maybe take the function name as an argument so we can print nicer error messages diff --git a/py/asmarm.c b/py/asmarm.c index 72b37f73a..e91421578 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 825fd8840..46da661fa 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 6d0ee4b76..17b694a74 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 723671d5a..fd64eaf98 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 28b1bd255..1a4987f5c 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 96de372ae..4b0f8047f 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); } diff --git a/py/asmx86.h b/py/asmx86.h index 4855cd7ee..8f1b06d22 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -63,10 +63,13 @@ // 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 @@ -93,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); @@ -185,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 5eb40daf7..43f1b608e 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 34bc78fd3..aee35167f 100644 --- a/py/bc.c +++ b/py/bc.c @@ -40,7 +40,9 @@ #define DEBUG_printf(...) (void)0 #endif -#if !MICROPY_PERSISTENT_CODE + + +#if !MICROPY_PERSISTENT_CODE || MICROPY_EMIT_WASM mp_uint_t mp_decode_uint(const byte **ptr) { mp_uint_t unum = 0; @@ -74,6 +76,282 @@ const byte *mp_decode_uint_skip(const byte *ptr) { #endif +#if NO_NLR + +STATIC mp_obj_t fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + // generic message, used also for other argument issues + (void)f; + (void)expected; + (void)given; + mp_arg_error_terse_mismatch(); + return MP_OBJ_NULL; + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL + (void)f; + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function takes %d positional arguments but %d were given", expected, given)); + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "%q() takes %d positional arguments but %d were given", + mp_obj_fun_get_name(MP_OBJ_FROM_PTR(f)), expected, given)); + #endif +} + +#if DEBUG_PRINT +STATIC void dump_args(const mp_obj_t *a, size_t sz) { + DEBUG_printf("%p: ", a); + for (size_t i = 0; i < sz; i++) { + DEBUG_printf("%p ", a[i]); + } + DEBUG_printf("\n"); +} +#else +#define dump_args(...) (void)0 +#endif + +// On entry code_state should be allocated somewhere (stack/heap) and +// contain the following valid entries: +// - code_state->fun_bc should contain a pointer to the function object +// - code_state->ip should contain the offset in bytes from the pointer +// code_state->fun_bc->bytecode to the entry n_state (0 for bytecode, non-zero for native) +mp_obj_t mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // This function is pretty complicated. It's main aim is to be efficient in speed and RAM + // usage for the common case of positional only args. + + // get the function object that we want to set up (could be bytecode or native code) + mp_obj_fun_bc_t *self = code_state->fun_bc; + + // ip comes in as an offset into bytecode, so turn it into a true pointer + code_state->ip = self->bytecode + (size_t)code_state->ip; + + #if MICROPY_STACKLESS + code_state->prev = NULL; + #endif + + #if MICROPY_PY_SYS_SETTRACE + code_state->prev_state = NULL; + code_state->frame = NULL; + #endif + + // Get cached n_state (rather than decode it again) + size_t n_state = code_state->n_state; + + // Decode prelude + size_t n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args; + MP_BC_PRELUDE_SIG_DECODE_INTO(code_state->ip, n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args); + (void)n_state_unused; + (void)n_exc_stack_unused; + + code_state->sp = &code_state->state[0] - 1; + code_state->exc_sp_idx = 0; + + // zero out the local stack to begin with + memset(code_state->state, 0, n_state * sizeof(*code_state->state)); + + const mp_obj_t *kwargs = args + n_args; + + // var_pos_kw_args points to the stack where the var-args tuple, and var-kw dict, should go (if they are needed) + mp_obj_t *var_pos_kw_args = &code_state->state[n_state - 1 - n_pos_args - n_kwonly_args]; + + // check positional arguments + + if (n_args > n_pos_args) { + // given more than enough arguments + if ((scope_flags & MP_SCOPE_FLAG_VARARGS) == 0) { + return fun_pos_args_mismatch(self, n_pos_args, n_args); + } + // put extra arguments in varargs tuple + *var_pos_kw_args-- = mp_obj_new_tuple(n_args - n_pos_args, args + n_pos_args); + n_args = n_pos_args; + } else { + if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { + DEBUG_printf("passing empty tuple as *args\n"); + *var_pos_kw_args-- = mp_const_empty_tuple; + } + // Apply processing and check below only if we don't have kwargs, + // otherwise, kw handling code below has own extensive checks. + if (n_kw == 0 && (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) == 0) { + if (n_args >= (size_t)(n_pos_args - n_def_pos_args)) { + // given enough arguments, but may need to use some default arguments + for (size_t i = n_args; i < n_pos_args; i++) { + code_state->state[n_state - 1 - i] = self->extra_args[i - (n_pos_args - n_def_pos_args)]; + } + } else { + return fun_pos_args_mismatch(self, n_pos_args - n_def_pos_args, n_args); + } + } + } + + // copy positional args into state + for (size_t i = 0; i < n_args; i++) { + code_state->state[n_state - 1 - i] = args[i]; + } + + // check keyword arguments + + if (n_kw != 0 || (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { + DEBUG_printf("Initial args: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + mp_obj_t dict = MP_OBJ_NULL; + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { + dict = mp_obj_new_dict(n_kw); // TODO: better go conservative with 0? + *var_pos_kw_args = dict; + } + + // get pointer to arg_names array + const mp_obj_t *arg_names = (const mp_obj_t *)self->const_table; + + for (size_t i = 0; i < n_kw; i++) { + // the keys in kwargs are expected to be qstr objects + mp_obj_t wanted_arg_name = kwargs[2 * i]; + for (size_t j = 0; j < n_pos_args + n_kwonly_args; j++) { + if (wanted_arg_name == arg_names[j]) { + if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function got multiple values for argument '%q'", MP_OBJ_QSTR_VALUE(wanted_arg_name))); + } + code_state->state[n_state - 1 - j] = kwargs[2 * i + 1]; + goto continue2; + } + } + // Didn't find name match with positional args + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) == 0) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_TypeError_o("unexpected keyword argument"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "unexpected keyword argument '%q'", MP_OBJ_QSTR_VALUE(wanted_arg_name))); + } + } + mp_obj_dict_store(dict, kwargs[2 * i], kwargs[2 * i + 1]); + continue2:; + } + + DEBUG_printf("Args with kws flattened: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + // fill in defaults for positional args + mp_obj_t *d = &code_state->state[n_state - n_pos_args]; + mp_obj_t *s = &self->extra_args[n_def_pos_args - 1]; + for (size_t i = n_def_pos_args; i > 0; i--, d++, s--) { + if (*d == MP_OBJ_NULL) { + *d = *s; + } + } + + DEBUG_printf("Args after filling default positional: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + // Check that all mandatory positional args are specified + while (d < &code_state->state[n_state]) { + if (*d++ == MP_OBJ_NULL) { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing required positional argument #%d", &code_state->state[n_state] - d)); + } + } + + // Check that all mandatory keyword args are specified + // Fill in default kw args if we have them + for (size_t i = 0; i < n_kwonly_args; i++) { + if (code_state->state[n_state - 1 - n_pos_args - i] == MP_OBJ_NULL) { + mp_map_elem_t *elem = NULL; + if ((scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { + elem = mp_map_lookup(&((mp_obj_dict_t *)MP_OBJ_TO_PTR(self->extra_args[n_def_pos_args]))->map, arg_names[n_pos_args + i], MP_MAP_LOOKUP); + } + if (elem != NULL) { + code_state->state[n_state - 1 - n_pos_args - i] = elem->value; + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing required keyword argument '%q'", MP_OBJ_QSTR_VALUE(arg_names[n_pos_args + i]))); + } + } + } + + } else { + // no keyword arguments given + if (n_kwonly_args != 0) { + return mp_raise_TypeError_o("function missing keyword-only argument"); + } + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { + *var_pos_kw_args = mp_obj_new_dict(0); + } + } + + // read the size part of the prelude + const byte *ip = code_state->ip; + MP_BC_PRELUDE_SIZE_DECODE(ip); + + // jump over code info (source file and line-number mapping) + ip += n_info; + + // bytecode prelude: initialise closed over variables + for (; n_cell; --n_cell) { + size_t local_num = *ip++; + code_state->state[n_state - 1 - local_num] = + mp_obj_new_cell(code_state->state[n_state - 1 - local_num]); + } + + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + ip = MP_ALIGN(ip, sizeof(mp_uint_t)); + #endif + + // now that we skipped over the prelude, set the ip for the VM + code_state->ip = ip; + + DEBUG_printf("Calling: n_pos_args=%d, n_kwonly_args=%d\n", n_pos_args, n_kwonly_args); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state->state, n_state); + + return MP_OBJ_SENTINEL; // success +} + +#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +// The following table encodes the number of bytes that a specific opcode +// takes up. Some opcodes have an extra byte, defined by MP_BC_MASK_EXTRA_BYTE. +// There are 4 special opcodes that have an extra byte only when +// MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE is enabled (and they take a qstr): +// MP_BC_LOAD_NAME +// MP_BC_LOAD_GLOBAL +// MP_BC_LOAD_ATTR +// MP_BC_STORE_ATTR +uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) { + uint f = MP_BC_FORMAT(*ip); + const byte *ip_start = ip; + if (f == MP_BC_FORMAT_QSTR) { + if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) { + if (*ip == MP_BC_LOAD_NAME + || *ip == MP_BC_LOAD_GLOBAL + || *ip == MP_BC_LOAD_ATTR + || *ip == MP_BC_STORE_ATTR) { + ip += 1; + } + } + ip += 3; + } else { + int extra_byte = (*ip & MP_BC_MASK_EXTRA_BYTE) == 0; + ip += 1; + if (f == MP_BC_FORMAT_VAR_UINT) { + if (count_var_uint) { + while ((*ip++ & 0x80) != 0) { + } + } + } else if (f == MP_BC_FORMAT_OFFSET) { + ip += 2; + } + ip += extra_byte; + } + *opcode_size = ip - ip_start; + return f; +} + +#endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +#else // NO_NLR + + STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues @@ -341,3 +619,6 @@ uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) } #endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +#endif // NO_NLR + diff --git a/py/bc.h b/py/bc.h index 0c19c5165..2ee0f71d3 100644 --- a/py/bc.h +++ b/py/bc.h @@ -225,7 +225,11 @@ const byte *mp_decode_uint_skip(const byte *ptr); mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t func, size_t n_args, size_t n_kw, const mp_obj_t *args); +#if NO_NLR +mp_obj_t mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); +#else void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); +#endif void mp_bytecode_print(const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); void mp_bytecode_print2(const byte *code, size_t len, const mp_uint_t *const_table); const byte *mp_bytecode_print_str(const byte *ip); diff --git a/py/bc0.h b/py/bc0.h index 842034ebf..fb6af3ecd 100644 --- a/py/bc0.h +++ b/py/bc0.h @@ -39,7 +39,7 @@ // Nibbles in magic number are: BB BB BB BB BB BO VV QU #define MP_BC_FORMAT(op) ((0x000003a4 >> (2 * ((op) >> 4))) & 3) -// Load, Store, Delete, Import, Make, Build, Unpack, Call, Jump, Exception, For, sTack, Return, Yield, Op +// Load, Store, Delete, Import, Make, Build, Unpack, Call, Jump, Exception, For, Stack, Return, Yield, Op #define MP_BC_BASE_RESERVED (0x00) // ---------------- #define MP_BC_BASE_QSTR_O (0x10) // LLLLLLSSSDDII--- #define MP_BC_BASE_VINT_E (0x20) // MMLLLLSSDDBBBBBB diff --git a/py/binary.c b/py/binary.c index d0f72ec23..0b37e360a 100644 --- a/py/binary.c +++ b/py/binary.c @@ -135,7 +135,11 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { } if (size == 0) { +#if NO_NLR + mp_raise_ValueError_o( mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("bad typecode"))); +#else mp_raise_ValueError(MP_ERROR_TEXT("bad typecode")); +#endif } if (palign != NULL) { diff --git a/py/builtinevex.c b/py/builtinevex.c index 800a20223..44c9fb314 100644 --- a/py/builtinevex.c +++ b/py/builtinevex.c @@ -58,6 +58,12 @@ STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj } // execute code +#if NO_NLR + mp_obj_t ret = mp_call_function_0(self->module_fun); + mp_globals_set(old_globals); + mp_locals_set(old_locals); + return ret; +#else nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_function_0(self->module_fun); @@ -71,6 +77,7 @@ STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj mp_locals_set(old_locals); nlr_jump(nlr.ret_val); } +#endif } STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { @@ -106,6 +113,11 @@ STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { mp_obj_code_t *code = m_new_obj(mp_obj_code_t); code->base.type = &mp_type_code; code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); +#if NO_NLR + if (code->module_fun == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_FROM_PTR(code); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_compile_obj, 3, 6, mp_builtin_compile); @@ -149,7 +161,11 @@ STATIC mp_obj_t eval_exec_helper(size_t n_args, const mp_obj_t *args, mp_parse_i } else { lex = mp_lexer_new_from_str_len(MP_QSTR__lt_string_gt_, bufinfo.buf, bufinfo.len, 0); } - +#if NO_NLR + if (lex == NULL) { + return MP_OBJ_NULL; + } +#endif return mp_parse_compile_execute(lex, parse_input_kind, globals, locals); } diff --git a/py/builtinimport.c b/py/builtinimport.c index bdc82e77c..84c0ba1b7 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include #include diff --git a/py/compile.c b/py/compile.c index 22f47ee0d..73a8ba5e3 100644 --- a/py/compile.c +++ b/py/compile.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2015 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 @@ -241,6 +241,11 @@ STATIC void compile_decrease_except_level(compiler_t *comp) { STATIC scope_t *scope_new_and_link(compiler_t *comp, scope_kind_t kind, mp_parse_node_t pn, uint emit_options) { scope_t *scope = scope_new(kind, pn, comp->source_file, emit_options); +#if NO_NLR + if (scope == NULL) { + return NULL; + } +#endif scope->parent = comp->scope_cur; scope->next = NULL; if (comp->scope_head == NULL) { @@ -1798,7 +1803,8 @@ STATIC void compile_async_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns uint try_finally_label = comp_next_label(comp); compile_node(comp, pns->nodes[1]); // iterator - compile_await_object_method(comp, MP_QSTR___aiter__); + EMIT_ARG(load_method, MP_QSTR___aiter__, false); + EMIT_ARG(call_method, 0, 0, 0); compile_store_id(comp, context); START_BREAK_CONTINUE_BLOCK @@ -1981,10 +1987,11 @@ 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__); + compile_load_id(comp, MP_QSTR_displayhook); compile_node(comp, pns->nodes[0]); EMIT_ARG(call_function, 1, 0, 0); EMIT(pop_top); @@ -1999,10 +2006,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])); @@ -2027,10 +2050,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 @@ -2070,7 +2093,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 { @@ -2108,6 +2131,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); @@ -2240,6 +2284,7 @@ STATIC void compile_atom_expr_normal(compiler_t *comp, mp_parse_node_struct_t *p size_t i = 0; // handle special super() call +#pragma message "FIXME: broken on super().__init__(self) when parent is dict" if (comp->scope_cur->kind == SCOPE_FUNCTION && MP_PARSE_NODE_IS_ID(pns->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]) == MP_QSTR_super @@ -2353,6 +2398,12 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar star_flags |= MP_EMIT_STAR_FLAG_DOUBLE; dblstar_args_node = pns_arg; } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_argument) { + #if MICROPY_PY_ASSIGN_EXPR + if (MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_argument_3)) { + compile_namedexpr_helper(comp, pns_arg->nodes[0], ((mp_parse_node_struct_t *)pns_arg->nodes[1])->nodes[0]); + n_positional++; + } else + #endif if (!MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_comp_for)) { if (!MP_PARSE_NODE_IS_ID(pns_arg->nodes[0])) { compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("LHS of keyword arg must be an id")); @@ -3083,7 +3134,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)); @@ -3463,6 +3514,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // create standard emitter; it's used at least for MP_PASS_SCOPE emit_t *emit_bc = emit_bc_new(); +#if NO_NLR + if (module_scope == NULL || emit_bc == NULL) { + return NULL; + } +#endif // compile pass 1 comp->emit = emit_bc; #if MICROPY_EMIT_NATIVE @@ -3478,6 +3534,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f { compile_scope(comp, s, MP_PASS_SCOPE); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif // Check if any implicitly declared variables should be closed over for (size_t i = 0; i < s->id_info_len; ++i) { id_info_t *id = &s->id_info[i]; @@ -3501,6 +3562,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // set max number of labels now that it's calculated emit_bc_set_max_num_labels(emit_bc, max_num_labels); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif // compile pass 2 and 3 #if MICROPY_EMIT_NATIVE emit_t *emit_native = NULL; @@ -3526,7 +3592,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f compile_scope_inline_asm(comp, s, MP_PASS_CODE_SIZE); } #endif +#if NO_NLR + if (MP_STATE_THREAD(active_exception) == NULL && comp->compile_error == MP_OBJ_NULL) { +#else if (comp->compile_error == MP_OBJ_NULL) { +#endif compile_scope_inline_asm(comp, s, MP_PASS_EMIT); } } else @@ -3565,7 +3635,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f } // final pass: emit code +#if NO_NLR + if (MP_STATE_THREAD(active_exception) == NULL && comp->compile_error == MP_OBJ_NULL) { +#else if (comp->compile_error == MP_OBJ_NULL) { +#endif compile_scope(comp, s, MP_PASS_EMIT); } } @@ -3606,15 +3680,26 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f } if (comp->compile_error != MP_OBJ_NULL) { - nlr_raise(comp->compile_error); + mp_raise_or_return_value(comp->compile_error, NULL); } else { return outer_raw_code; } } mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + // parser had an exception + return MP_OBJ_NULL; + } +#endif mp_raw_code_t *rc = mp_compile_to_raw_code(parse_tree, source_file, is_repl); // return function that executes the outer module +#if NO_NLR + if (rc == NULL) { + return MP_OBJ_NULL; + } +#endif return mp_make_function_from_raw_code(rc, MP_OBJ_NULL, MP_OBJ_NULL); } diff --git a/py/dynruntime.h b/py/dynruntime.h index 9f2803c53..696746f75 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -114,7 +114,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #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))) @@ -149,7 +149,9 @@ static inline mp_obj_t mp_obj_cast_to_native_base_dyn(mp_obj_t self_in, mp_const 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/emitcommon.c b/py/emitcommon.c index 791bf398a..19da9c942 100644 --- a/py/emitcommon.c +++ b/py/emitcommon.c @@ -33,6 +33,9 @@ void mp_emit_common_get_id_for_modification(scope_t *scope, qstr qst) { // name adding/lookup id_info_t *id = scope_find_or_add_id(scope, qst, ID_INFO_KIND_GLOBAL_IMPLICIT); + if (id == NULL) { + return; + } if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { // rebind as a local variable id->kind = ID_INFO_KIND_LOCAL; diff --git a/py/emitglue.c b/py/emitglue.c index 98320c426..1d087f1ab 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -52,6 +52,9 @@ mp_uint_t mp_verbose_flag = 0; mp_raw_code_t *mp_emit_glue_new_raw_code(void) { mp_raw_code_t *rc = m_new0(mp_raw_code_t, 1); + if (rc == NULL) { + return NULL; + } rc->kind = MP_CODE_RESERVED; #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = 0; @@ -85,7 +88,7 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif #ifdef DEBUG_PRINT - #if !MICROPY_DEBUG_PRINTERS + #if !MICROPY_DEBUG_PRINTERS && !MICROPY_PERSISTENT_CODE_SAVE const size_t len = 0; #endif DEBUG_printf("assign byte code: code=%p len=" UINT_FMT " flags=%x\n", code, len, (uint)scope_flags); diff --git a/py/emitnative.c b/py/emitnative.c index 6c8e9feeb..a69f73ca1 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -841,7 +841,11 @@ 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 { +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("conversion to object")); +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("conversion to object")); +#endif } return VTYPE_PYOBJ; } @@ -1104,13 +1108,16 @@ STATIC void emit_native_leave_exc_stack(emit_t *emit, bool start_of_handler) { } ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_RET); } - +#if NO_NLR +#pragma message "TODO" +#else STATIC exc_stack_entry_t *emit_native_pop_exc_stack(emit_t *emit) { assert(emit->exc_stack_size > 0); exc_stack_entry_t *e = &emit->exc_stack[--emit->exc_stack_size]; assert(e->is_active == false); return e; } +#endif STATIC void emit_load_reg_with_ptr(emit_t *emit, int reg, mp_uint_t ptr, size_t table_off) { if (!emit->do_viper_types) { @@ -1166,6 +1173,9 @@ STATIC void emit_native_label_assign(emit_t *emit, mp_uint_t l) { } STATIC void emit_native_global_exc_entry(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // Note: 4 labels are reserved for this function, starting at *emit->label_slot emit->exit_label = *emit->label_slot; @@ -1272,9 +1282,13 @@ STATIC void emit_native_global_exc_entry(emit_t *emit) { emit_call(emit, MP_F_NATIVE_RAISE); } } +#endif } STATIC void emit_native_global_exc_exit(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // Label for end of function emit_native_label_assign(emit, emit->exit_label); @@ -1307,6 +1321,7 @@ STATIC void emit_native_global_exc_exit(emit_t *emit) { } ASM_EXIT(emit->as); +#endif } STATIC void emit_native_import_name(emit_t *emit, qstr qst) { @@ -2196,6 +2211,9 @@ STATIC void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) { } STATIC void emit_native_end_finally(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // logic: // exc = pop_stack // if exc == None: pass @@ -2219,6 +2237,7 @@ STATIC void emit_native_end_finally(emit_t *emit) { } emit_post(emit); +#endif } STATIC void emit_native_get_iter(emit_t *emit, bool use_stack) { @@ -2289,7 +2308,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; @@ -2306,9 +2326,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 @@ -2316,6 +2340,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 { @@ -2334,31 +2362,35 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { if (op == MP_BINARY_OP_LSHIFT) { ASM_LSL_REG_REG(emit->as, REG_ARG_2, reg_rhs); } else { - ASM_ASR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + 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_INT, REG_ARG_2); + 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 @@ -2367,11 +2399,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, @@ -2379,11 +2426,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, @@ -2391,24 +2446,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, - ASM_THUMB_OP_ITE_GT, + 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_GE, ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LE, + ASM_THUMB_OP_ITE_GE, + 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, @@ -2416,9 +2486,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, @@ -2426,7 +2504,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 { @@ -2639,7 +2717,12 @@ 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) +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("casting")); + return; +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("casting")); +#endif } } else { assert(vtype_fun == VTYPE_PYOBJ); @@ -2727,6 +2810,9 @@ STATIC void emit_native_return_value(emit_t *emit) { } STATIC void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { +#if NO_NLR +#pragma message "TODO" +#else (void)n_args; assert(n_args == 1); vtype_kind_t vtype_exc; @@ -2736,13 +2822,21 @@ STATIC void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { } // 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); +#endif } STATIC void emit_native_yield(emit_t *emit, int kind) { +#if NO_NLR +#pragma message "TODO" +#else // Note: 1 (yield) or 3 (yield from) labels are reserved for this function, starting at *emit->label_slot if (emit->do_viper_types) { +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("native yield")); +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("native yield")); +#endif } emit->scope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; @@ -2819,6 +2913,7 @@ STATIC void emit_native_yield(emit_t *emit, int kind) { emit_native_adjust_stack_size(emit, 1); // ret_value emit_fold_stack_top(emit, REG_ARG_1); } +#endif } STATIC void emit_native_start_except_handler(emit_t *emit) { diff --git a/py/grammar.h b/py/grammar.h index c3d30cdf7..285fbded2 100644 --- a/py/grammar.h +++ b/py/grammar.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2015 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 @@ -30,6 +30,11 @@ // - 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; @@ -71,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 @@ -96,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 @@ -184,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)) @@ -210,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)) @@ -276,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)) @@ -312,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 @@ -342,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 10bb999af..b8e323df2 100644 --- a/py/lexer.c +++ b/py/lexer.c @@ -174,7 +174,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=" // * *= ** **= @@ -194,8 +195,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, @@ -360,7 +362,12 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { // 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 +#if NO_NLR + mp_raise_NotImplementedError_o("unicode name escapes"); +#pragma message "TODO: can we just safely break and expect caller to handle exception?" +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("unicode name escapes")); +#endif break; default: if (c >= '0' && c <= '7') { @@ -690,6 +697,11 @@ void mp_lexer_to_next(mp_lexer_t *lex) { mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { mp_lexer_t *lex = m_new_obj(mp_lexer_t); +#if NO_NLR + if (lex == NULL) { + return NULL; + } +#endif lex->source_name = src_name; lex->reader = reader; lex->line = 1; @@ -699,6 +711,11 @@ mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { lex->alloc_indent_level = MICROPY_ALLOC_LEXER_INDENT_INIT; lex->num_indent_level = 1; lex->indent_level = m_new(uint16_t, lex->alloc_indent_level); +#if NO_NLR + if (lex->indent_level == NULL) { + return NULL; + } +#endif vstr_init(&lex->vstr, 32); // store sentinel for first indentation level @@ -720,6 +737,11 @@ mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { lex->tok_kind = MP_TOKEN_INDENT; } +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif return lex; } @@ -734,6 +756,11 @@ mp_lexer_t *mp_lexer_new_from_str_len(qstr src_name, const char *str, size_t len mp_lexer_t *mp_lexer_new_from_file(const char *filename) { mp_reader_t reader; mp_reader_new_file(&reader, filename); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif return mp_lexer_new(qstr_from_str(filename), reader); } diff --git a/py/lexer.h b/py/lexer.h index b9f97013a..91767a44b 100644 --- a/py/lexer.h +++ b/py/lexer.h @@ -96,6 +96,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/malloc.c b/py/malloc.c index c775d5b15..e760610d1 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -85,6 +85,9 @@ STATIC void *realloc_ext(void *ptr, size_t n_bytes, bool allow_move) { void *m_malloc(size_t num_bytes) { void *ptr = malloc(num_bytes); if (ptr == NULL && num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS @@ -111,6 +114,9 @@ void *m_malloc_maybe(size_t num_bytes) { void *m_malloc_with_finaliser(size_t num_bytes) { void *ptr = malloc_with_finaliser(num_bytes); if (ptr == NULL && num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS @@ -140,6 +146,9 @@ void *m_realloc(void *ptr, size_t new_num_bytes) { void *new_ptr = realloc(ptr, new_num_bytes); if (new_ptr == NULL && new_num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(new_num_bytes); } #if MICROPY_MEM_STATS diff --git a/py/map.c b/py/map.c index 676c364da..85747df12 100644 --- a/py/map.c +++ b/py/map.c @@ -118,12 +118,16 @@ void mp_map_clear(mp_map_t *map) { map->table = NULL; } -STATIC void mp_map_rehash(mp_map_t *map) { +#if NO_NLR +STATIC int mp_map_rehash(mp_map_t *map) { size_t old_alloc = map->alloc; size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1); DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc); mp_map_elem_t *old_table = map->table; mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc); + if (new_table == NULL) { + return -1; + } // If we reach this point, table resizing succeeded, now we can edit the old map. map->alloc = new_alloc; map->used = 0; @@ -135,14 +139,44 @@ STATIC void mp_map_rehash(mp_map_t *map) { } } m_del(mp_map_elem_t, old_table, old_alloc); + return 0; // success } +#else +#error "please use no_nlr" +STATIC void mp_map_rehash(mp_map_t *map) { + size_t old_alloc = map->alloc; + size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1); + DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc); + mp_map_elem_t *old_table = map->table; + mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc); + // If we reach this point, table resizing succeeded, now we can edit the old map. + map->alloc = new_alloc; + map->used = 0; + map->all_keys_are_qstrs = 1; + map->table = new_table; + for (size_t i = 0; i < old_alloc; i++) { + if (old_table[i].key != MP_OBJ_NULL && old_table[i].key != MP_OBJ_SENTINEL) { + mp_map_elem_t lookup = mp_map_lookup(map, old_table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (lookup==NULL) { + //FIXME: maybe exception + return -1; + } + lookup->value = old_table[i].value; + } + } + m_del(mp_map_elem_t, old_table, old_alloc); +} +#endif // MP_MAP_LOOKUP behaviour: // - returns NULL if not found, else the slot it was found in with key,value non-null // MP_MAP_LOOKUP_ADD_IF_NOT_FOUND behaviour: // - returns slot, with key non-null and value=MP_OBJ_NULL if it was added // MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour: // - returns NULL if not found, else the slot if was found in with key null and value non-null +#if NO_NLR +#pragma message "TODO: NULL return can mean 1) not found; 2) exception" +#endif mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // If the map is a fixed array then we must only be called for a lookup assert(!map->is_fixed || lookup_kind == MP_MAP_LOOKUP); @@ -209,9 +243,17 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t // map is a hash table (not an ordered array), so do a hash lookup + if (map->alloc == 0) { if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { - mp_map_rehash(map); +#if NO_NLR + if (mp_map_rehash(map)) { + // exception + return NULL; + } +#else + mp_map_rehash(map); +#endif } else { return NULL; } @@ -222,7 +264,15 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t if (mp_obj_is_qstr(index)) { hash = qstr_hash(MP_OBJ_QSTR_VALUE(index)); } else { +#if NO_NLR + mp_obj_t hash_o = mp_unary_op(MP_UNARY_OP_HASH, index); + if (hash_o == MP_OBJ_NULL) { + return NULL; + } + hash = MP_OBJ_SMALL_INT_VALUE(hash_o); +#else hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); +#endif } size_t pos = hash % map->alloc; @@ -285,7 +335,14 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t return avail_slot; } else { // not enough room in table, rehash it +#if NO_NLR + if (mp_map_rehash(map)) { + // exception + return NULL; + } +#else mp_map_rehash(map); +#endif // restart the search for the new element start_pos = pos = hash % map->alloc; } @@ -321,6 +378,9 @@ STATIC void mp_set_rehash(mp_set_t *set) { m_del(mp_obj_t, old_table, old_alloc); } +#if NO_NLR +#pragma message "TODO: MP_OBJ_NULL return can mean 1) not found; 2) exception" +#endif mp_obj_t mp_set_lookup(mp_set_t *set, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // Note: lookup_kind can be MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND which // is handled by using bitwise operations. @@ -332,7 +392,15 @@ mp_obj_t mp_set_lookup(mp_set_t *set, mp_obj_t index, mp_map_lookup_kind_t looku return MP_OBJ_NULL; } } +#if NO_NLR + mp_obj_t hash_o = mp_unary_op(MP_UNARY_OP_HASH, index); + if (hash_o == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + mp_uint_t hash = MP_OBJ_SMALL_INT_VALUE(hash_o); +#else mp_uint_t hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); +#endif size_t pos = hash % set->alloc; size_t start_pos = pos; mp_obj_t *avail_slot = NULL; diff --git a/py/misc.h b/py/misc.h index c7060ddf9..cd0b134e7 100644 --- a/py/misc.h +++ b/py/misc.h @@ -97,7 +97,11 @@ void *m_realloc(void *ptr, size_t new_num_bytes); void *m_realloc_maybe(void *ptr, size_t new_num_bytes, bool allow_move); void m_free(void *ptr); #endif +#if NO_NLR +void *m_malloc_fail(size_t num_bytes); +#else NORETURN void m_malloc_fail(size_t num_bytes); +#endif #if MICROPY_MEM_STATS size_t m_get_total_bytes_allocated(void); @@ -191,8 +195,14 @@ char *vstr_add_len(vstr_t *vstr, size_t len); char *vstr_null_terminated_str(vstr_t *vstr); void vstr_add_byte(vstr_t *vstr, byte v); void vstr_add_char(vstr_t *vstr, unichar chr); +#if NO_NLR +//#pragma message "BEWARE: non void return is problematic with function pointers" +int vstr_add_str(vstr_t *vstr, const char *str); +int vstr_add_strn(vstr_t *vstr, const char *str, size_t len); +#else void vstr_add_str(vstr_t *vstr, const char *str); void vstr_add_strn(vstr_t *vstr, const char *str, size_t len); +#endif void vstr_ins_byte(vstr_t *vstr, size_t byte_pos, byte b); void vstr_ins_char(vstr_t *vstr, size_t char_pos, unichar chr); void vstr_cut_head_bytes(vstr_t *vstr, size_t bytes_to_cut); @@ -247,7 +257,7 @@ typedef union _mp_float_union_t { } p; #else struct { - mp_float_uint_t sgn : 1 + 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; diff --git a/py/modbuiltins.c b/py/modbuiltins.c index cfbfc5a25..53ef356e9 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -23,6 +23,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -624,7 +627,8 @@ STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { // built-in core functions { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, - { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, +// { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + { MP_ROM_QSTR(MP_QSTR_displayhook), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, // built-in types { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, diff --git a/py/modio.c b/py/modio.c index 8a18357e2..1825d1aee 100644 --- a/py/modio.c +++ b/py/modio.c @@ -54,6 +54,21 @@ STATIC mp_obj_t iobase_make_new(const mp_obj_type_t *type, size_t n_args, size_t return MP_OBJ_FROM_PTR(&iobase_singleton); } +#if NO_NLR +STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode, qstr qst) { + mp_obj_t dest[3]; + mp_load_method(obj, qst, dest); + mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, size, buf}; + dest[2] = MP_OBJ_FROM_PTR(&ar); + mp_obj_t ret = mp_call_method_n_kw(1, 0, dest); + if (ret == mp_const_none) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } else { + return mp_obj_get_int(ret); + } +} +#else STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode, qstr qst) { mp_obj_t dest[3]; mp_load_method(obj, qst, dest); @@ -72,6 +87,7 @@ STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int return MP_STREAM_ERROR; } } +#endif // NO_NLR STATIC mp_uint_t iobase_read(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode) { return iobase_read_write(obj, buf, size, errcode, MP_QSTR_readinto); } diff --git a/py/modmath.c b/py/modmath.c index b312eeb3d..6af0eafbb 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -37,7 +37,11 @@ #define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) #define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) +#if NO_NLR +STATIC mp_obj_t math_error(void) { +#else STATIC NORETURN void math_error(void) { +#endif mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); } @@ -45,6 +49,9 @@ STATIC mp_obj_t math_generic_1(mp_obj_t x_obj, mp_float_t (*f)(mp_float_t)) { mp_float_t x = mp_obj_get_float(x_obj); mp_float_t ans = f(x); if ((isnan(ans) && !isnan(x)) || (isinf(ans) && !isinf(x))) { +#if NO_NLR + return +#endif math_error(); } return mp_obj_new_float(ans); @@ -55,6 +62,9 @@ STATIC mp_obj_t math_generic_2(mp_obj_t x_obj, mp_obj_t y_obj, mp_float_t (*f)(m mp_float_t y = mp_obj_get_float(y_obj); mp_float_t ans = f(x, y); if ((isnan(ans) && !isnan(x) && !isnan(y)) || (isinf(ans) && !isinf(x))) { +#if NO_NLR + return +#endif math_error(); } return mp_obj_new_float(ans); @@ -158,7 +168,7 @@ STATIC mp_float_t MICROPY_FLOAT_C_FUN(fabs_func)(mp_float_t x) { } MATH_FUN_1(fabs, fabs_func) // floor(x) -MATH_FUN_1_TO_INT(floor, floor) // TODO: delegate to x.__floor__() if x is not a float +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) { @@ -207,6 +217,9 @@ STATIC mp_obj_t mp_math_isclose(size_t n_args, const mp_obj_t *pos_args, mp_map_ ? (mp_float_t)1e-9 : mp_obj_get_float(args[ARG_rel_tol].u_obj); const mp_float_t abs_tol = mp_obj_get_float(args[ARG_abs_tol].u_obj); if (rel_tol < (mp_float_t)0.0 || abs_tol < (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } if (a == b) { @@ -232,6 +245,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_math_isclose_obj, 2, mp_math_isclose); STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { mp_float_t x = mp_obj_get_float(args[0]); if (x <= (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } mp_float_t l = MICROPY_FLOAT_C_FUN(log)(x); @@ -240,6 +256,9 @@ STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { } else { mp_float_t base = mp_obj_get_float(args[1]); if (base <= (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } else if (base == (mp_float_t)1.0) { mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); diff --git a/py/modstruct.c b/py/modstruct.c index 4cbcad6d4..7bea6d2b4 100644 --- a/py/modstruct.c +++ b/py/modstruct.c @@ -99,6 +99,11 @@ STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { total_cnt += cnt; size_t align; size_t sz = mp_binary_get_size(fmt_type, *fmt, &align); +#if NO_NLR + if (sz == 0) { + return (size_t)-1; + } +#endif while (cnt--) { // Apply alignment size = (size + align - 1) & ~(align - 1); @@ -112,8 +117,19 @@ STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { STATIC mp_obj_t struct_calcsize(mp_obj_t fmt_in) { const char *fmt = mp_obj_str_get_str(fmt_in); +#if NO_NLR + if (fmt == NULL) { + return MP_OBJ_NULL; + } +#endif size_t size; +#if NO_NLR + if (calc_size_items(fmt, &size) == (size_t)-1) { + return MP_OBJ_NULL; + } +#else calc_size_items(fmt, &size); +#endif return MP_OBJ_NEW_SMALL_INT(size); } MP_DEFINE_CONST_FUN_OBJ_1(struct_calcsize_obj, struct_calcsize); @@ -126,6 +142,11 @@ STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { const char *fmt = mp_obj_str_get_str(args[0]); size_t total_sz; size_t num_items = calc_size_items(fmt, &total_sz); +#if NO_NLR + if (num_items == (size_t)-1) { + return MP_OBJ_NULL; + } +#endif char fmt_type = get_fmt_type(&fmt); mp_obj_tuple_t *res = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_items, NULL)); mp_buffer_info_t bufinfo; @@ -214,6 +235,13 @@ STATIC void struct_pack_into_internal(mp_obj_t fmt_in, byte *p, size_t n_args, c STATIC mp_obj_t struct_pack(size_t n_args, const mp_obj_t *args) { // TODO: "The arguments must match the values required by the format exactly." +#pragma message "TODO: The arguments must match the values required by the format exactly." +#if NO_NLR + mp_obj_t size_obj = struct_calcsize(args[0]); + if (size_obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif mp_int_t size = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); vstr_t vstr; vstr_init_len(&vstr, size); @@ -240,6 +268,12 @@ STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { p += offset; // Check that the output buffer is big enough to hold all the values +#if NO_NLR + mp_obj_t sz_obj = struct_calcsize(args[0]); + if (sz_obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif mp_int_t sz = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); if (p + sz > end_p) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); diff --git a/py/modsys.c b/py/modsys.c index 586b13e74..58f3b7c2d 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -116,7 +116,7 @@ STATIC mp_obj_t mp_sys_exit(size_t n_args, const mp_obj_t *args) { } else { exc = mp_obj_new_exception_arg1(&mp_type_SystemExit, args[0]); } - nlr_raise(exc); + mp_raise_or_return(exc); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_exit_obj, 0, 1, mp_sys_exit); @@ -124,7 +124,13 @@ STATIC mp_obj_t mp_sys_print_exception(size_t n_args, const mp_obj_t *args) { #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES void *stream_obj = &mp_sys_stdout_obj; if (n_args > 1) { +#if NO_NLR + if (mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } +#else mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE); +#endif stream_obj = MP_OBJ_TO_PTR(args[1]); } @@ -238,6 +244,10 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_getsizeof), MP_ROM_PTR(&mp_sys_getsizeof_obj) }, #endif + #if __ANDROID__ + { MP_ROM_QSTR(MP_QSTR_getandroidapilevel), MP_ROM_PTR(19) }, + #endif + /* * Extensions to CPython */ diff --git a/py/modthread.c b/py/modthread.c index 1306dc642..805c4cb7e 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -175,6 +175,9 @@ STATIC void *thread_entry(void *args_in) { mp_locals_set(args->dict_locals); mp_globals_set(args->dict_globals); +#if NO_NLR + MP_STATE_THREAD(active_exception) = NULL; +#endif MP_THREAD_GIL_ENTER(); // signal that we are set up and running @@ -186,14 +189,23 @@ STATIC void *thread_entry(void *args_in) { DEBUG_printf("[thread] start ts=%p args=%p stack=%p\n", &ts, &args, MP_STATE_THREAD(stack_top)); +#if NO_NLR + mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); + if (MP_STATE_THREAD(active_exception) != NULL) { +#else nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); nlr_pop(); } else { +#endif // uncaught exception // check for SystemExit +#if NO_NLR + mp_obj_base_t *exc = (mp_obj_base_t *)MP_STATE_THREAD(active_exception); +#else mp_obj_base_t *exc = (mp_obj_base_t *)nlr.ret_val; +#endif if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // swallow exception silently } else { diff --git a/py/mpconfig.h b/py/mpconfig.h index 27df3f483..afb4934bb 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -339,9 +339,11 @@ #define MICROPY_EMIT_XTENSAWIN (0) #endif +#if NO_NLR +#else // Convenience definition for whether any native emitter is enabled #define MICROPY_EMIT_NATIVE (MICROPY_EMIT_X64 || MICROPY_EMIT_X86 || MICROPY_EMIT_THUMB || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN) - +#endif // Select prelude-as-bytes-object for certain emitters #define MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ (MICROPY_EMIT_XTENSAWIN) @@ -439,8 +441,13 @@ // Whether to enable debugging versions of MP_OBJ_NULL/STOP_ITERATION/SENTINEL #ifndef MICROPY_DEBUG_MP_OBJ_SENTINELS +#if NO_NLR +// Note: this is currently required for no NLR, to distinguish MP_OBJ_NULL (exception) from MP_OBJ_STOP_ITERATION +#define MICROPY_DEBUG_MP_OBJ_SENTINELS (1) +#else #define MICROPY_DEBUG_MP_OBJ_SENTINELS (0) #endif +#endif // Whether to enable a simple VM stack overflow check #ifndef MICROPY_DEBUG_VM_STACK_OVERFLOW @@ -835,6 +842,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 diff --git a/py/mpprint.c b/py/mpprint.c index c550c1d95..a43ad2631 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -376,13 +376,6 @@ int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, c } #endif -int mp_printf(const mp_print_t *print, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = mp_vprintf(print, fmt, ap); - va_end(ap); - return ret; -} int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { int chrs = 0; @@ -575,3 +568,12 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { } return chrs; } + +int mp_printf(const mp_print_t *print, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = mp_vprintf(print, fmt, ap); + va_end(ap); + return ret; +} + diff --git a/py/mpstate.h b/py/mpstate.h index 5f6cf5593..167683658 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -257,7 +257,11 @@ typedef struct _mp_state_thread_t { mp_obj_dict_t *dict_locals; mp_obj_dict_t *dict_globals; +#if NO_NLR + mp_obj_base_t *active_exception; +#else nlr_buf_t *nlr_top; +#endif #if MICROPY_PY_SYS_SETTRACE mp_obj_t prof_trace_callback; diff --git a/py/nativeglue.c b/py/nativeglue.c index 30e5b4006..4ad162d6f 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -159,12 +159,55 @@ STATIC mp_obj_t mp_native_call_function_n_kw(mp_obj_t fun_in, size_t n_args_kw, // wrapper that makes raise obj and raises it // END_FINALLY opcode requires that we don't raise if o==None +#if NO_NLR +STATIC mp_obj_t mp_native_raise(mp_obj_t o) { + if (o != MP_OBJ_NULL && o != mp_const_none) { + return mp_raise_o(mp_make_raise_obj(o)); + } + return MP_OBJ_SENTINEL; +} + +STATIC mp_obj_t mp_native_is_exc(void) { + if (MP_STATE_THREAD(active_exception) == NULL) { + return MP_OBJ_SENTINEL; + } else { + return MP_OBJ_NULL; + } +} + +STATIC mp_obj_t mp_native_get_exc(void) { + return MP_STATE_THREAD(active_exception); +} + +STATIC void mp_native_clr_exc(void) { + MP_STATE_THREAD(active_exception) = NULL; +} + +// wrapper that handles iterator buffer +STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { + if (iter == NULL) { + return mp_getiter(obj, NULL); + } else { + obj = mp_getiter(obj, iter); + if (obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + if (obj != MP_OBJ_FROM_PTR(iter)) { + // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. + iter->base.type = MP_OBJ_NULL; + iter->buf[0] = obj; + } + return MP_OBJ_SENTINEL; + } +} +#else STATIC void mp_native_raise(mp_obj_t o) { if (o != MP_OBJ_NULL && o != mp_const_none) { nlr_raise(mp_make_raise_obj(o)); } } + // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { if (iter == NULL) { @@ -179,6 +222,7 @@ STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { return NULL; } } +#endif // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_iternext(mp_obj_iter_buf_t *iter) { @@ -191,6 +235,38 @@ STATIC mp_obj_t mp_native_iternext(mp_obj_iter_buf_t *iter) { return mp_iternext(obj); } +#if NO_NLR +STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value) { + mp_obj_t throw_value = *ret_value; + if (throw_value != MP_OBJ_NULL) { + send_value = MP_OBJ_NULL; + } + mp_vm_return_kind_t ret_kind = mp_resume(gen, send_value, throw_value, ret_value); + + if (ret_kind == MP_VM_RETURN_YIELD) { + return true; + } else if (ret_kind == MP_VM_RETURN_NORMAL) { + if (*ret_value == MP_OBJ_STOP_ITERATION) { + *ret_value = mp_const_none; + } + } else { + assert(ret_kind == MP_VM_RETURN_EXCEPTION); + if (!mp_obj_exception_match(*ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { +#pragma message "caller must also check active_exception" + mp_raise_o(*ret_value); + return false; + } + *ret_value = mp_obj_exception_get_value(*ret_value); + } + + if (throw_value != MP_OBJ_NULL && mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { + mp_raise_o(mp_make_raise_obj(throw_value)); + return false; // caller must also check active_exception + } + return false; +} + +#else STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value) { mp_vm_return_kind_t ret_kind; nlr_buf_t nlr_buf; @@ -226,6 +302,7 @@ STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *re return false; } +#endif #if !MICROPY_PY_BUILTINS_FLOAT @@ -292,6 +369,11 @@ const mp_fun_table_t mp_fun_table = { #endif nlr_pop, mp_native_raise, +#if NO_NLR + mp_native_is_exc, + mp_native_get_exc, + mp_native_clr_exc, +#endif mp_import_name, mp_import_from, mp_import_all, diff --git a/py/nativeglue.h b/py/nativeglue.h index 9d9a97b9e..47714fc9c 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -30,7 +30,9 @@ #include "py/obj.h" #include "py/persistentcode.h" #include "py/stream.h" - +#if NO_NLR +#pragma message "these must correspond to the respective enum in nativeglue.h" +#endif typedef enum { MP_F_CONST_NONE_OBJ = 0, MP_F_CONST_FALSE_OBJ, @@ -67,6 +69,11 @@ typedef enum { MP_F_NLR_PUSH, MP_F_NLR_POP, MP_F_NATIVE_RAISE, +#if NO_NLR + MP_F_NATIVE_IS_EXC, + MP_F_NATIVE_GET_EXC, + MP_F_NATIVE_CLR_EXC, +#endif MP_F_IMPORT_NAME, MP_F_IMPORT_FROM, MP_F_IMPORT_ALL, @@ -121,6 +128,11 @@ typedef struct _mp_fun_table_t { unsigned int (*nlr_push)(nlr_buf_t *); void (*nlr_pop)(void); void (*raise)(mp_obj_t o); +#if NO_NLR + mp_obj_t (*mp_native_is_exc)(void); + mp_obj_t (*mp_native_get_exc)(void); + void (*mp_native_clr_exc)(void); +#endif mp_obj_t (*import_name)(qstr name, mp_obj_t fromlist, mp_obj_t level); mp_obj_t (*import_from)(mp_obj_t module, qstr name); void (*import_all)(mp_obj_t module); @@ -135,7 +147,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); diff --git a/py/nlr.h b/py/nlr.h index f9fbf56e5..386fa2bc3 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -79,6 +79,9 @@ #define MICROPY_NLR_POWERPC (1) // this could be less but using 128 for safety #define MICROPY_NLR_NUM_REGS (128) +#elif defined(__WASM__) + #define MICROPY_NLR_NUM_REGS 1 + #define MICROPY_NLR_SETJMP (0) #else #define MICROPY_NLR_SETJMP (1) //#warning "No native NLR support for this arch, using setjmp implementation" diff --git a/py/obj.c b/py/obj.c index 07b161255..ab2b56243 100644 --- a/py/obj.c +++ b/py/obj.c @@ -96,18 +96,32 @@ 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) { + if (o_in == MP_OBJ_NULL) { + return ""; + } return qstr_str(mp_obj_get_type(o_in)->name); } void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { // There can be data structures nested too deep, or just recursive +#if NO_NLR + if (o_in == MP_OBJ_NULL) { + return; + } + + if (MP_STACK_CHECK()) { + return; + } +#else MP_STACK_CHECK(); + #ifndef NDEBUG if (o_in == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); return; } #endif +#endif const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->print != NULL) { type->print((mp_print_t *)print, o_in, kind); @@ -147,7 +161,7 @@ void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc) { mp_obj_print_helper(print, exc, PRINT_EXC); mp_print_str(print, "\n"); } - +#include bool mp_obj_is_true(mp_obj_t arg) { if (arg == mp_const_false) { return 0; @@ -162,6 +176,10 @@ bool mp_obj_is_true(mp_obj_t arg) { return 1; } } else { +if(!arg) { + fprintf(stderr,"180:NPE arg=%p\n",arg); + return 0; +} 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); @@ -301,6 +319,12 @@ mp_int_t mp_obj_get_int(mp_const_obj_t arg) { return mp_obj_int_get_checked(arg); } else { mp_obj_t res = mp_unary_op(MP_UNARY_OP_INT, (mp_obj_t)arg); +#if NO_NLR +#pragma message "TODO: caller should check for error" + if (res == MP_OBJ_NULL) { + return 0; + } +#endif return mp_obj_int_get_checked(res); } } @@ -359,19 +383,175 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { mp_float_t val; if (!mp_obj_get_float_maybe(arg, &val)) { +#if NO_NLR + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to float")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert %s to float"), mp_obj_get_type_str(arg))); + return 0; + #endif +#else #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to float")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert %s to float"), mp_obj_get_type_str(arg)); #endif +#endif } return val; } +#endif + +#if NO_NLR +// ----------------------------------------------------------------------------------------------------- #if MICROPY_PY_BUILTINS_COMPLEX -void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { +#pragma message "NEED REVIEW: return exc on complex op and mp_obj_get_array_fixed_n not clear" +int mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { + if (arg == mp_const_false) { + *real = 0; + *imag = 0; + } else if (arg == mp_const_true) { + *real = 1; + *imag = 0; + } else if (mp_obj_is_small_int(arg)) { + *real = MP_OBJ_SMALL_INT_VALUE(arg); + *imag = 0; + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE + } else if (mp_obj_is_type(arg, &mp_type_int)) { + *real = mp_obj_int_as_float_impl(arg); + *imag = 0; + #endif + } else if (mp_obj_is_float(arg)) { + *real = mp_obj_float_get(arg); + *imag = 0; + } else if (mp_obj_is_type(arg, &mp_type_complex)) { + mp_obj_complex_get(arg, real, imag); + } else { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError_o("can't convert to complex"); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "can't convert %s to complex", mp_obj_get_type_str(arg))); + } + return 1; + } + return 0; +} + +bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { + if (arg == mp_const_false) { + *real = 0; + *imag = 0; + } else if (arg == mp_const_true) { + *real = 1; + *imag = 0; + } else if (mp_obj_is_small_int(arg)) { + *real = (mp_float_t)MP_OBJ_SMALL_INT_VALUE(arg); + *imag = 0; + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE + } else if (mp_obj_is_type(arg, &mp_type_int)) { + *real = mp_obj_int_as_float_impl(arg); + *imag = 0; + #endif + } else if (mp_obj_is_float(arg)) { + *real = mp_obj_float_get(arg); + *imag = 0; + } else if (mp_obj_is_type(arg, &mp_type_complex)) { + mp_obj_complex_get(arg, real, imag); + } else { + return false; + } + return true; +} + +#endif // MICROPY_PY_BUILTINS_COMPLEX + +// note: returned value in *items may point to the interior of a GC block +int mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { + if (mp_obj_is_type(o, &mp_type_tuple)) { + mp_obj_tuple_get(o, len, items); + return 0; + } else if (mp_obj_is_type(o, &mp_type_list)) { + mp_obj_list_get(o, len, items); + return 0; + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_TypeError(MP_ERROR_TEXT("expected tuple/list")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("object '%s' isn't a tuple or list"), mp_obj_get_type_str(o))); + #endif + return 1; + } +} + +// note: returned value in *items may point to the interior of a GC block +void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items) { + size_t seq_len; + mp_obj_get_array(o, &seq_len, items); + if (seq_len != len) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_ValueError(MP_ERROR_TEXT("tuple/list has wrong length")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("requested length %d but object has length %d"), (int)len, (int)seq_len) ); + #endif + } +} + +// is_slice determines whether the index is a slice index +size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice) { + mp_int_t i; + if (mp_obj_is_small_int(index)) { + i = MP_OBJ_SMALL_INT_VALUE(index); + } else if (!mp_obj_get_int_maybe(index, &i)) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_TypeError(MP_ERROR_TEXT("indices must be integers")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("%q indices must be integers, not %s"), + type->name, mp_obj_get_type_str(index))); + #endif + return (size_t)-1; + } + + if (i < 0) { + i += len; + } + if (is_slice) { + if (i < 0) { + i = 0; + } else if ((mp_uint_t)i > len) { + i = len; + } + } else { + if (i < 0 || (mp_uint_t)i >= len) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("index out of range")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_IndexError, + MP_ERROR_TEXT("%q index out of range"), type->name)); + #endif + return (size_t)-1; + } + } + + // By this point 0 <= i <= len and so fits in a size_t + return (size_t)i; +} + +#else +// ================================================================================================================= +#if MICROPY_PY_BUILTINS_COMPLEX +bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (arg == mp_const_false) { *real = 0; *imag = 0; @@ -392,6 +572,13 @@ 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(MP_ERROR_TEXT("can't convert to complex")); #else @@ -400,8 +587,7 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { #endif } } -#endif -#endif +#endif // MICROPY_PY_BUILTINS_COMPLEX // note: returned value in *items may point to the interior of a GC block void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { @@ -470,6 +656,8 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool // By this point 0 <= i <= len and so fits in a size_t return (size_t)i; } +// ================================================================================================================= +#endif // NO_NLR mp_obj_t mp_obj_id(mp_obj_t o_in) { mp_int_t id = (mp_int_t)o_in; @@ -494,10 +682,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(MP_ERROR_TEXT("object has no len")); + mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in))); #endif } else { return len; @@ -528,6 +716,14 @@ mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { const mp_obj_type_t *type = mp_obj_get_type(base); if (type->subscr != NULL) { mp_obj_t ret = type->subscr(base, index, value); + +#if NO_NLR + // MP_OBJ_NULL return can mean either unsupported or exception + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#pragma message "TODO: call base classes here ?" +#endif // NO_NLR if (ret != MP_OBJ_NULL) { return ret; } @@ -535,24 +731,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(MP_ERROR_TEXT("object doesn't support item deletion")); + mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base))); #endif } else if (value == MP_OBJ_SENTINEL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); + mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base))); #endif } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item assignment")); + mp_raise_TypeError_o(MP_ERROR_TEXT("object doesn't support item assignment")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base))); #endif } } @@ -581,11 +777,21 @@ bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { return true; } +#if NO_NLR +bool 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_o("object with buffer protocol required"); + return false; + } + return true; +} +#else void mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { if (!mp_get_buffer(obj, bufinfo, flags)) { mp_raise_TypeError(MP_ERROR_TEXT("object with buffer protocol required")); } } +#endif // NO_NLR mp_obj_t mp_generic_unary_op(mp_unary_op_t op, mp_obj_t o_in) { switch (op) { diff --git a/py/obj.h b/py/obj.h index 125acf118..adff259be 100644 --- a/py/obj.h +++ b/py/obj.h @@ -328,7 +328,31 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_OBJ_FUN_ARGS_MAX (0xffff) // to set maximum value in n_args_max below #define MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw) ((uint32_t)((((uint32_t)(n_args_min)) << 17) | (((uint32_t)(n_args_max)) << 1) | ((takes_kw) ? 1 : 0))) - +#if MICROPY_PY_FUNCTION_ATTRS +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_0}, .fun._0 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_1}, .fun._1 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_2}, .fun._2 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_3}, .fun._3 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun.var = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun.var = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name, .name = TOSTRING(fun_name)} +#else #define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ {{&mp_type_fun_builtin_0}, .fun._0 = fun_name} @@ -350,7 +374,7 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name} - +#endif // These macros are used to define constant map/dict objects // You can put "static" in front of the definition to make it local @@ -407,8 +431,8 @@ typedef struct _mp_rom_map_elem_t { 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 - size_t is_ordered : 1; // an ordered array + size_t is_fixed : 1; // if set, table is fixed/read-only and can't be modified + size_t is_ordered : 1; // if set, table is an ordered array, not a hash map size_t used : (8 * sizeof(size_t) - 3); size_t alloc; mp_map_elem_t *table; @@ -473,11 +497,15 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); // 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 (0x0040) -#define MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE (0x0080) +#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, @@ -519,7 +547,12 @@ typedef struct _mp_buffer_p_t { mp_int_t (*get_buffer)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); } mp_buffer_p_t; 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 NO_NLR +bool +#else +void +#endif +mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); struct _mp_obj_type_t { // A type is an object so must start with this entry, which points to mp_type_type. @@ -659,6 +692,9 @@ extern const mp_obj_type_t mp_type_StopAsyncIteration; extern const mp_obj_type_t mp_type_StopIteration; extern const mp_obj_type_t mp_type_SyntaxError; extern const mp_obj_type_t mp_type_SystemExit; +#if NO_NLR +extern const mp_obj_type_t mp_type_TimeoutError; +#endif extern const mp_obj_type_t mp_type_TypeError; extern const mp_obj_type_t mp_type_UnicodeError; extern const mp_obj_type_t mp_type_ValueError; @@ -708,6 +744,7 @@ extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; #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); @@ -776,9 +813,22 @@ bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); #if MICROPY_PY_BUILTINS_FLOAT 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); +#if MICROPY_PY_BUILTINS_COMPLEX +#if NO_NLR +int mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +#else void mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); -#endif +#endif // NO_NLR +bool mp_obj_get_complex_maybe(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +#else + #error wtf +#endif //MICROPY_PY_BUILTINS_COMPLEX +#endif //MICROPY_PY_BUILTINS_FLOAT +#if NO_NLR +int mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block +#else void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block +#endif 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 size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice); mp_obj_t mp_obj_id(mp_obj_t o_in); @@ -890,11 +940,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, size_t n_kw, const mp_obj_t *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; } @@ -916,12 +968,16 @@ typedef struct _mp_obj_slice_t { 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 typedef struct _mp_obj_fun_builtin_fixed_t { mp_obj_base_t base; +#if MICROPY_PY_FUNCTION_ATTRS + const char *name; //PMPP +#endif union { mp_fun_0_t _0; mp_fun_1_t _1; @@ -933,6 +989,9 @@ typedef struct _mp_obj_fun_builtin_fixed_t { typedef struct _mp_obj_fun_builtin_var_t { mp_obj_base_t base; uint32_t sig; // see MP_OBJ_FUN_MAKE_SIG +#if MICROPY_PY_FUNCTION_ATTRS + const char *name; //PMPP +#endif union { mp_fun_var_t var; mp_fun_kw_t kw; @@ -975,7 +1034,12 @@ const mp_obj_t *mp_obj_property_get(mp_obj_t self_in); void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times, void *dest); #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); +#if NO_NLR +int +#else +bool +#endif +mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes); #endif #define mp_seq_copy(dest, src, len, item_t) memcpy(dest, src, len * sizeof(item_t)) #define mp_seq_cat(dest, src1, len1, src2, len2, item_t) { memcpy(dest, src1, (len1) * sizeof(item_t)); memcpy(dest + (len1), src2, (len2) * sizeof(item_t)); } @@ -984,17 +1048,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/obj.h.rej b/py/obj.h.rej new file mode 100644 index 000000000..4a311bb26 --- /dev/null +++ b/py/obj.h.rej @@ -0,0 +1,25 @@ +--- py/obj.h 2020-06-21 17:11:12.313623997 +0200 ++++ py/obj.h 2020-06-21 15:08:06.096095863 +0200 +@@ -808,9 +840,20 @@ + #if MICROPY_PY_BUILTINS_FLOAT + 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); ++#if NO_NLR ++int ++#else ++void + #endif +-void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block ++ mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); ++#endif ++#if NO_NLR ++int ++#else ++void ++#endif ++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 + size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice); + mp_obj_t mp_obj_id(mp_obj_t o_in); diff --git a/py/objarray.c b/py/objarray.c index 4947f7590..57ef7b6c0 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong File use _no_nlr.c instead" +#endif #include #include #include diff --git a/py/objclosure.c b/py/objclosure.c index f5038ffc4..9a8c7631c 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 91e440230..f4c4aeffc 100644 --- a/py/objcomplex.c +++ b/py/objcomplex.c @@ -178,7 +178,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: diff --git a/py/objdeque.c b/py/objdeque.c index c95bdeee9..55c01efeb 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -43,7 +43,13 @@ typedef struct _mp_obj_deque_t { } mp_obj_deque_t; STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 2, 3, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 2, 3, false); +#endif /* Initialization from existing sequence is not supported, so an empty tuple must be passed as such. */ @@ -102,7 +108,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, MP_ERROR_TEXT("full")); + mp_raise_msg(&mp_type_IndexError, "full"); } self->items[self->i_put] = arg; @@ -122,7 +128,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, MP_ERROR_TEXT("empty")); + mp_raise_msg(&mp_type_IndexError, "empty"); } mp_obj_t ret = self->items[self->i_get]; diff --git a/py/objdict.c b/py/objdict.c index 7690eeab2..6241835e8 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -24,6 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "wrong file: use _no_nlr.c file" +#endif #include #include @@ -33,8 +36,6 @@ #include "py/objtype.h" #include "py/objstr.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 @@ -90,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, size_t n_kw, const mp_obj_t *args) { +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_t dict_out = mp_obj_new_dict(0); mp_obj_dict_t *dict = MP_OBJ_TO_PTR(dict_out); dict->base.type = type; @@ -212,12 +213,16 @@ STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { STATIC void mp_ensure_not_fixed(const mp_obj_dict_t *dict) { if (dict->map.is_fixed) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception(&mp_type_TypeError)); +#else mp_raise_TypeError(NULL); +#endif } } 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); @@ -227,8 +232,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); @@ -240,7 +245,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 @@ -275,7 +280,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); @@ -320,7 +325,7 @@ 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); if (self->map.used == 0) { @@ -345,7 +350,7 @@ 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 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])); + 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); @@ -354,7 +359,7 @@ STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwarg 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; @@ -512,7 +517,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); } @@ -536,7 +541,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; @@ -573,7 +578,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, @@ -586,7 +591,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, @@ -613,7 +618,7 @@ size_t mp_obj_dict_len(mp_obj_t 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_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); mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; diff --git a/py/objenumerate.c b/py/objenumerate.c index d1de4add4..0f5266c13 100644 --- a/py/objenumerate.c +++ b/py/objenumerate.c @@ -50,9 +50,15 @@ STATIC mp_obj_t enumerate_make_new(const mp_obj_type_t *type, size_t n_args, siz struct { mp_arg_val_t iterable, start; } arg_vals; - mp_arg_parse_all_kw_array(n_args, n_kw, args, - MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); - +#if NO_NLR + if (mp_arg_parse_all_kw_array(n_args, n_kw, args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals)) { + return MP_OBJ_NULL; + } +#else + mp_arg_parse_all_kw_array(n_args, n_kw, args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); +#endif // create enumerate object mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); o->base.type = type; @@ -81,6 +87,11 @@ STATIC mp_obj_t enumerate_iternext(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_enumerate)); mp_obj_enumerate_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next = mp_iternext(self->iter); +#if NO_NLR + if (next == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif if (next == MP_OBJ_STOP_ITERATION) { return MP_OBJ_STOP_ITERATION; } else { diff --git a/py/objfilter.c b/py/objfilter.c index 41b2a3bc5..25125cf46 100644 --- a/py/objfilter.c +++ b/py/objfilter.c @@ -47,7 +47,11 @@ STATIC mp_obj_t filter_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_filter)); mp_obj_filter_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next; +#if NO_NLR + while ((next = mp_iternext2(self->iter)) != MP_OBJ_NULL) { +#else while ((next = mp_iternext(self->iter)) != MP_OBJ_STOP_ITERATION) { +#endif mp_obj_t val; if (self->fun != mp_const_none) { val = mp_call_function_n_kw(self->fun, 1, 0, &next); @@ -58,6 +62,11 @@ STATIC mp_obj_t filter_iternext(mp_obj_t self_in) { return next; } } +#if NO_NLR + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_STOP_ITERATION; } diff --git a/py/objfloat.c b/py/objfloat.c index 09b73c8cd..5ff674f0d 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -52,8 +52,13 @@ typedef struct _mp_obj_float_t { mp_float_t value; } mp_obj_float_t; +#if NO_NLR +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}; +#else 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 #endif @@ -118,8 +123,13 @@ STATIC void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t STATIC mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 0, 1, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 0, 1, false); - +#endif switch (n_args) { case 0: return mp_obj_new_float(0); @@ -135,7 +145,15 @@ STATIC mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size return args[0]; } else { // something else, try to cast it to a float +#if NO_NLR + mp_float_t val = mp_obj_get_float(args[0]); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_float(val); +#else return mp_obj_new_float(mp_obj_get_float(args[0])); +#endif } } } diff --git a/py/objfun.c b/py/objfun.c index 3a63d8f43..b360255b3 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -58,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, @@ -72,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, @@ -86,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, @@ -100,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, @@ -130,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, @@ -197,8 +204,8 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) { MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state_out_var, n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args); \ \ /* state size in bytes */ \ - state_size_out_var = n_state_out_var *sizeof(mp_obj_t) \ - + n_exc_stack *sizeof(mp_exc_stack_t); \ + state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + + n_exc_stack * sizeof(mp_exc_stack_t); \ } #define INIT_CODESTATE(code_state, _fun_bc, _n_state, n_args, n_kw, args) \ @@ -355,6 +362,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, @@ -406,6 +414,7 @@ STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, co STATIC const mp_obj_type_t mp_type_fun_native = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = fun_native_call, .unary_op = mp_generic_unary_op, @@ -513,6 +522,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/objfun.h b/py/objfun.h index 905b5dbca..9c3c2bb17 100644 --- a/py/objfun.h +++ b/py/objfun.h @@ -46,4 +46,5 @@ typedef struct _mp_obj_fun_bc_t { void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); + #endif // MICROPY_INCLUDED_PY_OBJFUN_H diff --git a/py/objgenerator.c b/py/objgenerator.c index 5e140ba23..be97be4fb 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -73,6 +73,7 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons 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, @@ -84,7 +85,7 @@ const mp_obj_type_t mp_type_gen_wrap = { /******************************************************************************/ // native generator wrapper -#if MICROPY_EMIT_NATIVE +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // The state for a native generating function is held in the same struct as a bytecode function @@ -126,6 +127,7 @@ STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_k const mp_obj_type_t mp_type_native_gen_wrap = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_generator, .call = native_gen_wrap_call, .unary_op = mp_generic_unary_op, @@ -146,7 +148,15 @@ STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_pri } mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { +#if NO_NLR + if (MP_STACK_CHECK()) { + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } +#else MP_STACK_CHECK(); +#endif mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance)); mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->code_state.ip == 0) { @@ -157,7 +167,15 @@ 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) { + +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("generator already executing")); + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; +#else mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); +#endif } #if MICROPY_PY_GENERATOR_PEND_THROW @@ -170,7 +188,14 @@ 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(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); +#if NO_NLR + mp_raise_TypeError_o(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; +#else + mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); +#endif } } else { *self->code_state.sp = send_value; @@ -222,7 +247,21 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ case MP_VM_RETURN_EXCEPTION: { self->code_state.ip = 0; +#if NO_NLR + // TODO probably makes sense to have bytecode also place exception in active_exception + #if MICROPY_EMIT_NATIVE + if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { + *ret_val = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; // clear it + } else + #endif + { + *ret_val = self->code_state.state[0]; + } +#else *ret_val = self->code_state.state[0]; +#endif + // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); @@ -243,14 +282,22 @@ 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 { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &ret)); +#else nlr_raise(mp_obj_new_exception_arg1(&mp_type_StopIteration, ret)); +#endif } case MP_VM_RETURN_YIELD: return ret; case MP_VM_RETURN_EXCEPTION: +#if NO_NLR + return mp_raise_o(ret); +#else nlr_raise(ret); +#endif } } @@ -261,7 +308,11 @@ 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) { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception(&mp_type_StopIteration)); +#else mp_raise_type(&mp_type_StopIteration); +#endif } else { return ret; } @@ -288,7 +339,11 @@ 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) { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception(&mp_type_StopIteration)); +#else mp_raise_type(&mp_type_StopIteration); +#endif } else { return ret; } @@ -299,7 +354,11 @@ 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: +#if NO_NLR + return mp_raise_msg_o(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); +#endif // Swallow GeneratorExit (== successful close), and re-raise any other case MP_VM_RETURN_EXCEPTION: @@ -307,7 +366,11 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) { if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { return mp_const_none; } +#if NO_NLR + return mp_raise_o(ret); +#else nlr_raise(ret); +#endif default: // The only choice left is MP_VM_RETURN_NORMAL which is successful close @@ -320,7 +383,12 @@ 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) { +#if NO_NLR +#pragma message "TODO: ValueError or TypeError ?" + return mp_raise_TypeError_o("can't pend throw to just-started generator"); +#else mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); +#endif } mp_obj_t prev = self->pend_exc; self->pend_exc = exc_in; diff --git a/py/objgetitemiter.c b/py/objgetitemiter.c index 54e27b8f1..835154549 100644 --- a/py/objgetitemiter.c +++ b/py/objgetitemiter.c @@ -34,7 +34,27 @@ typedef struct _mp_obj_getitem_iter_t { mp_obj_base_t base; mp_obj_t args[3]; } mp_obj_getitem_iter_t; - +#if NO_NLR +STATIC mp_obj_t it_iternext(mp_obj_t self_in) { + mp_obj_getitem_iter_t *self = MP_OBJ_TO_PTR(self_in); + // try to get next item + mp_obj_t value = mp_call_method_n_kw(1, 0, self->args); + if (value == MP_OBJ_NULL) { + // an exception was raised + mp_obj_type_t *t = (mp_obj_type_t *)MP_STATE_THREAD(active_exception)->type; + if (t == &mp_type_StopIteration || t == &mp_type_IndexError) { + // return MP_OBJ_STOP_ITERATION instead of raising + MP_STATE_THREAD(active_exception) = NULL; + return MP_OBJ_STOP_ITERATION; + } else { + // re-raise exception + return MP_OBJ_NULL; + } + } + self->args[2] = MP_OBJ_NEW_SMALL_INT(MP_OBJ_SMALL_INT_VALUE(self->args[2]) + 1); + return value; +} +#else STATIC mp_obj_t it_iternext(mp_obj_t self_in) { mp_obj_getitem_iter_t *self = MP_OBJ_TO_PTR(self_in); nlr_buf_t nlr; @@ -56,6 +76,7 @@ STATIC mp_obj_t it_iternext(mp_obj_t self_in) { } } } +#endif STATIC const mp_obj_type_t it_type = { { &mp_type_type }, diff --git a/py/objint.c b/py/objint.c index 4619fb575..74a38308e 100644 --- a/py/objint.c +++ b/py/objint.c @@ -38,10 +38,11 @@ #if MICROPY_PY_BUILTINS_FLOAT #include #endif - +#include // This dispatcher function is expected to be independent of the implementation of long int STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; + mp_arg_check_num(n_args, n_kw, 0, 2, false); switch (n_args) { @@ -49,6 +50,10 @@ STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, return MP_OBJ_NEW_SMALL_INT(0); case 1: + if (!args[0]) { + fprintf(stderr,"54:mp_obj_int_make_new NPE\n"); + return MP_OBJ_NULL; + } if (mp_obj_is_int(args[0])) { // already an int (small or long), just return it return args[0]; @@ -67,8 +72,13 @@ STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, case 2: default: { + if (!args[0]) { + fprintf(stderr,"76:mp_obj_int_make_new NPE\n"); + return MP_OBJ_NULL; + } // should be a string, parse it size_t l; + fprintf(stderr,"72:mp_obj_int_make_new %p\n", args[0] ); const char *s = mp_obj_str_get_data(args[0], &l); return mp_parse_num_integer(s, l, mp_obj_get_int(args[1]), NULL); } @@ -134,6 +144,36 @@ STATIC mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { #undef MP_FLOAT_SIGN_SHIFT_I32 #undef MP_FLOAT_EXP_SHIFT_I32 +#if NO_NLR +mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { + + int cl = fpclassify((float)val); + if (cl == FP_INFINITE) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("can't convert inf to int")); + } else if (cl == FP_NAN) { + mp_raise_ValueError(MP_ERROR_TEXT("can't convert NaN to int")); + } else { + mp_fp_as_int_class_t icl = mp_classify_fp_as_int(val); + if (icl == MP_FP_CLASS_FIT_SMALLINT) { + return MP_OBJ_NEW_SMALL_INT((mp_int_t)val); + #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ + } else { + mp_obj_int_t *o = mp_obj_int_new_mpz(); + mpz_set_from_float(&o->mpz, val); + return MP_OBJ_FROM_PTR(o); + } + #else + #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG + } else if (icl == MP_FP_CLASS_FIT_LONGINT) { + return mp_obj_new_int_from_ll((long long)val); + #endif + } else { + mp_raise_ValueError(MP_ERROR_TEXT("float too big")); + } + #endif + } +} +#else // NO_NLR mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { mp_float_union_t u = {val}; // IEEE-754: if biased exponent is all 1 bits... @@ -165,6 +205,7 @@ mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { #endif } } +#endif // NO_NLR #endif diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 6e52073a6..9f36256ad 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -275,6 +275,11 @@ 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_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: { mp_int_t irhs = mp_obj_int_get_checked(rhs_in); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (irhs < 0) { mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } @@ -416,6 +421,9 @@ mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { } } +#if NO_NLR +#pragma message "TODO callers must handle exceptions mp_obj_(u)int_get_checked" +#endif mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); @@ -426,7 +434,13 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return value; } else { // overflow +#if NO_NLR + mp_raise_msg_o(&mp_type_OverflowError, MP_ERROR_TEXT("438:overflow converting long int to machine word")); + return 0; // TODO callers must handle exceptions +#else mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); +#endif + } } } @@ -444,7 +458,12 @@ mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in) { } } +#if NO_NLR + mp_raise_msg_o(&mp_type_OverflowError, MP_ERROR_TEXT("462:overflow converting long int to machine word")); + return 0; // TODO callers must handle exceptions +#else mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); +#endif } #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/objlist.c b/py/objlist.c index 8c989facc..69424c479 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -23,6 +23,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include diff --git a/py/objlist.h b/py/objlist.h index a43663db7..5a01dd650 100644 --- a/py/objlist.h +++ b/py/objlist.h @@ -35,6 +35,10 @@ typedef struct _mp_obj_list_t { mp_obj_t *items; } mp_obj_list_t; +#if NO_NLR +mp_obj_list_t *mp_obj_list_init(mp_obj_list_t *o, size_t n); +#else void mp_obj_list_init(mp_obj_list_t *o, size_t n); +#endif #endif // MICROPY_INCLUDED_PY_OBJLIST_H diff --git a/py/objmap.c b/py/objmap.c index 78c52c892..557bfe684 100644 --- a/py/objmap.c +++ b/py/objmap.c @@ -59,6 +59,12 @@ STATIC mp_obj_t map_iternext(mp_obj_t self_in) { m_del(mp_obj_t, nextses, self->n_iters); return MP_OBJ_STOP_ITERATION; } +#if NO_NLR + if (next == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } +#endif nextses[i] = next; } return mp_call_function_n_kw(self->fun, self->n_iters, 0, nextses); diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c index e4543f5b9..e0b18d3c6 100644 --- a/py/objnamedtuple.c +++ b/py/objnamedtuple.c @@ -87,7 +87,11 @@ STATIC 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 +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't set attribute")) ); +#else mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't set attribute")); +#endif } } @@ -97,14 +101,17 @@ STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, if (n_args + n_kw != num_fields) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); +#if NO_NLR + return MP_OBJ_NULL; +#endif #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL - mp_raise_msg_varg(&mp_type_TypeError, + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), - num_fields, n_args + n_kw); + num_fields, n_args + n_kw)); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED - mp_raise_msg_varg(&mp_type_TypeError, + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("%q() takes %d positional arguments but %d were given"), - type->base.name, num_fields, n_args + n_kw); + type->base.name, num_fields, n_args + n_kw)); #endif } @@ -123,16 +130,21 @@ STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, if (id == (size_t)-1) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); +#if NO_NLR + return MP_OBJ_NULL; +#endif #else - mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw)); #endif } if (tuple->items[id] != MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_arg_error_terse_mismatch(); + mp_arg_error_terse_mismatch(); + return MP_OBJ_NULL; #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw)); #endif } tuple->items[id] = args[i + 1]; @@ -176,7 +188,13 @@ STATIC mp_obj_t new_namedtuple_type(mp_obj_t name_in, mp_obj_t fields_in) { fields_in = mp_obj_str_split(1, &fields_in); } #endif +#if NO_NLR + if (mp_obj_get_array(fields_in, &n_fields, &fields)) { + return MP_OBJ_NULL; + } +#else mp_obj_get_array(fields_in, &n_fields, &fields); +#endif return mp_obj_new_namedtuple_type(name, n_fields, fields); } MP_DEFINE_CONST_FUN_OBJ_2(mp_namedtuple_obj, new_namedtuple_type); diff --git a/py/objrange.c b/py/objrange.c index 4eed4b941..b7cb80678 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -90,7 +90,13 @@ STATIC void range_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind } STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 1, 3, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 1, 3, false); +#endif mp_obj_range_t *o = m_new_obj(mp_obj_range_t); o->base.type = type; @@ -110,6 +116,12 @@ STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t } } +#if NO_NLR + // check for errors from mp_obj_get_int + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_FROM_PTR(o); } diff --git a/py/objset.c b/py/objset.c index f31a901a7..4c102469c 100644 --- a/py/objset.c +++ b/py/objset.c @@ -175,6 +175,11 @@ STATIC mp_obj_t set_copy(mp_obj_t self_in) { check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_set_t *other = m_new_obj(mp_obj_set_t); +#if NO_NLR + if (other == NULL) { + return MP_OBJ_NULL; + } +#endif other->base.type = self->base.type; mp_set_init(&other->set, self->set.alloc); other->set.used = self->set.used; @@ -372,7 +377,7 @@ STATIC mp_obj_t set_remove(mp_obj_t self_in, mp_obj_t item) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); if (mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_KeyError, item)); + mp_raise_or_return(mp_obj_new_exception_arg1(&mp_type_KeyError, item)); } return mp_const_none; } @@ -579,6 +584,11 @@ const mp_obj_type_t mp_type_frozenset = { mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { mp_obj_set_t *o = m_new_obj(mp_obj_set_t); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_set; mp_set_init(&o->set, n_args); for (size_t i = 0; i < n_args; i++) { diff --git a/py/objslice.c b/py/objslice.c index c65c30601..8e8800c9f 100644 --- a/py/objslice.c +++ b/py/objslice.c @@ -51,7 +51,11 @@ STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { mp_int_t length = mp_obj_int_get_checked(length_obj); mp_bound_slice_t bound_indices; mp_obj_slice_indices(self_in, length, &bound_indices); - +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_obj_t results[3] = { MP_OBJ_NEW_SMALL_INT(bound_indices.start), MP_OBJ_NEW_SMALL_INT(bound_indices.stop), @@ -59,6 +63,7 @@ STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { }; return mp_obj_new_tuple(3, results); } + STATIC MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices); #endif @@ -105,6 +110,11 @@ const mp_obj_type_t mp_type_slice = { mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) { mp_obj_slice_t *o = m_new_obj(mp_obj_slice_t); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_slice; o->start = ostart; o->stop = ostop; @@ -124,7 +134,11 @@ 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) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("slice step can't be zero")); +#else mp_raise_ValueError(MP_ERROR_TEXT("slice step can't be zero")); +#endif } } diff --git a/py/objstr.c b/py/objstr.c index a276a255e..415fcd2d6 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include diff --git a/py/objstringio.c b/py/objstringio.c index ef942e74e..5a3b1dcc7 100644 --- a/py/objstringio.c +++ b/py/objstringio.c @@ -35,6 +35,22 @@ #if MICROPY_PY_IO +#if NO_NLR + +#if MICROPY_CPYTHON_COMPAT +STATIC int check_stringio_is_open(const mp_obj_stringio_t *o) { + if (o->vstr == NULL) { + mp_raise_ValueError_o(MP_ERROR_TEXT("I/O operation on closed file")); + return 1; + } + return 0; +} +#else +#define check_stringio_is_open(o) 0 +#endif + +#else // NO_NLR + #if MICROPY_CPYTHON_COMPAT STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { if (o->vstr == NULL) { @@ -45,6 +61,8 @@ STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { #define check_stringio_is_open(o) #endif +#endif // NO_NLR + STATIC void stringio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); @@ -54,7 +72,14 @@ STATIC void stringio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k STATIC mp_uint_t stringio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); + +#if NO_NLR + if (check_stringio_is_open(o)) { + return MP_STREAM_ERROR; + } +#else check_stringio_is_open(o); +#endif if (o->vstr->len <= o->pos) { // read to EOF, or seeked to EOF or beyond return 0; } @@ -78,8 +103,14 @@ STATIC void stringio_copy_on_write(mp_obj_stringio_t *o) { STATIC mp_uint_t stringio_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); - check_stringio_is_open(o); +#if NO_NLR + if (check_stringio_is_open(o)) { + return MP_STREAM_ERROR; + } +#else + check_stringio_is_open(o); +#endif if (o->vstr->fixed_buf) { stringio_copy_on_write(o); } @@ -164,8 +195,16 @@ STATIC mp_uint_t stringio_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, STATIC mp_obj_t stringio_getvalue(mp_obj_t self_in) { mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); +#if NO_NLR +#pragma message "TODO: Try to avoid copying string" + if (check_stringio_is_open(self)) { + return MP_OBJ_NULL; + } +#else check_stringio_is_open(self); // TODO: Try to avoid copying string +#endif + return mp_obj_new_str_of_type(STREAM_TO_CONTENT_TYPE(self), (byte *)self->vstr->buf, self->vstr->len); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(stringio_getvalue_obj, stringio_getvalue); @@ -185,8 +224,8 @@ STATIC mp_obj_stringio_t *stringio_new(const mp_obj_type_t *type) { } STATIC mp_obj_t stringio_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - (void)n_kw; // TODO check n_kw==0 - + (void)n_kw; +#pragma message "TODO: check n_kw==0" mp_uint_t sz = 16; bool initdata = false; mp_buffer_info_t bufinfo; diff --git a/py/objstrunicode.c b/py/objstrunicode.c index b21df22a3..5b2ca819b 100644 --- a/py/objstrunicode.c +++ b/py/objstrunicode.c @@ -129,7 +129,14 @@ 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_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index)); +#if NO_NLR + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index))); + return NULL; +#else + mp_raise_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index)); +#endif } const byte *s, *top = self_data + self_len; if (i < 0) { @@ -139,7 +146,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_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); + goto excepthandler; } if (!UTF8_IS_CONT(*s)) { ++i; @@ -158,7 +165,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_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); + goto excepthandler; } // Then check completion if (i-- == 0) { @@ -172,6 +179,13 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s } } return s; +excepthandler: +#if NO_NLR + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); +#else + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); +#endif + return NULL; } STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { @@ -195,6 +209,10 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { const byte *pstart, *pstop; if (ostart != mp_const_none) { pstart = str_index_to_ptr(type, self_data, self_len, ostart, true); +#if NO_NLR + if (pstart==NULL) + return MP_OBJ_NULL; +#endif } else { pstart = self_data; } @@ -202,6 +220,11 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { // pstop will point just after the stop character. This depends on // the \0 at the end of the string. pstop = str_index_to_ptr(type, self_data, self_len, ostop, true); +#if NO_NLR + if (pstop==NULL) + return MP_OBJ_NULL; +#endif + } else { pstop = self_data + self_len; } @@ -212,6 +235,11 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } #endif const byte *s = str_index_to_ptr(type, self_data, self_len, index, false); +#if NO_nLR + if (s == NULL) { + return MP_OBJ_NULL; + } +#endif int len = 1; if (UTF8_IS_NONASCII(*s)) { // Count the number of 1 bits (after the first) diff --git a/py/objtuple.c b/py/objtuple.c index 07a560ac0..98bfd9fac 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -86,7 +86,10 @@ STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg mp_obj_t iterable = mp_getiter(args[0], NULL); mp_obj_t item; - while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + while ((item = mp_iternext(iterable)) != MP_OBJ_NULL) { +#if NO_NLR + if ( item == MP_OBJ_STOP_ITERATION) goto done; +#endif if (len >= alloc) { items = m_renew(mp_obj_t, items, alloc, alloc * 2); alloc *= 2; @@ -94,6 +97,12 @@ STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg items[len++] = item; } +#if NO_NLR + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } +done:; +#endif mp_obj_t tuple = mp_obj_new_tuple(len, items); m_del(mp_obj_t, items, alloc); @@ -193,6 +202,12 @@ mp_obj_t mp_obj_tuple_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } #endif size_t index_value = mp_get_index(self->base.type, self->len, index, false); +#if NO_NLR + if (index_value == (size_t)-1) { + // exception + return MP_OBJ_NULL; + } +#endif return self->items[index_value]; } else { return MP_OBJ_NULL; // op not supported @@ -240,6 +255,11 @@ mp_obj_t mp_obj_new_tuple(size_t n, const mp_obj_t *items) { return mp_const_empty_tuple; } mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_tuple; o->len = n; if (items) { diff --git a/py/objtype.c b/py/objtype.c index d08c69e28..54fab90b8 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -153,7 +153,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) { @@ -351,8 +351,8 @@ mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("__init__() should return None")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("__init__() should return None, not '%s'"), mp_obj_get_type_str(init_ret)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("__init__() should return None, not '%s'"), mp_obj_get_type_str(init_ret))); #endif } } @@ -423,6 +423,11 @@ STATIC mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) { case MP_UNARY_OP_HASH: // __hash__ must return a small int val = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int_truncated(val)); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif break; case MP_UNARY_OP_INT: // Must return int @@ -588,16 +593,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 @@ -627,7 +629,11 @@ 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) { +#if NO_NLR + mp_raise_msg_o(&mp_type_AttributeError, MP_ERROR_TEXT("unreadable attribute")); +#else mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("unreadable attribute")); +#endif } else { dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); } @@ -866,8 +872,8 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(self_in)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(self_in))); #endif } mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); @@ -988,11 +994,21 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (self->make_new == NULL) { + // module(name[, doc]) + if (n_args) { + const char * mod_name = mp_obj_str_get_str(args[0]); + if (mod_name) { + return mp_obj_new_module( qstr_from_strn(mod_name, strlen(mod_name) )); + } + return MP_OBJ_NULL; + } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("can't create instance")); + mp_raise_TypeError(MP_ERROR_TEXT("can't create instance")); #else - mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't create '%q' instances"), self->name); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("cannot create '%q' instances"), self->name)); #endif + } } // make new instance @@ -1013,6 +1029,24 @@ 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) { + dict = mp_obj_new_dict(0); + } + 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; @@ -1041,7 +1075,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 @@ -1060,7 +1094,14 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { if (self->flags & MP_TYPE_FLAG_IS_SUBCLASSED) { // This class is already subclassed so can't have special accessors added - mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#if NO_NLR + mp_raise_msg_o(&mp_type_AttributeError, + MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#else + mp_raise_msg(&mp_type_AttributeError, + MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#endif + return; } self->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } @@ -1091,7 +1132,7 @@ 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); } @@ -1108,13 +1149,13 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) mp_raise_TypeError(NULL); } mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); - // TODO: Verify with CPy, tested on function type +#pragma message "TODO: Verify with CPy, tested on function type" if (t->make_new == NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("type isn't an acceptable base type")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name)); #endif } #if ENABLE_SPECIAL_ACCESSORS @@ -1300,10 +1341,17 @@ const mp_obj_type_t mp_type_super = { .attr = super_attr, }; +#if NO_NLR +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest) { + mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; + return mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); +} +#else void mp_load_super_method(qstr attr, mp_obj_t *dest) { mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); } +#endif /******************************************************************************/ // subclassing and built-ins specific to types diff --git a/py/objzip.c b/py/objzip.c index 4abc917c3..bd3e512a6 100644 --- a/py/objzip.c +++ b/py/objzip.c @@ -58,6 +58,12 @@ STATIC mp_obj_t zip_iternext(mp_obj_t self_in) { for (size_t i = 0; i < self->n_iters; i++) { mp_obj_t next = mp_iternext(self->iters[i]); +#if NO_NLR + if (next == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } +#endif if (next == MP_OBJ_STOP_ITERATION) { mp_obj_tuple_del(MP_OBJ_FROM_PTR(tuple)); return MP_OBJ_STOP_ITERATION; diff --git a/py/parse.c b/py/parse.c index b93282165..7ad3570de 100644 --- a/py/parse.c +++ b/py/parse.c @@ -284,6 +284,11 @@ STATIC void *parser_alloc(parser_t *parser, size_t num_bytes) { alloc = num_bytes; } chunk = (mp_parse_chunk_t *)m_new(byte, sizeof(mp_parse_chunk_t) + alloc); +#if NO_NLR + if (chunk == NULL) { + return NULL; + } +#endif chunk->alloc = alloc; chunk->union_.used = 0; parser->cur_chunk = chunk; @@ -453,6 +458,11 @@ STATIC void push_result_node(parser_t *parser, mp_parse_node_t pn) { STATIC mp_parse_node_t make_node_const_object(parser_t *parser, size_t src_line, mp_obj_t obj) { mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_obj_t)); +#if NO_NLR + if (pn == NULL) { + return MP_PARSE_NODE_NULL; + } +#endif pn->source_line = src_line; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // nodes are 32-bit pointers, but need to store 64-bit object @@ -611,7 +621,11 @@ STATIC bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu return false; } +#if NO_NLR +STATIC int fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { +#else STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { +#endif // this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4 // it does not do partial folding, eg 1 + 2 + x -> 3 + x @@ -724,7 +738,7 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { 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); + mp_raise_or_return_value(exc, -1); } // store the value in the table of dynamic constants @@ -825,6 +839,11 @@ STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, #endif mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * num_args); +#if NO_NLR + if (pn == NULL) { + return; + } +#endif pn->source_line = src_line; pn->kind_num_nodes = (rule_id & 0xff) | (num_args << 8); for (size_t i = num_args; i > 0; i--) { @@ -847,6 +866,11 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { parser.result_stack_top = 0; parser.result_stack = m_new(mp_parse_node_t, parser.result_stack_alloc); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return parser.tree; + } +#endif parser.lexer = lex; parser.tree.chunk = NULL; @@ -1126,6 +1150,12 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { break; } } + +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return parser.tree; + } +#endif } #if MICROPY_COMP_CONST @@ -1162,7 +1192,7 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { // add traceback to give info about file name and location // we don't have a 'block' name, so just pass the NULL qstr to indicate this mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); - nlr_raise(exc); + mp_raise_or_return_value(exc, parser.tree); } // get the root parse node that we created diff --git a/py/parsenum.c b/py/parsenum.c index e665da7d8..2c164ee8e 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -36,14 +36,18 @@ #include #endif +#if NO_NLR +STATIC mp_obj_t raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +#else STATIC NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +#endif // if lex!=NULL then the parser called us and we need to convert the // exception's type from ValueError to SyntaxError and add traceback info if (lex != NULL) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(exc))->type = &mp_type_SyntaxError; mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); } - nlr_raise(exc); + mp_raise_or_return(exc); } mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) { @@ -148,10 +152,16 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m #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")); +#if NO_NLR + return +#endif 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); +#if NO_NLR + return +#endif raise_exc(exc, lex); #else vstr_t vstr; @@ -161,8 +171,14 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m 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)); +#if NO_NLR +#else raise_exc(exc, lex); +#endif #endif +#if NO_NLR + return raise_exc(exc, lex); +#endif } } @@ -344,6 +360,9 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } #else if (imag || force_complex) { +#if NO_NLR + return +#endif raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("complex values not supported")), lex); } #endif @@ -352,9 +371,15 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } value_error: +#if NO_NLR + return +#endif raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for number")), lex); #else +#if NO_NLR + return +#endif 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 386ea4947..f0e393c15 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -542,12 +542,22 @@ 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) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("incompatible .mpy file")); + return NULL; +#else mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); +#endif } 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)) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("incompatible .mpy arch")); + return NULL; +#else mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy arch")); +#endif } } qstr_window_t qw; @@ -841,8 +851,12 @@ void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename) { MP_THREAD_GIL_ENTER(); } +#else +#if __WASM__ +#pragma message "#error mp_raw_code_save_file not implemented for this platform" #else #error mp_raw_code_save_file not implemented for this platform #endif +#endif #endif // MICROPY_PERSISTENT_CODE_SAVE diff --git a/py/pystack.c b/py/pystack.c index f7323fd74..c761061ac 100644 --- a/py/pystack.c +++ b/py/pystack.c @@ -43,8 +43,15 @@ void *mp_pystack_alloc(size_t n_bytes) { #endif if (MP_STATE_THREAD(pystack_cur) + n_bytes > MP_STATE_THREAD(pystack_end)) { // out of memory in the pystack +#if NO_NLR + fprintf(stderr,"mp_pystack_alloc '%p' + '%ld' > '%p'\n", MP_STATE_THREAD(pystack_cur) , (size_t)n_bytes , MP_STATE_THREAD(pystack_end) ); + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_RuntimeError, + MP_OBJ_NEW_QSTR(MP_QSTR_pystack_space_exhausted))); + return NULL; +#else nlr_raise(mp_obj_new_exception_arg1(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_pystack_space_exhausted))); +#endif } void *ptr = MP_STATE_THREAD(pystack_cur); MP_STATE_THREAD(pystack_cur) += n_bytes; diff --git a/py/qstr.c b/py/qstr.c index c14ec5ae0..aff77e93e 100644 --- a/py/qstr.c +++ b/py/qstr.c @@ -155,6 +155,9 @@ STATIC qstr qstr_add(const byte *q_ptr) { if (pool == NULL) { QSTR_EXIT(); m_malloc_fail(new_alloc); +#if NO_NLR + return 0; +#endif } pool->prev = MP_STATE_VM(last_pool); pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len; @@ -201,7 +204,12 @@ 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(); +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); + return 0; +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); +#endif } // compute number of bytes needed to intern this string @@ -233,6 +241,9 @@ qstr qstr_from_strn(const char *str, size_t len) { if (MP_STATE_VM(qstr_last_chunk) == NULL) { QSTR_EXIT(); m_malloc_fail(n_bytes); +#if NO_NLR + return 0; +#endif } al = n_bytes; } diff --git a/py/reader.c b/py/reader.c index d68406b1c..c13bf1a24 100644 --- a/py/reader.c +++ b/py/reader.c @@ -113,6 +113,11 @@ STATIC void mp_reader_posix_close(void *data) { 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); +#if NO_NLR + if (rp == NULL) { + return; + } +#endif rp->close_fd = close_fd; rp->fd = fd; MP_THREAD_GIL_EXIT(); @@ -122,7 +127,12 @@ void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { close(fd); } MP_THREAD_GIL_ENTER(); +#if NO_NLR + mp_raise_OSError_o(errno); + return; +#else mp_raise_OSError(errno); +#endif } MP_THREAD_GIL_ENTER(); rp->len = n; @@ -139,7 +149,13 @@ void mp_reader_new_file(mp_reader_t *reader, const char *filename) { int fd = open(filename, O_RDONLY, 0644); MP_THREAD_GIL_ENTER(); if (fd < 0) { +#if NO_NLR + mp_raise_OSError_o(errno); + return; +#else mp_raise_OSError(errno); +#endif + } mp_reader_new_file_from_fd(reader, fd, true); } diff --git a/py/ringbuf.c b/py/ringbuf.c index 83887b300..8f049a237 100644 --- a/py/ringbuf.c +++ b/py/ringbuf.c @@ -23,7 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "ringbuf.h" +#include "py/ringbuf.h" int ringbuf_get16(ringbuf_t *r) { int v = ringbuf_peek16(r); diff --git a/py/ringbuf.h b/py/ringbuf.h index 293e41830..468584896 100644 --- a/py/ringbuf.h +++ b/py/ringbuf.h @@ -63,6 +63,13 @@ static inline int ringbuf_get(ringbuf_t *r) { return v; } +static inline int ringbuf_peek(ringbuf_t *r) { + if (r->iget == r->iput) { + return -1; + } + return r->buf[r->iget]; +} + static inline int ringbuf_put(ringbuf_t *r, uint8_t v) { uint32_t iput_new = r->iput + 1; if (iput_new >= r->size) { diff --git a/py/runtime.c b/py/runtime.c index 157bc74a8..2a4eb8476 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -24,6 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -993,6 +996,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, }; @@ -1011,49 +1015,54 @@ 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) - && type != &mp_type_object) { - // we extracted a builtin method without a first argument, so we must - // wrap this function in a type checker - // Note that object will do its own checking so shouldn't be wrapped. - 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; } } else { - // class member is a value, so just return that value + // `member` is a value, so just return that value. dest[0] = member; } } @@ -1076,6 +1085,13 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { dest[0] = MP_OBJ_FROM_PTR(type); return; } + if (attr == MP_QSTR___base__) { // PMPP https://github.com/micropython/micropython/pull/4368 + const mp_obj_type_t *t = MP_OBJ_TO_PTR(obj); + if (mp_obj_is_instance_type(t)) { + dest[0] = MP_OBJ_FROM_PTR(t->parent); + return; // success + } + } #endif if (attr == MP_QSTR___next__ && type->iternext != NULL) { @@ -1096,6 +1112,13 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { mp_convert_member_lookup(obj, type, elem->value, dest); } } + #if MICROPY_PY_FUNCTION_ATTRS //PMPP + else if (attr == MP_QSTR___name__) { + if ( type->name == MP_QSTR_function) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(obj)); + } + } + #endif } void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { diff --git a/py/runtime.h b/py/runtime.h index 0bf988b90..f1b4a95ff 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -29,6 +29,193 @@ #include "py/mpstate.h" #include "py/pystack.h" +#if NO_NLR + +typedef enum { + MP_VM_RETURN_NORMAL, + MP_VM_RETURN_YIELD, + MP_VM_RETURN_EXCEPTION, +} mp_vm_return_kind_t; + +typedef enum { + MP_ARG_BOOL = 0x001, + MP_ARG_INT = 0x002, + MP_ARG_OBJ = 0x003, + MP_ARG_KIND_MASK = 0x0ff, + MP_ARG_REQUIRED = 0x100, + MP_ARG_KW_ONLY = 0x200, +} mp_arg_flag_t; + +typedef union _mp_arg_val_t { + bool u_bool; + mp_int_t u_int; + mp_obj_t u_obj; + mp_rom_obj_t u_rom_obj; +} mp_arg_val_t; + +typedef struct _mp_arg_t { + uint16_t qst; + uint16_t flags; + mp_arg_val_t defval; +} mp_arg_t; + +// Tables mapping operator enums to qstrs, defined in objtype.c +extern const byte mp_unary_op_method_name[]; +extern const byte mp_binary_op_method_name[]; + +void mp_init(void); +void mp_deinit(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); +} +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +#endif + +// extra printing method specifically for mp_obj_t's which are integral type +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); + +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); +static inline int mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { + return mp_arg_check_num_sig(n_args, n_kw, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw)); +} +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +void mp_arg_error_terse_mismatch(void); +void mp_arg_error_unimpl_kw(void); + +static inline mp_obj_dict_t *mp_locals_get(void) { + return MP_STATE_THREAD(dict_locals); +} +static inline void mp_locals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_locals) = d; +} +static inline mp_obj_dict_t *mp_globals_get(void) { + return MP_STATE_THREAD(dict_globals); +} +static inline void mp_globals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_globals) = d; +} + +mp_obj_t mp_load_name(qstr qst); +mp_obj_t mp_load_global(qstr qst); +mp_obj_t mp_load_build_class(void); +void mp_store_name(qstr qst, mp_obj_t obj); +void mp_store_global(qstr qst, mp_obj_t obj); +mp_obj_t mp_delete_name(qstr qst); +mp_obj_t mp_delete_global(qstr qst); + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg); +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs); + +mp_obj_t mp_call_function_0(mp_obj_t fun); +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); +mp_obj_t mp_call_function_n_kw(mp_obj_t fun, size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_self_n_kw(mp_obj_t meth, mp_obj_t self, size_t n_args, size_t n_kw, const mp_obj_t *args); +// Call function and catch/dump exception - for Python callbacks from C code +// (return MP_OBJ_NULL in case of exception). +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); + +typedef struct _mp_call_args_t { + mp_obj_t fun; + size_t n_args, n_kw, n_alloc; + mp_obj_t *args; +} mp_call_args_t; + +#if MICROPY_STACKLESS +// Takes arguments which are the most general mix of Python arg types, and +// prepares argument array suitable for passing to ->call() method of a +// function object (and mp_call_function_n_kw()). +// (Only needed in stackless mode.) +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); +#endif + +mp_obj_t mp_unpack_sequence(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_unpack_ex(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value); +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr); +void mp_convert_member_lookup(mp_obj_t obj, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest); +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc); +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest); +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val); + +mp_obj_t mp_getiter(mp_obj_t o, mp_obj_iter_buf_t *iter_buf); +mp_obj_t mp_iternext_allow_raise(mp_obj_t o); // may return MP_OBJ_STOP_ITERATION instead of raising StopIteration() +mp_obj_t mp_iternext(mp_obj_t o); // will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration(...) +mp_obj_t mp_iternext2(mp_obj_t o); +bool mp_iternext_had_exc(void); +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); + +mp_obj_t mp_make_raise_obj(mp_obj_t o); + +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); + +void mp_raise_recursion_depth(void); +mp_obj_t mp_raise_o(mp_obj_t exc); +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg); +mp_obj_t mp_raise_ValueError_o(const char *msg); +mp_obj_t mp_raise_TypeError_o(const char *msg); +mp_obj_t mp_raise_NotImplementedError_o(const char *msg); +mp_obj_t mp_raise_OSError_o(int errno_); + + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG +#undef mp_check_self +#define mp_check_self(pred) +#else +// A port may define to raise TypeError for example +#ifndef mp_check_self +#define mp_check_self(pred) assert(pred) +#endif +#endif + +// helper functions for native/viper code +int mp_native_type_from_qstr(qstr qst); +mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type); +mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type); + +#define mp_sys_path (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_path_obj))) +#define mp_sys_argv (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_argv_obj))) + +#if MICROPY_WARNINGS +#ifndef mp_warning +void mp_warning(const char *category, const char *msg, ...); +#endif +#else +#define mp_warning(...) +#endif + + +#define mp_raise_or_return(exc) { return mp_raise_o(exc); } +#define mp_raise_or_return_value(exc, retval) { mp_raise_o(exc); return retval; } +#define mp_raise_OSError(errno) { return mp_raise_OSError_o(errno); } +#define mp_raise_OSError_or_return(errno, retval) { mp_raise_OSError_o(errno); return retval; } +#define mp_raise_type(extype) { return mp_raise_o(mp_obj_new_exception(extype)); } +#define mp_raise_type_or_return(extype, retval) { mp_raise_o(mp_obj_new_exception(extype)); return retval; } +#define mp_raise_ValueError(v) { return mp_raise_ValueError_o(v); } +//#define mp_raise_msg(extype, mpt) { return mp_raise_o( mp_obj_new_exception_msg(extype, mpt)); } +#define mp_raise_msg(extype, mpt) { return mp_raise_msg_o(extype, mpt); } +#define mp_raise_TypeError(mpt) { return mp_raise_TypeError_o(mpt); } +#define mp_raise_TypeError_or_return(mpt, retval) { mp_raise_TypeError_o(mpt); return retval; } +#define mp_raise_NotImplementedError(mpt) { return mp_raise_NotImplementedError_o(mpt); } + +#else // NO_NLR + typedef enum { MP_VM_RETURN_NORMAL, MP_VM_RETURN_YIELD, @@ -194,4 +381,19 @@ void mp_warning(const char *category, const char *msg, ...); #define mp_warning(...) #endif +// NO_NLR compat +#define mp_raise_or_return(exc) nlr_raise(exc) +#define mp_raise_or_return_value(exc, retval) nlr_raise(exc) +// #define mp_raise_OSError(exc) +#define mp_raise_OSError_or_return(errno, retval) mp_raise_OSError(errno) +// #define mp_raise_type(extype) +#define mp_raise_type_or_return(extype, retval) mp_raise_type(extype) +// #define mp_raise_ValueError(v) +// #define mp_raise_msg(extype, mpt) +// #define mp_raise_TypeError(mpt) +#define mp_raise_TypeError_or_return(mpt, retval) +//#define mp_raise_NotImplementedError(mpt) + +#endif + #endif // MICROPY_INCLUDED_PY_RUNTIME_H diff --git a/py/runtime_utils.c b/py/runtime_utils.c index b92c6bd76..e877c6d5d 100644 --- a/py/runtime_utils.c +++ b/py/runtime_utils.c @@ -27,6 +27,25 @@ #include "py/runtime.h" +#if NO_NLR +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg) { + mp_obj_t ret = mp_call_function_1(fun, arg); + if (ret == MP_OBJ_NULL) { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception))); + MP_STATE_THREAD(active_exception) = NULL; + } + return ret; +} + +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { + mp_obj_t ret = mp_call_function_2(fun, arg1, arg2); + if (ret == MP_OBJ_NULL) { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception))); + MP_STATE_THREAD(active_exception) = NULL; + } + return ret; +} +#else mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -50,3 +69,4 @@ mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2 return MP_OBJ_NULL; } } +#endif // NO_NLR diff --git a/py/scheduler.c b/py/scheduler.c index 6b138a631..421c077b9 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -144,11 +144,15 @@ bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj // A variant of this is inlined in the VM at the pending exception check void mp_handle_pending(bool raise_exc) { if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { +#if NO_NLR +#pragma message "mp_handle_pending what, where and how ?" +#else mp_obj_t obj = MP_STATE_VM(mp_pending_exception); MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; if (raise_exc) { nlr_raise(obj); } +#endif } } diff --git a/py/scope.c b/py/scope.c index 073c2a38c..6ea012271 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. See assertions below. +// These low numbered qstrs should fit in 8 bits STATIC const uint8_t scope_simple_name_table[] = { [SCOPE_MODULE] = MP_QSTR__lt_module_gt_, [SCOPE_LAMBDA] = MP_QSTR__lt_lambda_gt_, @@ -50,6 +50,11 @@ scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_u MP_STATIC_ASSERT(MP_QSTR__lt_genexpr_gt_ <= UINT8_MAX); scope_t *scope = m_new0(scope_t, 1); +#if NO_NLR + if (scope == NULL) { + return NULL; + } +#endif scope->kind = kind; scope->pn = pn; scope->source_file = source_file; @@ -64,6 +69,11 @@ scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_u scope->id_info_alloc = MICROPY_ALLOC_SCOPE_ID_INIT; scope->id_info = m_new(id_info_t, scope->id_info_alloc); +#if NO_NLR + if (scope->raw_code == NULL || scope->id_info == NULL) { + return NULL; + } +#endif return scope; } @@ -81,6 +91,11 @@ id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, scope_kind_t kind) { // make sure we have enough memory if (scope->id_info_len >= scope->id_info_alloc) { scope->id_info = m_renew(id_info_t, scope->id_info, scope->id_info_alloc, scope->id_info_alloc + MICROPY_ALLOC_SCOPE_ID_INC); +#if NO_NLR + if (scope->id_info == NULL) { + return NULL; + } +#endif scope->id_info_alloc += MICROPY_ALLOC_SCOPE_ID_INC; } @@ -97,6 +112,11 @@ id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, scope_kind_t kind) { } id_info_t *scope_find(scope_t *scope, qstr qst) { +#if NO_NLR + if (scope->id_info == NULL) { + return NULL; + } +#endif for (mp_uint_t i = 0; i < scope->id_info_len; i++) { if (scope->id_info[i].qst == qst) { return &scope->id_info[i]; diff --git a/py/scope.h b/py/scope.h index ba07c3949..b52d98ea1 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 fa660a338..1f6c1517b 100644 --- a/py/sequence.c +++ b/py/sequence.c @@ -44,10 +44,18 @@ void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times } #if MICROPY_PY_BUILTINS_SLICE - +#if NO_NLR +int mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) { +#else bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) { - mp_obj_slice_indices(slice, len, indexes); +#endif + mp_obj_slice_indices(slice, len, indexes); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return -1; + } +#endif // If the index is negative then stop points to the last item, not after it if (indexes->step < 0) { indexes->stop++; diff --git a/py/stackctrl.c b/py/stackctrl.c index c2f3adb5e..b2b249fbd 100644 --- a/py/stackctrl.c +++ b/py/stackctrl.c @@ -48,10 +48,20 @@ void mp_stack_set_limit(mp_uint_t limit) { MP_STATE_THREAD(stack_limit) = limit; } +#if NO_NLR +int mp_stack_check(void) { + if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { + mp_raise_recursion_depth(); + + return 1; + } + return 0; +} +#else void mp_stack_check(void) { if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { mp_raise_recursion_depth(); } } - +#endif #endif // MICROPY_STACK_CHECK diff --git a/py/stackctrl.h b/py/stackctrl.h index ff8da0ab1..6234a2361 100644 --- a/py/stackctrl.h +++ b/py/stackctrl.h @@ -35,14 +35,23 @@ mp_uint_t mp_stack_usage(void); #if MICROPY_STACK_CHECK void mp_stack_set_limit(mp_uint_t limit); +#if NO_NLR +int mp_stack_check(void); +#else void mp_stack_check(void); +#endif #define MP_STACK_CHECK() mp_stack_check() #else #define mp_stack_set_limit(limit) +#if NO_NLR +static inline int MP_STACK_CHECK(void) { + return 0; +} +#else #define MP_STACK_CHECK() - +#endif // NO_NLR #endif #endif // MICROPY_INCLUDED_PY_STACKCTRL_H diff --git a/py/stream.c b/py/stream.c index dce64a44d..563178edc 100644 --- a/py/stream.c +++ b/py/stream.c @@ -92,7 +92,12 @@ 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 +#if NO_NLR + mp_raise_msg_o(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); + return NULL; +#else mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); +#endif } return stream_p; } @@ -312,6 +317,11 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) { int error; mp_uint_t out_sz = stream_p->read(self_in, p, current_read, &error); if (out_sz == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (mp_is_nonblocking_error(error)) { // With non-blocking streams, we read as much as we can. // If we read nothing, return None, just like read(). @@ -361,6 +371,11 @@ STATIC mp_obj_t stream_unbuffered_readline(size_t n_args, const mp_obj_t *args) int error; mp_uint_t out_sz = stream_p->read(args[0], p, 1, &error); if (out_sz == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (mp_is_nonblocking_error(error)) { if (vstr.len == 1) { // We just incremented it, but otherwise we read nothing @@ -421,6 +436,11 @@ mp_obj_t mp_stream_close(mp_obj_t stream) { int error; mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } return mp_const_none; @@ -445,6 +465,12 @@ STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) { int error; mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR +#pragma message "TODO: Could be uint64" + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } @@ -466,6 +492,11 @@ STATIC mp_obj_t stream_flush(mp_obj_t self) { int error; mp_uint_t res = stream_p->ioctl(self, MP_STREAM_FLUSH, 0, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } return mp_const_none; @@ -487,6 +518,11 @@ STATIC mp_obj_t stream_ioctl(size_t n_args, const mp_obj_t *args) { int error; mp_uint_t res = stream_p->ioctl(args[0], mp_obj_get_int(args[1]), val, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } diff --git a/py/vm.c b/py/vm.c index f30aed8ff..ccbb9bba1 100644 --- a/py/vm.c +++ b/py/vm.c @@ -37,6 +37,9 @@ #include "py/profile.h" // *FORMAT-OFF* +#if WAPY +#include "../wapy/upython.h" +#endif #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); @@ -229,7 +232,20 @@ mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp // sees that it's possible for us to jump from the dispatch loop to the exception // handler. Without this, the code may have a different stack layout in the dispatch // loop and the exception handler, leading to very obscure bugs. +#if NO_NLR + #define RAISE(o) do { MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o); goto exception_handler; } while (0) + //#define RAISE_IT() do { goto exception_handler; } while (0) + #define RAISE_IF(arg) if (arg) { goto exception_handler; } + #define RAISE_IF_NULL(arg) RAISE_IF(arg == MP_OBJ_NULL) + #define THE_EXC the_exc + #define TEST_TOP() (*sp) +#else #define RAISE(o) do { nlr_pop(); nlr.ret_val = MP_OBJ_TO_PTR(o); goto exception_handler; } while (0) + #define RAISE_IF(condition) { condition; } + #define THE_EXC nlr.ret_val + #define RAISE_IF_NULL(arg) {arg;} + #define TEST_TOP() +#endif #if MICROPY_STACKLESS run_code_state: ; @@ -259,11 +275,47 @@ FRAME_SETUP(); volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; #endif +#if WAPY +if (VMFLAGS_IF>0) { + static size_t source_line=0; + const byte *ip = code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ip); + MP_BC_PRELUDE_SIZE_DECODE(ip); + const byte *bytecode_start = ip + n_info + n_cell; + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + size_t bc = code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr block_name = ip[0] | (ip[1] << 8); + qstr source_file = ip[2] | (ip[3] << 8); + ip += 4; + #else + qstr block_name = mp_decode_uint_value(ip); + ip = mp_decode_uint_skip(ip); + qstr source_file = mp_decode_uint_value(ip); + ip = mp_decode_uint_skip(ip); + #endif + size_t the_line = mp_bytecode_get_source_line(ip, bc); + if (the_line != source_line) { + source_line = the_line; + cdbg("247:vm.c NOINT %s:%lu but VMFLAGS_IF set", qstr_str(source_file), source_line); + } +} +#else + #pragma message "no wapy vm check" +#endif + // outer exception handling loop for (;;) { +#if NO_NLR + { +#else nlr_buf_t nlr; outer_dispatch_loop: if (nlr_push(&nlr) == 0) { +#endif // local variables that are not visible to the exception handler const byte *ip = code_state->ip; mp_obj_t *sp = code_state->sp; @@ -338,12 +390,12 @@ FRAME_SETUP(); 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, MP_ERROR_TEXT("local variable referenced before assignment")); - RAISE(obj); + RAISE(mp_obj_new_exception_msg(&mp_type_NameError, "local variable referenced before assignment")); } } PUSH(obj_shared); @@ -361,6 +413,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -373,6 +426,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_name(qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } PUSH(obj); ip++; @@ -385,6 +441,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_global(qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -397,6 +454,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_global(qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } PUSH(obj); ip++; @@ -410,6 +470,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; SET_TOP(mp_load_attr(TOP(), qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -428,6 +489,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_attr(top, qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } SET_TOP(obj); ip++; @@ -438,7 +502,7 @@ FRAME_SETUP(); ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_load_method(*sp, qst, sp); + RAISE_IF_NULL(mp_load_method(*sp, qst, sp)); sp += 1; DISPATCH(); } @@ -447,7 +511,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; sp -= 1; - mp_load_super_method(qst, sp - 1); + RAISE_IF_NULL(mp_load_super_method(qst, sp - 1)); DISPATCH(); } @@ -460,6 +524,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); mp_obj_t index = POP(); SET_TOP(mp_obj_subscr(TOP(), index, MP_OBJ_SENTINEL)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -494,7 +559,7 @@ FRAME_SETUP(); FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_store_attr(sp[0], qst, sp[-1]); + RAISE_IF_NULL(mp_store_attr(sp[0], qst, sp[-1])); sp -= 2; DISPATCH(); } @@ -517,7 +582,7 @@ FRAME_SETUP(); if (elem != NULL) { elem->value = sp[-1]; } else { - mp_store_attr(sp[0], qst, sp[-1]); + RAISE_IF_NULL(mp_store_attr(sp[0], qst, sp[-1])); } sp -= 2; ip++; @@ -527,7 +592,7 @@ FRAME_SETUP(); ENTRY(MP_BC_STORE_SUBSCR): MARK_EXC_IP_SELECTIVE(); - mp_obj_subscr(sp[-1], sp[0], sp[-2]); + RAISE_IF_NULL(mp_obj_subscr(sp[-1], sp[0], sp[-2])); sp -= 3; DISPATCH(); @@ -554,14 +619,14 @@ FRAME_SETUP(); ENTRY(MP_BC_DELETE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_delete_name(qst); + RAISE_IF_NULL(mp_delete_name(qst)); DISPATCH(); } ENTRY(MP_BC_DELETE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_delete_global(qst); + RAISE_IF_NULL(mp_delete_global(qst)); DISPATCH(); } @@ -642,9 +707,12 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); // stack: (..., ctx_mgr) mp_obj_t obj = TOP(); - mp_load_method(obj, MP_QSTR___exit__, sp); - mp_load_method(obj, MP_QSTR___enter__, sp + 2); + RAISE_IF_NULL(mp_load_method(obj, MP_QSTR___exit__, sp)); + RAISE_IF_NULL(mp_load_method(obj, MP_QSTR___enter__, sp + 2)); mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 2); +#if NO_NLR + RAISE_IF_NULL(ret); +#endif sp += 1; PUSH_EXC_BLOCK(1); PUSH(ret); @@ -665,7 +733,7 @@ FRAME_SETUP(); sp[1] = mp_const_none; sp[2] = mp_const_none; sp -= 2; - mp_call_method_n_kw(3, 0, sp); + RAISE_IF_NULL(mp_call_method_n_kw(3, 0, sp)); SET_TOP(mp_const_none); } else if (mp_obj_is_small_int(TOP())) { // Getting here there are two distinct cases: @@ -790,6 +858,7 @@ unwind_jump:; ENTRY(MP_BC_GET_ITER): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_getiter(TOP(), NULL)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); // An iterator for a for-loop takes MP_OBJ_ITER_BUF_NSLOTS slots on @@ -802,6 +871,9 @@ unwind_jump:; 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 NO_NLR + RAISE_IF_NULL(obj); +#endif if (obj != MP_OBJ_FROM_PTR(iter_buf)) { // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; @@ -825,6 +897,17 @@ unwind_jump:; if (value == MP_OBJ_STOP_ITERATION) { sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator ip += ulab; // jump to after for-block +#if NO_NLR + } else if (value == MP_OBJ_NULL) { + // raised an exception + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + MP_STATE_THREAD(active_exception) = NULL; + sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator + ip += ulab; // jump to after for-block + } else { + RAISE_IF(true); + } +#endif } else { PUSH(value); // push the next iteration value #if MICROPY_PY_SYS_SETTRACE @@ -850,6 +933,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_tuple(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -858,6 +942,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_list(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -865,13 +950,14 @@ unwind_jump:; MARK_EXC_IP_SELECTIVE(); DECODE_UINT; PUSH(mp_obj_new_dict(unum)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } ENTRY(MP_BC_STORE_MAP): MARK_EXC_IP_SELECTIVE(); sp -= 2; - mp_obj_dict_store(sp[0], sp[2], sp[1]); + RAISE_IF_NULL(mp_obj_dict_store(sp[0], sp[2], sp[1])); DISPATCH(); #if MICROPY_PY_BUILTINS_SET @@ -880,6 +966,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_set(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #endif @@ -895,6 +982,7 @@ unwind_jump:; mp_obj_t stop = POP(); mp_obj_t start = TOP(); SET_TOP(mp_obj_new_slice(start, stop, step)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #endif @@ -921,7 +1009,7 @@ unwind_jump:; ENTRY(MP_BC_UNPACK_SEQUENCE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; - mp_unpack_sequence(sp[0], unum, sp); + RAISE_IF_NULL(mp_unpack_sequence(sp[0], unum, sp)); sp += unum - 1; DISPATCH(); } @@ -929,7 +1017,7 @@ unwind_jump:; ENTRY(MP_BC_UNPACK_EX): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; - mp_unpack_ex(sp[0], unum, sp); + RAISE_IF_NULL(mp_unpack_ex(sp[0], unum, sp)); sp += (unum & 0xff) + ((unum >> 8) & 0xff); DISPATCH(); } @@ -986,18 +1074,23 @@ unwind_jump:; #if MICROPY_STACKLESS_STRICT deep_recursion_error: mp_raise_recursion_depth(); + RAISE_IF(true); #endif } else #endif { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1017,8 +1110,7 @@ unwind_jump:; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; - mp_call_prepare_args_n_kw_var(false, unum, sp, &out_args); - + RAISE_IF(mp_call_prepare_args_n_kw_var(false, unum, sp, &out_args)); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); #if !MICROPY_ENABLE_PYSTACK @@ -1038,12 +1130,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(false, unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1077,12 +1173,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1102,7 +1202,8 @@ unwind_jump:; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; - mp_call_prepare_args_n_kw_var(true, unum, sp, &out_args); +#warning no NULL test ? + RAISE_IF(mp_call_prepare_args_n_kw_var(true, unum, sp, &out_args)); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); @@ -1123,12 +1224,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(true, unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1166,7 +1271,10 @@ unwind_jump:; } POP_EXC_BLOCK(); } +#if NO_NLR +#else nlr_pop(); +#endif code_state->sp = sp; assert(exc_sp == exc_stack - 1); MICROPY_VM_HOOK_RETURN @@ -1201,7 +1309,7 @@ unwind_jump:; } } if (obj == MP_OBJ_NULL) { - obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("no active exception to reraise")); + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); } RAISE(obj); } @@ -1222,7 +1330,6 @@ unwind_jump:; 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); @@ -1238,7 +1345,10 @@ unwind_jump:; mp_obj_t send_value = POP(); mp_obj_t t_exc = MP_OBJ_NULL; mp_obj_t ret_value; +#if NO_NLR +#else code_state->sp = sp; // Save sp because it's needed if mp_resume raises StopIteration +#endif if (inject_exc != MP_OBJ_NULL) { t_exc = inject_exc; inject_exc = MP_OBJ_NULL; @@ -1268,9 +1378,20 @@ unwind_jump:; DISPATCH(); } else { assert(ret_kind == MP_VM_RETURN_EXCEPTION); +#if NO_NLR + // Pop exhausted gen + sp--; + if (EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + // StopIteration inside yield from call means return a value of + // yield from, so inject exception's value as yield from's result + PUSH(mp_obj_exception_get_value(MP_OBJ_FROM_PTR(ret_value))); + DISPATCH(); + } +#else assert(!EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))); // Pop exhausted gen sp--; +#endif RAISE(ret_value); } } @@ -1281,6 +1402,7 @@ unwind_jump:; DECODE_QSTR; mp_obj_t obj = POP(); SET_TOP(mp_import_name(qst, obj, TOP())); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1289,6 +1411,9 @@ unwind_jump:; MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t obj = mp_import_from(TOP(), qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif PUSH(obj); DISPATCH(); } @@ -1314,6 +1439,7 @@ unwind_jump:; ENTRY(MP_BC_UNARY_OP_MULTI): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); ENTRY(MP_BC_BINARY_OP_MULTI): { @@ -1321,6 +1447,7 @@ unwind_jump:; mp_obj_t rhs = POP(); mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1331,26 +1458,34 @@ unwind_jump:; 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())); + RAISE_IF_NULL(TEST_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 rhs = POP(); // XXX may now be unreachable mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); + } else #endif { - - mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, MP_ERROR_TEXT("opcode")); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, "opcode"); +#if NO_NLR +#else nlr_pop(); +#endif code_state->state[0] = obj; FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; @@ -1366,23 +1501,18 @@ unwind_jump:; #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(); - // 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_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; } - 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 @@ -1415,19 +1545,35 @@ unwind_jump:; } // for loop +#if !NO_NLR } else { exception_handler: // exception occurred +#else + } + { +exception_handler: + // exception occurred + assert(MP_STATE_THREAD(active_exception) != NULL); - #if MICROPY_PY_SYS_EXC_INFO + // clear exception because we caught it + mp_obj_base_t *the_exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; +#endif + #if MICROPY_PY_SYS_EXC_INFO +#if !NO_NLR MP_STATE_VM(cur_exception) = nlr.ret_val; - #endif +#else + MP_STATE_VM(cur_exception) = the_exc; +#endif + #endif #if SELECTIVE_EXC_IP // with selective ip, we store the ip 1 byte past the opcode, so move ptr back code_state->ip -= 1; #endif +#if !NO_NLR 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 @@ -1454,6 +1600,15 @@ unwind_jump:; TRACE_TICK(code_state->ip, code_state->sp, true /* yes, it's an exception */); } #endif +#else + #if MICROPY_PY_SYS_SETTRACE + // Exceptions are traced here + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)the_exc)->type), MP_OBJ_FROM_PTR(&mp_type_Exception))) { + TRACE_TICK(code_state->ip, code_state->sp, true /* yes, it's an exception */); + } + #endif +#endif + #if MICROPY_STACKLESS unwind_loop: @@ -1462,7 +1617,11 @@ unwind_jump:; // - constant GeneratorExit object, because it's const // - exceptions re-raised by END_FINALLY // - exceptions re-raised explicitly by "raise" - if (nlr.ret_val != &mp_const_GeneratorExit_obj +#if !NO_NLR + if (THE_EXC != &mp_const_GeneratorExit_obj +#else + if (THE_EXC != &mp_const_GeneratorExit_obj.base +#endif && *code_state->ip != MP_BC_END_FINALLY && *code_state->ip != MP_BC_RAISE_LAST) { const byte *ip = code_state->fun_bc->bytecode; @@ -1485,7 +1644,7 @@ unwind_jump:; ip = mp_decode_uint_skip(ip); #endif size_t source_line = mp_bytecode_get_source_line(ip, bc); - mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(nlr.ret_val), source_file, source_line, block_name); + mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(THE_EXC), source_file, source_line, block_name); } while (exc_sp >= exc_stack && exc_sp->handler <= code_state->ip) { @@ -1506,9 +1665,9 @@ unwind_jump:; code_state->ip = exc_sp->handler; mp_obj_t *sp = MP_TAGPTR_PTR(exc_sp->val_sp); // save this exception in the stack so it can be used in a reraise, if needed - exc_sp->prev_exc = nlr.ret_val; + exc_sp->prev_exc = THE_EXC; // push exception object so it can be handled by bytecode - PUSH(MP_OBJ_FROM_PTR(nlr.ret_val)); + PUSH(MP_OBJ_FROM_PTR(THE_EXC)); code_state->sp = sp; #if MICROPY_STACKLESS @@ -1534,7 +1693,7 @@ unwind_jump:; } else { // propagate exception to higher level // Note: ip and sp don't have usable values at this point - code_state->state[0] = MP_OBJ_FROM_PTR(nlr.ret_val); // put exception here because sp is invalid + code_state->state[0] = MP_OBJ_FROM_PTR(THE_EXC); // put exception here because sp is invalid FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; } diff --git a/py/vstr.c b/py/vstr.c index 56327b5f7..3084ef9ee 100644 --- a/py/vstr.c +++ b/py/vstr.c @@ -61,13 +61,21 @@ void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf) { vstr->buf = buf; vstr->fixed_buf = true; } +#if NO_NLR +void void_add_strn(vstr_t *vstr, const char *str, size_t len); +void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) { + vstr_init(vstr, alloc); + print->data = vstr; + print->print_strn = (mp_print_strn_t)void_add_strn; +} +#else void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) { vstr_init(vstr, alloc); print->data = vstr; print->print_strn = (mp_print_strn_t)vstr_add_strn; } - +#endif void vstr_clear(vstr_t *vstr) { if (!vstr->fixed_buf) { m_del(char, vstr->buf, vstr->alloc); @@ -95,7 +103,12 @@ char *vstr_extend(vstr_t *vstr, size_t size) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, NULL); + return NULL; +#else mp_raise_msg(&mp_type_RuntimeError, NULL); +#endif } char *new_buf = m_renew(char, vstr->buf, vstr->alloc, vstr->alloc + size); char *p = new_buf + vstr->alloc; @@ -104,18 +117,34 @@ char *vstr_extend(vstr_t *vstr, size_t size) { return p; } +#if NO_NLR +STATIC int vstr_ensure_extra(vstr_t *vstr, size_t size) { + if (vstr->buf == NULL) { + // could not allocate a buffer + return -1; + } +#else STATIC void vstr_ensure_extra(vstr_t *vstr, size_t size) { +#endif if (vstr->len + size > vstr->alloc) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, NULL); + return -1; +#else mp_raise_msg(&mp_type_RuntimeError, NULL); +#endif } size_t new_alloc = ROUND_ALLOC((vstr->len + size) + 16); char *new_buf = m_renew(char, vstr->buf, vstr->alloc, new_alloc); vstr->alloc = new_alloc; vstr->buf = new_buf; } +#if NO_NLR + return 0; +#endif } void vstr_hint_size(vstr_t *vstr, size_t size) { @@ -133,7 +162,13 @@ char *vstr_add_len(vstr_t *vstr, size_t len) { char *vstr_null_terminated_str(vstr_t *vstr) { // If there's no more room, add single byte if (vstr->alloc == vstr->len) { +#if NO_NLR + if (vstr_extend(vstr, 1) == NULL) { + return NULL; + } +#else vstr_extend(vstr, 1); +#endif } vstr->buf[vstr->len] = '\0'; return vstr->buf; @@ -172,7 +207,34 @@ void vstr_add_char(vstr_t *vstr, unichar c) { vstr_add_byte(vstr, c); #endif } +#if NO_NLR + +#pragma message "NEED FIX TO CLANG WASM PRINT FUNCTION PTR" + +int vstr_add_str(vstr_t *vstr, const char *str) { + return vstr_add_strn(vstr, str, strlen(str)); +} + +int vstr_add_strn(vstr_t *vstr, const char *str, size_t len) { + if (vstr_ensure_extra(vstr, len)) { + return -1; + } + memmove(vstr->buf + vstr->len, str, len); + vstr->len += len; + return 0; // success +} + +void void_add_strn(vstr_t *vstr, const char *str, size_t len) { + if (vstr_ensure_extra(vstr, len)) { + //return -1; + } + memmove(vstr->buf + vstr->len, str, len); + vstr->len += len; +// return 0; // success +} + +#else void vstr_add_str(vstr_t *vstr, const char *str) { vstr_add_strn(vstr, str, strlen(str)); } @@ -182,6 +244,7 @@ void vstr_add_strn(vstr_t *vstr, const char *str, size_t len) { memmove(vstr->buf + vstr->len, str, len); vstr->len += len; } +#endif STATIC char *vstr_ins_blank_bytes(vstr_t *vstr, size_t byte_pos, size_t byte_len) { size_t l = vstr->len; @@ -233,6 +296,15 @@ void vstr_cut_out_bytes(vstr_t *vstr, size_t byte_pos, size_t bytes_to_cut) { } } +void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) { +#if NO_NLR + mp_print_t print = {vstr, (mp_print_strn_t)void_add_strn}; +#else + mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn}; +#endif + mp_vprintf(&print, fmt, ap); +} + void vstr_printf(vstr_t *vstr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -240,7 +312,3 @@ void vstr_printf(vstr_t *vstr, const char *fmt, ...) { va_end(ap); } -void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) { - mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn}; - mp_vprintf(&print, fmt, ap); -} diff --git a/rebuild-android.sh b/rebuild-android.sh new file mode 100755 index 000000000..a85388bdf --- /dev/null +++ b/rebuild-android.sh @@ -0,0 +1,72 @@ +#!/bin/bash +reset + +CC=${CC:-clang} + +export PORT=$(pwd)/build-no_nlr/ports/wapy-wasm + +if echo "$@" |grep -q clean +then + make -C $PORT clean && rm -rf $PORT/build + exit 0 +fi + +if echo $0|grep -q ash +then + echo not to be sourced, use chmod +x on script +else + if $CC -### 2>&1 | grep -q android + then + export PYTHONDONTWRITEBYTECODE=1 + for py in 8 7 6 + do + if which python3.${py} + then + export PYTHON=python3.${py} + break + fi + done + echo Will use python $PYTHON + echo + + export ROOT=$(dirname $(realpath "$0") ) + + if cd $ROOT + then + + echo + echo Building ... + + export USER_C_MODULES=${ROOT}/cmod/wasm + if PYTHONPATH=./wapy-lib $PYTHON -u -B -m modgen + then + echo ok + else + echo fail + exit 1 + fi + +#exit 1 + + # future-fstrings-show now used to serve file via http + #$PYTHON -u -B -m fstrings_helper micropython/link.cpy > micropython/ulink.py + + #export EMCC_FORCE_STDLIBS=libc,libc++abi,libc++,libdlmalloc + export MOD="-DMODULE_EMBED_ENABLED=1 -DMODULE_EXAMPLE_ENABLED=1 -DMODULE_ZIPFILE_ENABLED=1" + # -DMODULE_LVGL_ENABLED=1" + + if make NDK=/data/cross/pydk/android-sdk/ndk-bundle NDK_CC=${CC} NDK_LD=${LD} WAPY=1 NO_NLR=1 CWARN="-Wall" \ + USER_C_MODULES=${USER_C_MODULES} \ + CFLAGS_EXTRA="${MOD}" \ + FROZEN_MPY_DIR=${ROOT}/rom \ + FROZEN_DIR=flash \ + $@ -C $PORT + then + cp -vf $PORT/libwapy.so /data/cross/pydk-applications/pydk/prebuilt/armeabi-v7a/wapy/ + fi + fi + else + echo "shell.xxx.sh android toolchain env not sourced !" + fi +fi + diff --git a/rebuild.sh b/rebuild.sh new file mode 100755 index 000000000..b9f86d1ee --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,133 @@ +#!/bin/bash +reset +# BLOBS="--preload-file assets@/assets --preload-file micropython/lib@/lib" + +rm -vf demos/wapy.wasm.map demos/wapy.wasm demos/wapy.js demos/wapy.data + +if echo $0|grep -q bash +then + echo not to be sourced, use chmod +x on script +else + if command -v emcc + then + export PYTHONDONTWRITEBYTECODE=1 + for py in 8 7 6 + do + if which python3.${py} + then + export PYTHON=python3.${py} + break + fi + done + echo Will use python $PYTHON + echo + + export ROOT=$(dirname $(realpath "$0") ) + + if cd $ROOT + then + if echo "$@"|grep -q fast + then + rm -vf ./$1/lib*.* ./$1/build/main.o + else + echo "Cleaning up ( use 'fast' arg to to partial cleanup only )" + emmake make clean + fi + + echo + echo Building ... + + export USER_C_MODULES=${ROOT}/cmod/wasm + + if PYTHONPATH=./wapy-lib $PYTHON -u -B -m modgen + then + echo ok + else + exit 1 + fi + + # future-fstrings-show now used to serve file via http + #$PYTHON -u -B -m fstrings_helper micropython/link.cpy > micropython/ulink.py + + #export EMCC_FORCE_STDLIBS=libc,libc++abi,libc++,libdlmalloc + export MOD="-DMODULE_EMBED_ENABLED=1 -DMODULE_EXAMPLE_ENABLED=1 -DMODULE_ZIPFILE_ENABLED=1" + + if [ -f pic-deps ] + then + echo pic-deps ok + else + # fake strip + cp -vf /bin/true $EMSDK/upstream/emscripten/emstrip + + # on first time they are not built + embuilder --pic build sdl2 vorbis + touch pic-deps + fi + +export PORT=$(pwd)/build-no_nlr/ports/wapy-wasm + +rm demos/libwapy.so ${PORT}/libwapy.* $PORT/libpanda3d.* + +export LP3D=/data/cross/pydk/wasm/build-wasm/panda3dffi-wasm/lib +export DST=/data/cross/wapy/build-no_nlr/ports/wapy-wasm +export TMP=/data/cross/wapy/artmp + +if false +then + + ar --output $TMP x ${LP3D}/libp3dtool.a + ar --output $TMP x ${LP3D}/libp3dtoolconfig.a + ar --output $TMP x ${LP3D}/libpandagles2.a + ar --output $TMP x ${LP3D}/libp3openal_audio.a + ar --output $TMP x ${LP3D}/libp3interrogatedb.a + ar --output $TMP x ${LP3D}/libp3direct.a + ar --output $TMP x ${LP3D}/libpandabullet.a + ar --output $TMP x ${LP3D}/libpandaexpress.a + ar --output $TMP x ${LP3D}/libpanda.a + ar --output $TMP x ${LP3D}/libp3framework.a + + emar rcs $DST/libpanda3d.a $TMP/*.o + file $DST/libpanda3d.a +fi + + + + if emmake make WAPY=1 NO_NLR=1 CWARN="-Wall" \ + JSFLAGS="-s WARN_ON_UNDEFINED_SYMBOLS=1" \ + USER_C_MODULES=${USER_C_MODULES} \ + CFLAGS_EXTRA="${MOD}" \ + FROZEN_MPY_DIR=${ROOT}/rom \ + FROZEN_DIR=flash \ + $@ -C $PORT + then + mv -vf ${PORT}/wapy.* ${ROOT}/demos/ + #this is default emscripten's one + rm ${ROOT}/demos/wapy.html + +# maintenance of lib folder +# --lz4 +$PYTHON -u -B wapy/ports/wapy-wasm/file_packager.py wapy-lib.data --use-preload-plugins --no-heap-copy --separate-metadata\ + --js-output=wapy-lib.js \ + --preload ${ROOT}/demos/samples/libtest.wasm@/lib/libtest.so \ + --preload ${ROOT}/demos/assets/resources/hello.bmp@/hello.bmp \ + --preload ${ROOT}/sources.py/pythons@/assets/pythons \ + --preload /data/git/wapy-lib/pythons/fixes@/assets/pythons/fixes \ + --preload ${ROOT}/sources.py/wapy_wasm/wapy_wasm_site.py@/assets/site.py \ +> packager.log +mv wapy-lib.* ${ROOT}/demos/ + + mv ${PORT}/libwapy.wasm ../demos/libwapy.so + mv ${PORT}/libwapy.wasm.map ../demos/ + + mv ${PORT}/libsdl2.wasm ../demos/libsdl2.so + mv ${PORT}/libsdl2.wasm.map ../demos/ + cd ${ROOT}/demos + PYTHONPATH=. $PYTHON -u -B -m pythons.js ${WEB:-"8000"} + + fi + fi + else + echo "emsdk_env.sh not sourced !" + fi +fi + diff --git a/tests/basics/annotate_var.py b/tests/basics/annotate_var.py new file mode 100644 index 000000000..3f767e4a7 --- /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 000000000..9b6536e96 --- /dev/null +++ b/tests/basics/annotate_var.py.exp @@ -0,0 +1,5 @@ +False +1 +(1, 2) +NameError +1 diff --git a/tests/basics/assign_expr.py b/tests/basics/assign_expr.py new file mode 100644 index 000000000..f243905dc --- /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 000000000..e38e1ae7a --- /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 000000000..11b350129 --- /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 000000000..2ba7d7df8 --- /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/async_for.py b/tests/basics/async_for.py index 6b4e136d5..5fd054082 100644 --- a/tests/basics/async_for.py +++ b/tests/basics/async_for.py @@ -6,7 +6,7 @@ def __init__(self, obj): print('init') self._it = iter(obj) - async def __aiter__(self): + def __aiter__(self): print('aiter') return self diff --git a/tests/basics/async_for2.py b/tests/basics/async_for2.py index 89584fcb1..aad23a3e5 100644 --- a/tests/basics/async_for2.py +++ b/tests/basics/async_for2.py @@ -1,4 +1,4 @@ -# test waiting within "async for" aiter/anext functions +# test waiting within "async for" __anext__ function import sys if sys.implementation.name == 'micropython': @@ -21,9 +21,8 @@ def __init__(self, high): self.cur = 0 self.high = high - async def __aiter__(self): + def __aiter__(self): print('aiter') - print('f returned:', await f(10)) return self async def __anext__(self): diff --git a/tests/basics/async_for2.py.exp b/tests/basics/async_for2.py.exp index 886232f7b..52bbe90c8 100644 --- a/tests/basics/async_for2.py.exp +++ b/tests/basics/async_for2.py.exp @@ -1,9 +1,5 @@ init aiter -f start: 10 -coro yielded: 11 -coro yielded: 12 -f returned: 13 anext f start: 20 coro yielded: 21 diff --git a/tests/basics/class_bind_self.py b/tests/basics/class_bind_self.py index 16e4f5ac9..813f87644 100644 --- a/tests/basics/class_bind_self.py +++ b/tests/basics/class_bind_self.py @@ -40,11 +40,18 @@ def f4(self, arg): # generator 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_dict.py b/tests/basics/class_dict.py new file mode 100644 index 000000000..f80ded678 --- /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_inplace_op2.py b/tests/basics/class_inplace_op2.py new file mode 100644 index 000000000..0e3f2add4 --- /dev/null +++ b/tests/basics/class_inplace_op2.py @@ -0,0 +1,73 @@ +# Test inplace special methods enabled by MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS + + +class A: + def __imul__(self, other): + print("__imul__") + return self + + def __imatmul__(self, other): + print("__imatmul__") + return self + + def __ifloordiv__(self, other): + print("__ifloordiv__") + return self + + def __itruediv__(self, other): + print("__itruediv__") + return self + + def __imod__(self, other): + print("__imod__") + return self + + def __ipow__(self, other): + print("__ipow__") + return self + + def __ior__(self, other): + print("__ior__") + return self + + def __ixor__(self, other): + print("__ixor__") + return self + + def __iand__(self, other): + print("__iand__") + return self + + def __ilshift__(self, other): + print("__ilshift__") + return self + + def __irshift__(self, other): + print("__irshift__") + return self + + +a = A() + +try: + a *= None +except TypeError: + print("SKIP") + raise SystemExit + +a @= None +a //= None +a /= None +a %= None +a **= None +a |= None +a ^= None +a &= None +a <<= None +a >>= None + +# Normal operator should not fallback to inplace operator +try: + a * None +except TypeError: + print("TypeError") diff --git a/tests/basics/class_inplace_op2.py.exp b/tests/basics/class_inplace_op2.py.exp new file mode 100644 index 000000000..8c323b517 --- /dev/null +++ b/tests/basics/class_inplace_op2.py.exp @@ -0,0 +1,12 @@ +__imul__ +__imatmul__ +__ifloordiv__ +__itruediv__ +__imod__ +__ipow__ +__ior__ +__ixor__ +__iand__ +__ilshift__ +__irshift__ +TypeError diff --git a/tests/basics/class_ordereddict.py b/tests/basics/class_ordereddict.py new file mode 100644 index 000000000..4dd25eb6e --- /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 000000000..b723e3275 --- /dev/null +++ b/tests/basics/class_ordereddict.py.exp @@ -0,0 +1 @@ +['a', 'b', 'c', 'd', 'e'] diff --git a/tests/basics/memoryview1.py b/tests/basics/memoryview1.py index 1bfeabdfd..4c20c91f4 100644 --- a/tests/basics/memoryview1.py +++ b/tests/basics/memoryview1.py @@ -54,54 +54,6 @@ 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) - -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') - # invalid attribute try: memoryview(b'a').noexist diff --git a/tests/basics/memoryview_slice_assign.py b/tests/basics/memoryview_slice_assign.py new file mode 100644 index 000000000..74f6fae6f --- /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/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index 18986318a..42a8228fb 100644 --- a/tests/cmdline/cmd_parsetree.py.exp +++ b/tests/cmdline/cmd_parsetree.py.exp @@ -3,7 +3,7 @@ tok(4) [ 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/cpydiff/syntax_assign_expr.py b/tests/cpydiff/syntax_assign_expr.py new file mode 100644 index 000000000..d4ed063b3 --- /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/extmod/uasyncio_micropython.py b/tests/extmod/uasyncio_micropython.py new file mode 100644 index 000000000..69e5fa322 --- /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 000000000..f5be1dc75 --- /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/ure1.py b/tests/extmod/ure1.py index 9e1be5fc7..2bdf2d0cf 100644 --- a/tests/extmod/ure1.py +++ b/tests/extmod/ure1.py @@ -125,3 +125,14 @@ 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 6bb332077..953e7bf62 100644 --- a/tests/extmod/ure_sub.py +++ b/tests/extmod/ure_sub.py @@ -60,3 +60,12 @@ def A(): 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/usocket_tcp_basic.py b/tests/extmod/usocket_tcp_basic.py new file mode 100644 index 000000000..368dfe3c9 --- /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/ussl_basic.py b/tests/extmod/ussl_basic.py index b4e21c7dc..9e1821dca 100644 --- a/tests/extmod/ussl_basic.py +++ b/tests/extmod/ussl_basic.py @@ -9,7 +9,7 @@ # 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 528233831..eb7df855a 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_fat_mtime.py b/tests/extmod/vfs_fat_mtime.py new file mode 100644 index 000000000..d8fd66b75 --- /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 000000000..55550b983 --- /dev/null +++ b/tests/extmod/vfs_fat_mtime.py.exp @@ -0,0 +1,3 @@ +test +True True +True diff --git a/tests/extmod/vfs_lfs_mtime.py b/tests/extmod/vfs_lfs_mtime.py new file mode 100644 index 000000000..bacdd2246 --- /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 000000000..cf6455c04 --- /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/float/cmath_fun.py b/tests/float/cmath_fun.py index 7b5e69245..15b72e7a6 100644 --- a/tests/float/cmath_fun.py +++ b/tests/float/cmath_fun.py @@ -57,3 +57,9 @@ 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/complex_reverse_op.py b/tests/float/complex_reverse_op.py new file mode 100644 index 000000000..a7351949d --- /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_mehods.py b/tests/float/complex_special_mehods.py new file mode 100644 index 000000000..6789013fa --- /dev/null +++ b/tests/float/complex_special_mehods.py @@ -0,0 +1,15 @@ +# test complex interacting with special methods + + +class A: + def __add__(self, x): + print("__add__") + return 1 + + def __radd__(self, x): + print("__radd__") + return 2 + + +print(A() + 1j) +print(1j + A()) diff --git a/tests/float/complex_special_methods.py b/tests/float/complex_special_methods.py new file mode 100644 index 000000000..7e54905e4 --- /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/inf_nan_arith.py b/tests/float/inf_nan_arith.py new file mode 100644 index 000000000..c27e38bc5 --- /dev/null +++ b/tests/float/inf_nan_arith.py @@ -0,0 +1,20 @@ +# Test behaviour of inf and nan in basic float operations + +inf = float("inf") +nan = float("nan") + +values = (-2, -1, 0, 1, 2, inf, nan) + +for x in values: + for y in values: + print(x, y) + print(" + - *", x + y, x - y, x * y) + try: + print(" /", x / y) + except ZeroDivisionError: + print(" / ZeroDivisionError") + try: + print(" ** pow", x ** y, pow(x, y)) + except ZeroDivisionError: + print(" ** pow ZeroDivisionError") + print(" == != < <= > >=", x == y, x != y, x < y, x <= y, x > y, x >= y) diff --git a/tests/micropython/import_mpy_invalid.py b/tests/micropython/import_mpy_invalid.py new file mode 100644 index 000000000..00973fe3f --- /dev/null +++ b/tests/micropython/import_mpy_invalid.py @@ -0,0 +1,69 @@ +# test importing of invalid .mpy files + +try: + import sys, uio, uos + + 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]) + + +# these are the test .mpy files +user_files = { + "/mod0.mpy": b"", # empty file + "/mod1.mpy": b"M", # too short header + "/mod2.mpy": b"M\x00\x00\x00", # bad version + "/mod3.mpy": b"M\x00\x00\x00\x7f", # qstr window too large +} + +# 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) + 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_invalid.py.exp b/tests/micropython/import_mpy_invalid.py.exp new file mode 100644 index 000000000..ea748ff6c --- /dev/null +++ b/tests/micropython/import_mpy_invalid.py.exp @@ -0,0 +1,4 @@ +mod0 ValueError incompatible .mpy file +mod1 ValueError incompatible .mpy file +mod2 ValueError incompatible .mpy file +mod3 ValueError incompatible .mpy file diff --git a/tests/micropython/import_mpy_native_gc.py b/tests/micropython/import_mpy_native_gc.py new file mode 100644 index 000000000..e8fac8f17 --- /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 000000000..3fbd4a869 --- /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 000000000..d0de507d4 --- /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 000000000..320cac09d --- /dev/null +++ b/tests/micropython/import_mpy_native_x64.py.exp @@ -0,0 +1,3 @@ +mod0 ValueError incompatible .mpy arch +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 000000000..e4270a10a --- /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 000000000..72f84b716 --- /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 000000000..3bc7ba8d1 --- /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 000000000..3ad6469a2 --- /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 000000000..85aa32c78 --- /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 000000000..cacce62b6 --- /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 790f3d75c..80617af0c 100644 --- a/tests/micropython/viper_error.py +++ b/tests/micropython/viper_error.py @@ -52,6 +52,7 @@ def f(): # 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 @@ def f(): 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 da9a0ca93..31c85b1d8 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/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index b5dfefc84..0d72c181f 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -5,21 +5,25 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") CHAR = ( CHAR_UUID, - bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) SERVICE = ( SERVICE_UUID, @@ -27,15 +31,13 @@ ) SERVICES = (SERVICE,) -last_event = None -last_data = None +waiting_event = None +waiting_data = None value_handle = 0 def irq(event, data): - global last_event, last_data, value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -53,21 +55,36 @@ def irq(event, data): value_handle = data[2] elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", data[-1]) - elif event == _IRQ_GATTC_WRITE_STATUS: - print("_IRQ_GATTC_WRITE_STATUS", data[-1]) + elif event == _IRQ_GATTC_READ_DONE: + print("_IRQ_GATTC_READ_DONE", data[-1]) + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: print("_IRQ_GATTC_NOTIFY", data[-1]) + elif event == _IRQ_GATTC_INDICATE: + print("_IRQ_GATTC_INDICATE", data[-1]) + elif event == _IRQ_GATTS_INDICATE_DONE: + print("_IRQ_GATTS_INDICATE_DONE", data[-1]) + + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -82,17 +99,18 @@ def instance0(): ble.gatts_write(char_handle, "periph0") # Wait for central to connect to us. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Wait for a write to the characteristic from the central. wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) # Wait a bit, then write the characteristic and notify it. time.sleep_ms(1000) + print("gatts_write") ble.gatts_write(char_handle, "periph1") + print("gatts_notify") ble.gatts_notify(conn_handle, char_handle) # Wait for a write to the characteristic from the central. @@ -100,8 +118,22 @@ def instance0(): # Wait a bit, then notify a new value on the characteristic. time.sleep_ms(1000) + print("gatts_notify") ble.gatts_notify(conn_handle, char_handle, "periph2") + # Wait for a write to the characteristic from the central. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + + # Wait a bit, then notify a new value on the characteristic. + time.sleep_ms(1000) + print("gatts_write") + ble.gatts_write(char_handle, "periph3") + print("gatts_indicate") + ble.gatts_indicate(conn_handle, char_handle) + + # Wait for the indicate ack. + wait_for_event(_IRQ_GATTS_INDICATE_DONE, TIMEOUT_MS) + # Wait for the central to disconnect. wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: @@ -115,14 +147,13 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) - wait_for_event(lambda: value_handle, TIMEOUT_MS) + wait_for_event(lambda event, data: value_handle, TIMEOUT_MS) # Issue read of characteristic, should get initial value. print("gattc_read") @@ -132,7 +163,7 @@ def instance1(): # Write to the characteristic, and ask for a response. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central0", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for a notify, then read new value. wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) @@ -143,7 +174,7 @@ def instance1(): # Write to the characteristic, and ask for a response. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central1", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for a notify (should have new data), then read old value (should be unchanged). wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) @@ -151,6 +182,17 @@ def instance1(): ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + # Write to the characteristic, and ask for a response. + print("gattc_write") + ble.gattc_write(conn_handle, value_handle, "central2", 1) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) + + # Wait for a indicate (should have new data), then read new value. + wait_for_event(_IRQ_GATTC_INDICATE, TIMEOUT_MS) + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index 1532574d9..25b5544ef 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -2,7 +2,14 @@ gap_advertise _IRQ_CENTRAL_CONNECT _IRQ_GATTS_WRITE b'central0' +gatts_write +gatts_notify _IRQ_GATTS_WRITE b'central1' +gatts_notify +_IRQ_GATTS_WRITE b'central2' +gatts_write +gatts_indicate +_IRQ_GATTS_INDICATE_DONE 0 _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect @@ -10,15 +17,24 @@ _IRQ_PERIPHERAL_CONNECT _IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000000-1111-2222-3333-444444444444') gattc_read _IRQ_GATTC_READ_RESULT b'periph0' +_IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph1' gattc_read _IRQ_GATTC_READ_RESULT b'periph1' +_IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph2' gattc_read _IRQ_GATTC_READ_RESULT b'central1' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +_IRQ_GATTC_INDICATE b'periph3' +gattc_read +_IRQ_GATTC_READ_RESULT b'periph3' +_IRQ_GATTC_READ_DONE 0 gap_disconnect: True _IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_gap_advertise.py b/tests/multi_bluetooth/ble_gap_advertise.py index 644b1fe28..71a5b58a5 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -3,8 +3,8 @@ from micropython import const import time, machine, bluetooth -_IRQ_SCAN_RESULT = const(1 << 4) -_IRQ_SCAN_COMPLETE = const(1 << 5) +_IRQ_SCAN_RESULT = const(5) +_IRQ_SCAN_DONE = const(6) ADV_TIME_S = 3 @@ -43,7 +43,7 @@ def irq(ev, data): else: if adv_data != data[4]: adv_data = b"MISMATCH" - elif ev == _IRQ_SCAN_COMPLETE: + elif ev == _IRQ_SCAN_DONE: finished = True ble.config(rxbuf=2000) diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index def256f2c..8e3ed8b81 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -5,19 +5,17 @@ TIMEOUT_MS = 4000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) -last_event = None -last_data = None +waiting_event = None +waiting_data = None def irq(event, data): - global last_event, last_data - last_event = event - last_data = data + global waiting_event, waiting_data if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -27,11 +25,23 @@ def irq(event, data): elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") + if waiting_event is not None: + if event == waiting_event: + waiting_event = None + waiting_data = data + def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() - while last_event != event and time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -42,20 +52,20 @@ def instance0(): multitest.next() try: # Wait for central to connect, then wait for it to disconnect. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_DISCONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): return # Start advertising again. ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") # Wait for central to connect, then disconnect it. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + print("gap_disconnect:", ble.gap_disconnect(waiting_data[0])) + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(last_data[0])) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) @@ -67,12 +77,10 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(last_data[0])) - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_DISCONNECT: + print("gap_disconnect:", ble.gap_disconnect(waiting_data[0])) + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): return # Wait for peripheral to start advertising again. @@ -81,8 +89,7 @@ def instance1(): # Connect to peripheral and then let the peripheral disconnect us. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index ee4a7a544..dafa36712 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -5,24 +5,24 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) GAP_DEVICE_NAME_UUID = bluetooth.UUID(0x2A00) -last_event = None -last_data = None +waiting_event = None +waiting_data = None value_handle = 0 def irq(event, data): - global last_event, last_data, value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -38,16 +38,23 @@ def irq(event, data): elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", data[-1]) + if waiting_event is not None: + if isinstance(waiting_event, int) and event == waiting_event: + waiting_event = None + waiting_data = data + def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -72,9 +79,9 @@ def instance0(): ble.gap_advertise(20_000) # Wait for central to connect, then wait for it to disconnect. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_DISCONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS): return finally: ble.active(0) @@ -91,10 +98,9 @@ def instance1(): # Connect to peripheral. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data if iteration == 0: print("gattc_discover_characteristics") @@ -111,8 +117,7 @@ def instance1(): # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_DISCONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): return finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index 1be557f9b..dba0c333b 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -5,16 +5,19 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) SERVICE_UUID = bluetooth.UUID("00000001-1111-2222-3333-444444444444") CHAR_CTRL_UUID = bluetooth.UUID("00000002-1111-2222-3333-444444444444") @@ -26,17 +29,15 @@ SERVICE = (SERVICE_UUID, (CHAR_CTRL, CHAR_RX, CHAR_TX)) SERVICES = (SERVICE,) -last_event = None -last_data = None +waiting_event = None +waiting_data = None ctrl_value_handle = 0 rx_value_handle = 0 tx_value_handle = 0 def irq(event, data): - global last_event, last_data, ctrl_value_handle, rx_value_handle, tx_value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, ctrl_value_handle, rx_value_handle, tx_value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -49,31 +50,44 @@ def irq(event, data): print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: if data[-1] == CHAR_CTRL_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) ctrl_value_handle = data[2] elif data[-1] == CHAR_RX_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) rx_value_handle = data[2] elif data[-1] == CHAR_TX_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) tx_value_handle = data[2] + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", data[-1]) - elif event == _IRQ_GATTC_WRITE_STATUS: - print("_IRQ_GATTC_WRITE_STATUS", data[-1]) + elif event == _IRQ_GATTC_READ_DONE: + print("_IRQ_GATTC_READ_DONE", data[-1]) + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: print("_IRQ_GATTC_NOTIFY", data[-1]) + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data + def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -89,14 +103,13 @@ def instance0(): multitest.next() try: # Wait for central to connect to us. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Wait for the central to signal that it's done with its part of the test. wait_for_event( - lambda: last_event == _IRQ_GATTS_WRITE and last_data[1] == char_ctrl_handle, + lambda event, data: event == _IRQ_GATTS_WRITE and data[1] == char_ctrl_handle, 2 * TIMEOUT_MS, ) @@ -125,32 +138,33 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) wait_for_event( - lambda: ctrl_value_handle and rx_value_handle and tx_value_handle, TIMEOUT_MS + lambda event, data: ctrl_value_handle and rx_value_handle and tx_value_handle, + TIMEOUT_MS, ) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) # Write to the characteristic a few times, with and without response. for i in range(4): print("gattc_write") ble.gattc_write(conn_handle, rx_value_handle, "central{}".format(i), i & 1) if i & 1: - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) time.sleep_ms(400) # Write to say that we are done with our part of the test. ble.gattc_write(conn_handle, ctrl_value_handle, "OK", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for notification that peripheral is done with its part of the test. wait_for_event( - lambda: last_event == _IRQ_GATTC_NOTIFY and last_data[1] == ctrl_value_handle, + lambda event, data: event == _IRQ_GATTC_NOTIFY and data[1] == ctrl_value_handle, TIMEOUT_MS, ) diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp index 6ecd48225..a1b3cbcd0 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp @@ -11,16 +11,17 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_SERVICE_RESULT UUID128('00000002-1111-2222-3333-444444444444') -_IRQ_GATTC_SERVICE_RESULT UUID128('00000003-1111-2222-3333-444444444444') -_IRQ_GATTC_SERVICE_RESULT UUID128('00000004-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000002-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000003-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000004-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE gattc_write gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 gattc_write gattc_write -_IRQ_GATTC_WRITE_STATUS 0 -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'message0' _IRQ_GATTC_NOTIFY b'message1' _IRQ_GATTC_NOTIFY b'message2' diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py b/tests/multi_bluetooth/ble_gattc_discover_services.py index b01a890c7..e746b8745 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -5,11 +5,12 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) UUID_A = bluetooth.UUID(0x180D) UUID_B = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") @@ -23,15 +24,13 @@ ) SERVICES = (SERVICE_A, SERVICE_B) -last_event = None -last_data = None +waiting_event = None +waiting_data = None num_service_result = 0 def irq(event, data): - global last_event, last_data, num_service_result - last_event = event - last_data = data + global waiting_event, waiting_data, num_service_result if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -45,16 +44,25 @@ def irq(event, data): print("_IRQ_GATTC_SERVICE_RESULT", data[3]) num_service_result += 1 + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data + def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -78,14 +86,13 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover services. ble.gattc_discover_services(conn_handle) - wait_for_event(lambda: num_service_result == 2, TIMEOUT_MS) + wait_for_event(lambda event, data: num_service_result == 2, TIMEOUT_MS) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py new file mode 100644 index 000000000..ef3199c5b --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py @@ -0,0 +1,208 @@ +# Test MTU exchange (initiated by both central and peripheral) and the effect on +# notify and write size. + +# Seven connections are made (four central->peripheral, three peripheral->central). +# +# Test | Requested | Preferred | Result | Notes +# 0 | 300 (C) | 256 (P) | 256 | +# 1 | 300 (C) | 200 (P) | 200 | +# 2 | 300 (C) | 400 (P) | 300 | +# 3 | 300 (C) | 50 (P) | 50 | Shorter than 64 so the notification is truncated. +# 4 | 290 (P) | 256 (C) | 256 | +# 5 | 290 (P) | 190 (C) | 190 | +# 6 | 290 (P) | 350 (C) | 290 | +# +# For each connection a notification is sent by the server (peripheral) and a characteristic +# is written by the client (central) to ensure that the expected size is transmitted. +# +# Note: This currently fails on btstack for two reasons: +# - btstack doesn't truncate writes to the MTU (it fails instead) +# - btstack (in central mode) doesn't handle the peripheral initiating the MTU exchange + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 5000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_MTU_EXCHANGED = const(21) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = ( + CHAR_UUID, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY, +) +SERVICE = ( + SERVICE_UUID, + (CHAR,), +) +SERVICES = (SERVICE,) + +waiting_events = {} + + +def irq(event, data): + global value_handle + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_WRITE: + print("_IRQ_GATTS_WRITE") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE") + elif event == _IRQ_GATTC_NOTIFY: + print("_IRQ_GATTC_NOTIFY", len(data[-1]), chr(data[-1][0])) + elif event == _IRQ_MTU_EXCHANGED: + print("_IRQ_MTU_EXCHANGED", data[-1]) + waiting_events[event] = data[-1] + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return True + machine.idle() + return False + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + ble.gatts_set_buffer(char_handle, 500, False) + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i == 1: + ble.config(mtu=200) + elif i == 2: + ble.config(mtu=400) + elif i == 3: + ble.config(mtu=50) + elif i >= 4: + ble.config(mtu=290) + else: + # This is the NimBLE default. + ble.config(mtu=256) + + # Wait for central to connect to us. + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + conn_handle = waiting_events[_IRQ_CENTRAL_CONNECT] + + if i >= 4: + print("gattc_exchange_mtu") + ble.gattc_exchange_mtu(conn_handle) + + if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): + return + mtu = waiting_events[_IRQ_MTU_EXCHANGED] + + print("gatts_notify") + ble.gatts_notify(conn_handle, char_handle, str(i) * 64) + + if not wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS): + return + + print("gatts_read") + data = ble.gatts_read(char_handle) + print("characteristic len:", len(data), chr(data[0])) + + # Wait for the central to disconnect. + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): + return + + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i < 4: + ble.config(mtu=300) + elif i == 5: + ble.config(mtu=190) + elif i == 6: + ble.config(mtu=350) + else: + ble.config(mtu=256) + + # Connect to peripheral and then disconnect. + print("gap_connect") + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): + return + conn_handle = waiting_events[_IRQ_PERIPHERAL_CONNECT] + + if i < 4: + print("gattc_exchange_mtu") + ble.gattc_exchange_mtu(conn_handle) + + if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): + return + mtu = waiting_events[_IRQ_MTU_EXCHANGED] + + if not wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS): + return + + print("gattc_discover_characteristics") + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + if not wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS): + return + value_handle = waiting_events[_IRQ_GATTC_CHARACTERISTIC_RESULT] + + # Write 20 more than the MTU to test truncation. + print("gattc_write") + ble.gattc_write(conn_handle, value_handle, chr(ord("a") + i) * (mtu + 20), 1) + if not wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS): + return + + # Disconnect from peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): + return + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_mtu.py.exp b/tests/multi_bluetooth/ble_mtu.py.exp new file mode 100644 index 000000000..1039a5da1 --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py.exp @@ -0,0 +1,143 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 a +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 200 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 197 b +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 300 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 297 c +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 50 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 47 d +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 e +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 190 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 187 f +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 290 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 287 g +_IRQ_CENTRAL_DISCONNECT +gap_advertise +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 0 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 200 +_IRQ_GATTC_NOTIFY 64 1 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 300 +_IRQ_GATTC_NOTIFY 64 2 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 50 +_IRQ_GATTC_NOTIFY 47 3 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 4 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 190 +_IRQ_GATTC_NOTIFY 64 5 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 290 +_IRQ_GATTC_NOTIFY 64 6 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_net/uasyncio_tcp_readexactly.py b/tests/multi_net/uasyncio_tcp_readexactly.py new file mode 100644 index 000000000..71d8c6d0e --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readexactly.py @@ -0,0 +1,68 @@ +# Test uasyncio stream readexactly() method using TCP server/client + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + +PORT = 8000 + + +async def handle_connection(reader, writer): + writer.write(b"a") + await writer.drain() + + # Split the first 2 bytes up so the client must wait for the second one + await asyncio.sleep(0.1) + + writer.write(b"b") + await writer.drain() + + writer.write(b"c") + await writer.drain() + + writer.write(b"d") + await writer.drain() + + print("close") + writer.close() + await writer.wait_closed() + + print("done") + ev.set() + + +async def tcp_server(): + global ev + ev = asyncio.Event() + server = await asyncio.start_server(handle_connection, "0.0.0.0", PORT) + print("server running") + multitest.next() + async with server: + await asyncio.wait_for(ev.wait(), 10) + + +async def tcp_client(): + reader, writer = await asyncio.open_connection(IP, PORT) + print(await reader.readexactly(2)) + print(await reader.readexactly(0)) + print(await reader.readexactly(1)) + try: + print(await reader.readexactly(2)) + except EOFError as er: + print("EOFError") + print(await reader.readexactly(0)) + + +def instance0(): + multitest.globals(IP=multitest.get_network_ip()) + asyncio.run(tcp_server()) + + +def instance1(): + multitest.next() + asyncio.run(tcp_client()) diff --git a/tests/multi_net/uasyncio_tcp_readexactly.py.exp b/tests/multi_net/uasyncio_tcp_readexactly.py.exp new file mode 100644 index 000000000..65ce6d628 --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readexactly.py.exp @@ -0,0 +1,10 @@ +--- instance0 --- +server running +close +done +--- instance1 --- +b'ab' +b'' +b'c' +EOFError +b'' diff --git a/tests/net_inet/test_tls_sites.py b/tests/net_inet/test_tls_sites.py index 876343acf..d2cb928c8 100644 --- a/tests/net_inet/test_tls_sites.py +++ b/tests/net_inet/test_tls_sites.py @@ -54,7 +54,7 @@ def main(): test_one(site, opts) print(site, "ok") except Exception as e: - print(site, repr(e)) + print(site, e) main() diff --git a/tests/net_inet/tls_num_errors.py b/tests/net_inet/tls_num_errors.py new file mode 100644 index 000000000..dd7f714e6 --- /dev/null +++ b/tests/net_inet/tls_num_errors.py @@ -0,0 +1,44 @@ +# test that modtls produces a numerical error message when out of heap + +try: + import usocket as socket, ussl as ssl, sys +except: + import socket, ssl, sys +try: + from micropython import alloc_emergency_exception_buf, heap_lock, heap_unlock +except: + print("SKIP") + raise SystemExit + + +# test with heap locked to see it switch to number-only error message +def test(addr): + alloc_emergency_exception_buf(256) + s = socket.socket() + s.connect(addr) + try: + s.setblocking(False) + s = ssl.wrap_socket(s, do_handshake=False) + heap_lock() + print("heap is locked") + while True: + ret = s.write("foo") + if ret: + break + heap_unlock() + print("wrap: no exception") + except OSError as e: + heap_unlock() + # mbedtls produces "-29184" + # axtls produces "RECORD_OVERFLOW" + ok = "-29184" in str(e) or "RECORD_OVERFLOW" in str(e) + print("wrap:", ok) + if not ok: + print("got exception:", e) + s.close() + + +if __name__ == "__main__": + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr) diff --git a/tests/net_inet/tls_num_errors.py.exp b/tests/net_inet/tls_num_errors.py.exp new file mode 100644 index 000000000..e6a15634d --- /dev/null +++ b/tests/net_inet/tls_num_errors.py.exp @@ -0,0 +1,2 @@ +heap is locked +wrap: True diff --git a/tests/net_inet/tls_text_errors.py b/tests/net_inet/tls_text_errors.py new file mode 100644 index 000000000..9e8ccfaf9 --- /dev/null +++ b/tests/net_inet/tls_text_errors.py @@ -0,0 +1,33 @@ +# test that modtls produces a text error message + +try: + import usocket as socket, ussl as ssl, sys +except: + import socket, ssl, sys + + +def test(addr): + s = socket.socket() + s.connect(addr) + try: + s = ssl.wrap_socket(s) + print("wrap: no exception") + except OSError as e: + # mbedtls produces "mbedtls -0x7200: SSL - An invalid SSL record was received" + # axtls produces "RECORD_OVERFLOW" but also prints "TLS buffer overflow,..." + # CPython produces "[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1108)" + ok = ( + "SSL_INVALID_RECORD" in str(e) + or "RECORD_OVERFLOW" in str(e) + or "wrong version" in str(e) + ) + print("wrap:", ok) + if not ok: + print("got exception:", e) + s.close() + + +if __name__ == "__main__": + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index c83895fb6..7ab4e85c5 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -92,20 +92,25 @@ def start_file(self, filename, prepend="", append=""): class PyInstanceSubProcess(PyInstance): - def __init__(self, cmd): - self.cmd = cmd + 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.cmd[0].rsplit("/")[-1] + return self.argv[0].rsplit("/")[-1] def run_script(self, script): output = b"" err = None try: p = subprocess.run( - self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=script + self.argv, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + input=script, + env=self.env, ) output = p.stdout except subprocess.CalledProcessError as er: @@ -114,7 +119,11 @@ def run_script(self, script): def start_script(self, script): self.popen = subprocess.Popen( - self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + self.argv, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=self.env, ) self.popen.stdin.write(script) self.popen.stdin.close() @@ -289,7 +298,7 @@ def run_test_on_instances(test_file, num_instances, instances): continue num_output += 1 last_read_time[idx] = time.time() - if out is not None: + 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: @@ -404,16 +413,20 @@ def main(): instances_test = [] for i in cmd_args.instance: - if i.startswith("exec:"): - instances_test.append(PyInstanceSubProcess([i[len("exec:") :]])) - elif i == "micropython": - instances_test.append(PyInstanceSubProcess([MICROPYTHON])) - elif i == "cpython": - instances_test.append(PyInstanceSubProcess([CPYTHON3])) - elif i.startswith("pyb:"): - instances_test.append(PyInstancePyboard(i[len("pyb:") :])) + # 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(i), file=sys.stderr) + print("unknown instance string: {}".format(cmd), file=sys.stderr) sys.exit(1) for _ in range(max_instances - len(instances_test)): diff --git a/tests/run-tests b/tests/run-tests index 74e2f71ac..102b0f779 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -355,6 +355,7 @@ def run_tests(pyb, tests, args, base_path="."): if not has_complex: skip_tests.add('float/complex1.py') skip_tests.add('float/complex1_intbig.py') + skip_tests.add('float/complex_special_mehods.py') skip_tests.add('float/int_big_float.py') skip_tests.add('float/true_value.py') skip_tests.add('float/types.py') @@ -414,6 +415,7 @@ def run_tests(pyb, tests, args, base_path="."): 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 async_with skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs + 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 diff --git a/tools/check_code_size.sh b/tools/check_code_size.sh new file mode 100755 index 000000000..2925ff168 --- /dev/null +++ b/tools/check_code_size.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# 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 5aef4ccfb..81a3cdcf8 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -36,6 +36,9 @@ PATHS = [ # C "extmod/*.[ch]", + "extmod/btstack/*.[ch]", + "extmod/nimble/*.[ch]", + "lib/mbedtls_errors/tester.c", "lib/netutils/*.[ch]", "lib/timeutils/*.[ch]", "lib/utils/*.[ch]", @@ -76,9 +79,6 @@ PY_EXTS = (".py",) -FIXUP_REPLACEMENTS = ((re.compile("sizeof\(([a-z_]+)\) \*\(([a-z_]+)\)"), r"sizeof(\1) * (\2)"),) - - def list_files(paths, exclusions=None, prefix=""): files = set() for pattern in paths: @@ -124,10 +124,6 @@ def fixup_c(filename): if directive == "endif": dedent_stack.pop() - # Apply general regex-based fixups. - for regex, replacement in FIXUP_REPLACEMENTS: - l = regex.sub(replacement, l) - # Write out line. f.write(l) diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 2cee6aebc..6779198c4 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -25,6 +25,7 @@ # THE SOFTWARE. from __future__ import print_function +import errno import sys import os import subprocess @@ -76,9 +77,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. @@ -170,7 +172,7 @@ def mkdir(path): try: os.mkdir(cur_path) except OSError as er: - if er.args[0] == 17: # file exists + if er.args[0] == errno.EEXIST: pass else: raise er @@ -182,14 +184,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(): diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg index b0b1239b1..80542b903 100644 --- a/tools/uncrustify.cfg +++ b/tools/uncrustify.cfg @@ -1,4 +1,4 @@ -# Uncrustify-0.70.1 +# Uncrustify-0.71.0_f # # General options @@ -37,13 +37,18 @@ string_replace_tab_chars = false # true/false # Improvements to template detection may make this option obsolete. tok_split_gte = false # true/false +# Disable formatting of NL_CONT ('\\n') ended lines (e.g. multiline macros) +disable_processing_nl_cont = false # true/false + # Specify the marker used in comments to disable processing of part of the # file. +# The comment should be used alone in one line. # # Default: *INDENT-OFF* disable_processing_cmt = " *FORMAT-OFF*" # string # Specify the marker used in comments to (re)enable processing in a file. +# The comment should be used alone in one line. # # Default: *INDENT-ON* enable_processing_cmt = " *FORMAT-ON*" # string @@ -507,35 +512,35 @@ sp_func_proto_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function declaration # without parameters. -sp_func_proto_paren_empty = ignore # ignore/add/remove/force +sp_func_proto_paren_empty = remove # ignore/add/remove/force # Add or remove space between function name and '(' with a typedef specifier. -sp_func_type_paren = ignore # ignore/add/remove/force +sp_func_type_paren = remove # ignore/add/remove/force # Add or remove space between alias name and '(' of a non-pointer function type typedef. -sp_func_def_paren = ignore # ignore/add/remove/force +sp_func_def_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function definition # without parameters. -sp_func_def_paren_empty = ignore # ignore/add/remove/force +sp_func_def_paren_empty = remove # ignore/add/remove/force # Add or remove space inside empty function '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. -sp_inside_fparens = ignore # ignore/add/remove/force +sp_inside_fparens = remove # ignore/add/remove/force # Add or remove space inside function '(' and ')'. sp_inside_fparen = remove # ignore/add/remove/force # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. -sp_inside_tparen = ignore # ignore/add/remove/force +sp_inside_tparen = remove # ignore/add/remove/force # Add or remove space between the ')' and '(' in a function type, as in # 'void (*x)(...)'. -sp_after_tparen_close = ignore # ignore/add/remove/force +sp_after_tparen_close = remove # ignore/add/remove/force # Add or remove space between ']' and '(' when part of a function call. -sp_square_fparen = ignore # ignore/add/remove/force +sp_square_fparen = remove # ignore/add/remove/force # Add or remove space between ')' and '{' of function. sp_fparen_brace = force # ignore/add/remove/force @@ -550,11 +555,11 @@ sp_fparen_brace_initializer = ignore # ignore/add/remove/force sp_fparen_dbrace = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function calls. -sp_func_call_paren = ignore # ignore/add/remove/force +sp_func_call_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function calls without # parameters. If set to ignore (the default), sp_func_call_paren is used. -sp_func_call_paren_empty = ignore # ignore/add/remove/force +sp_func_call_paren_empty = remove # ignore/add/remove/force # Add or remove space between the user function name and '(' on function # calls. You need to set a keyword to be a user function in the config file, @@ -604,6 +609,10 @@ sp_catch_paren = ignore # ignore/add/remove/force # in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. sp_oc_catch_paren = ignore # ignore/add/remove/force +# (OC) Add or remove space before Objective-C protocol list +# as in '@protocol Protocol' or '@interface MyClass : NSObject'. +sp_before_oc_proto_list = ignore # ignore/add/remove/force + # (OC) Add or remove space between class name and '(' # in '@interface className(categoryName):BaseClass' sp_oc_classname_paren = ignore # ignore/add/remove/force @@ -671,9 +680,7 @@ sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform # initialization. -# -# Default: add -sp_word_brace = add # ignore/add/remove/force +sp_word_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace. # @@ -1113,6 +1120,10 @@ indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. indent_sing_line_comments = 0 # unsigned number +# When opening a paren for a control statement (if, for, while, etc), increase +# the indent level by this value. Negative values decrease the indent level. +indent_sparen_extra = 0 # number + # Whether to indent trailing single line ('//') comments relative to the code # instead of trying to keep the same absolute column. indent_relative_single_line_comments = false # true/false @@ -1219,12 +1230,19 @@ indent_preserve_sql = false # true/false # Default: true indent_align_assign = false # true/false +# If true, the indentation of the chunks after a '=' sequence will be set at +# LHS token indentation column before '='. +indent_off_after_assign = false # true/false + # Whether to align continued statements at the '('. If false or the '(' is # followed by a newline, the next line indent is one tab. # # Default: true indent_align_paren = false # true/false +# (OC) Whether to indent Objective-C code inside message selectors. +indent_oc_inside_msg_sel = false # true/false + # (OC) Whether to indent Objective-C blocks at brace level instead of usual # rules. indent_oc_block = false # true/false @@ -1284,6 +1302,15 @@ indent_token_after_brace = true # true/false # Whether to indent the body of a C++11 lambda. indent_cpp_lambda_body = false # true/false +# How to indent compound literals that are being returned. +# true: add both the indent from return & the compound literal open brace (ie: +# 2 indent levels) +# false: only indent 1 level, don't add the indent for the open brace, only add +# the indent for the return. +# +# Default: true +indent_compound_literal_return = true # true/false + # (C#) Whether to indent a 'using' block if no braces are used. # # Default: true @@ -1296,6 +1323,12 @@ indent_using_block = true # true/false # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number +# Whether to indent the statments inside ternary operator. +indent_inside_ternary_operator = false # true/false + +# If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. +indent_off_after_return = false # true/false + # If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. indent_off_after_return_new = false # true/false @@ -1329,7 +1362,7 @@ nl_getset_leave_one_liners = false # true/false nl_cs_property_leave_one_liners = false # true/false # Don't split one-line function definitions, as in 'int foo() { return 0; }'. -# night modify nl_func_type_name +# might modify nl_func_type_name nl_func_leave_one_liners = false # true/false # Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. @@ -1436,6 +1469,9 @@ nl_else_brace = remove # ignore/add/remove/force # Add or remove newline between 'else' and 'if'. nl_else_if = ignore # ignore/add/remove/force +# Add or remove newline before '{' opening brace +nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force + # Add or remove newline before 'if'/'else if' closing parenthesis. nl_before_if_closing_paren = ignore # ignore/add/remove/force @@ -1684,6 +1720,9 @@ nl_func_decl_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function definition. nl_func_def_args = ignore # ignore/add/remove/force +# Add or remove newline after each ',' in a function call. +nl_func_call_args = ignore # ignore/add/remove/force + # Whether to add a newline after each ',' in a function declaration if '(' # and ')' are in different lines. If false, nl_func_decl_args is used instead. nl_func_decl_args_multi_line = false # true/false @@ -1725,6 +1764,9 @@ nl_func_call_empty = ignore # ignore/add/remove/force # has preference over nl_func_call_start_multi_line. nl_func_call_start = ignore # ignore/add/remove/force +# Whether to add a newline before ')' in a function call. +nl_func_call_end = ignore # ignore/add/remove/force + # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. nl_func_call_start_multi_line = false # true/false @@ -1737,6 +1779,9 @@ nl_func_call_args_multi_line = false # true/false # different lines. nl_func_call_end_multi_line = false # true/false +# Whether to respect nl_func_call_XXX option incase of closure args. +nl_func_call_args_multi_line_ignore_closures = false # true/false + # Whether to add a newline after '<' of a template parameter list. nl_template_start = false # true/false @@ -1871,7 +1916,7 @@ nl_before_return = false # true/false # close brace. nl_after_return = false # true/false -# (Java) Whether to put a blank line before a member '.' or '->' operators. +# Whether to put a blank line before a member '.' or '->' operators. nl_before_member = ignore # ignore/add/remove/force # (Java) Whether to put a blank line after a member '.' or '->' operators. @@ -2134,6 +2179,26 @@ nl_after_annotation = ignore # ignore/add/remove/force # (Java) Add or remove newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force +# The number of newlines before a whole-file #ifdef. +# +# 0: No change (default). +nl_before_whole_file_ifdef = 0 # unsigned number + +# The number of newlines after a whole-file #ifdef. +# +# 0: No change (default). +nl_after_whole_file_ifdef = 0 # unsigned number + +# The number of newlines before a whole-file #endif. +# +# 0: No change (default). +nl_before_whole_file_endif = 0 # unsigned number + +# The number of newlines after a whole-file #endif. +# +# 0: No change (default). +nl_after_whole_file_endif = 0 # unsigned number + # # Positioning options # @@ -2503,6 +2568,11 @@ align_oc_msg_colon_first = false # true/false # on the ':'. align_oc_decl_colon = false # true/false +# (OC) Whether to not align parameters in an Objectve-C message call if first +# colon is not on next line of the message call (the same way Xcode does +# aligment) +align_oc_msg_colon_xcode_like = false # true/false + # # Comment modification options # @@ -2734,6 +2804,25 @@ mod_sort_using = false # true/false # break your code if your includes/imports have ordering dependencies. mod_sort_include = false # true/false +# Whether to prioritize '#include' and '#import' statements that contain +# filename without extension when sorting is enabled. +mod_sort_incl_import_prioritize_filename = false # true/false + +# Whether to prioritize '#include' and '#import' statements that does not +# contain extensions when sorting is enabled. +mod_sort_incl_import_prioritize_extensionless = false # true/false + +# Whether to prioritize '#include' and '#import' statements that contain +# angle over quotes when sorting is enabled. +mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false + +# Whether to ignore file extension in '#include' and '#import' statements +# for sorting comparison. +mod_sort_incl_import_ignore_extension = false # true/false + +# Whether to group '#include' and '#import' statements when sorting is enabled. +mod_sort_incl_import_grouping_enabled = false # true/false + # Whether to move a 'break' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. mod_move_case_break = false # true/false @@ -2920,6 +3009,11 @@ use_sp_after_angle_always = false # true/false # Default: true use_options_overriding_for_qt_macros = true # true/false +# If true: the form feed character is removed from the list +# of whitespace characters. +# See https://en.cppreference.com/w/cpp/string/byte/isspace +use_form_feed_no_more_as_whitespace_character = false # true/false + # # Warn levels - 1: error, 2: warning (default), 3: note # @@ -2930,6 +3024,16 @@ use_options_overriding_for_qt_macros = true # true/false # Default: 2 warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number +# Limit the number of loops. +# Used by uncrustify.cpp to exit from infinite loop. +# 0: no limit. +debug_max_number_of_loops = 0 # number + +# Set the number of the line to protocol; +# Used in the function prot_the_line if the 2. parameter is zero. +# 0: nothing protocol. +debug_line_number_to_protocol = 0 # number + # Meaning of the settings: # Ignore - do not do any changes # Add - makes sure there is 1 or more space/brace/newline/etc @@ -2982,7 +3086,7 @@ warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number # `macro-close END_MESSAGE_MAP` # # -# option(s) with 'not default' value: 0 +# option(s) with 'not default' value: 67 # # Custom types for MicroPython