From 7f9d1d6ab923096582622b700bedb6a571518eac Mon Sep 17 00:00:00 2001
From: Damien George <damien.p.george@gmail.com>
Date: Thu, 9 Apr 2015 23:56:15 +0100
Subject: [PATCH] py: Overhaul and simplify printf/pfenv mechanism.

Previous to this patch the printing mechanism was a bit of a tangled
mess.  This patch attempts to consolidate printing into one interface.

All (non-debug) printing now uses the mp_print* family of functions,
mainly mp_printf.  All these functions take an mp_print_t structure as
their first argument, and this structure defines the printing backend
through the "print_strn" function of said structure.

Printing from the uPy core can reach the platform-defined print code via
two paths: either through mp_sys_stdout_obj (defined pert port) in
conjunction with mp_stream_write; or through the mp_plat_print structure
which uses the MP_PLAT_PRINT_STRN macro to define how string are printed
on the platform.  The former is only used when MICROPY_PY_IO is defined.

With this new scheme printing is generally more efficient (less layers
to go through, less arguments to pass), and, given an mp_print_t*
structure, one can call mp_print_str for efficiency instead of
mp_printf("%s", ...).  Code size is also reduced by around 200 bytes on
Thumb2 archs.
---
 bare-arm/main.c              |   3 +-
 cc3200/misc/pin_named_pins.c |   4 +-
 cc3200/mods/modwlan.c        |  10 +-
 cc3200/mods/pybadc.c         |   4 +-
 cc3200/mods/pybi2c.c         |   6 +-
 cc3200/mods/pybpin.c         |  10 +-
 cc3200/mods/pybspi.c         |   6 +-
 cc3200/mods/pybuart.c        |  20 ++--
 cc3200/mpconfigport.h        |   3 +
 esp8266/modesp.c             |   3 +-
 esp8266/modpybpin.c          |   4 +-
 esp8266/mpconfigport.h       |   3 +
 extmod/moductypes.c          |   4 +-
 extmod/modujson.c            |   5 +-
 extmod/modure.c              |   8 +-
 minimal/main.c               |   3 +-
 minimal/mpconfigport.h       |   3 +
 pic16bit/modpybled.c         |   4 +-
 pic16bit/modpybswitch.c      |   4 +-
 pic16bit/mpconfigport.h      |   3 +
 py/misc.h                    |   2 +
 py/modbuiltins.c             |  18 ++-
 py/modsys.c                  |   9 +-
 py/mpconfig.h                |   7 +-
 py/{pfenv.c => mpprint.c}    | 224 +++++++++++++++++++++++++++++++----
 py/{pfenv.h => mpprint.h}    |  40 ++++---
 py/obj.c                     |  36 +++---
 py/obj.h                     |   9 +-
 py/objarray.c                |  18 +--
 py/objbool.c                 |  10 +-
 py/objboundmeth.c            |  12 +-
 py/objcell.c                 |  10 +-
 py/objclosure.c              |  16 +--
 py/objcomplex.c              |  18 +--
 py/objdict.c                 |  30 ++---
 py/objexcept.c               |  12 +-
 py/objfloat.c                |  10 +-
 py/objfun.c                  |   4 +-
 py/objgenerator.c            |   4 +-
 py/objint.c                  |   4 +-
 py/objint.h                  |   2 +-
 py/objlist.c                 |  10 +-
 py/objmodule.c               |   6 +-
 py/objnamedtuple.c           |  12 +-
 py/objnone.c                 |   6 +-
 py/objrange.c                |   8 +-
 py/objset.c                  |  19 ++-
 py/objslice.c                |  20 ++--
 py/objstr.c                  | 110 +++++++++--------
 py/objstr.h                  |   2 +-
 py/objstringio.c             |   4 +-
 py/objstrunicode.c           |  33 +++---
 py/objtuple.c                |  16 +--
 py/objtuple.h                |   2 +-
 py/objtype.c                 |  28 ++---
 py/pfenv_printf.c            | 204 -------------------------------
 py/py.mk                     |   3 +-
 py/runtime.h                 |   3 +
 py/vstr.c                    |  37 ++----
 qemu-arm/main.c              |   3 +-
 qemu-arm/test_main.c         |   3 +-
 stmhal/adc.c                 |   8 +-
 stmhal/can.c                 |  13 +-
 stmhal/extint.c              |   8 +-
 stmhal/file.c                |   4 +-
 stmhal/i2c.c                 |   8 +-
 stmhal/led.c                 |   4 +-
 stmhal/modstm.c              |   4 +-
 stmhal/mpconfigport.h        |   3 +
 stmhal/pin.c                 |  20 ++--
 stmhal/pin_named_pins.c      |   4 +-
 stmhal/printf.c              |  51 ++++----
 stmhal/pybstdio.c            |   4 +-
 stmhal/pyexec.c              |   3 +-
 stmhal/servo.c               |   4 +-
 stmhal/spi.c                 |  14 +--
 stmhal/timer.c               |  19 +--
 stmhal/uart.c                |  12 +-
 stmhal/usb.c                 |   4 +-
 stmhal/usrsw.c               |   4 +-
 teensy/led.c                 |   4 +-
 teensy/mpconfigport.h        |   3 +
 teensy/timer.c               |  13 +-
 teensy/uart.c                |  10 +-
 unix-cpy/main.c              |   1 -
 unix/file.c                  |   4 +-
 unix/main.c                  |   3 +-
 unix/modffi.c                |  16 +--
 unix/modsocket.c             |   4 +-
 unix/mpconfigport.h          |   2 +
 windows/mpconfigport.h       |   2 +
 91 files changed, 671 insertions(+), 716 deletions(-)
 rename py/{pfenv.c => mpprint.c} (58%)
 rename py/{pfenv.h => mpprint.h} (66%)
 delete mode 100644 py/pfenv_printf.c

diff --git a/bare-arm/main.c b/bare-arm/main.c
index 61a43beec..ca32dc459 100644
--- a/bare-arm/main.c
+++ b/bare-arm/main.c
@@ -6,7 +6,6 @@
 #include "py/compile.h"
 #include "py/runtime.h"
 #include "py/repl.h"
-#include "py/pfenv.h"
 
 void do_str(const char *src) {
     mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
@@ -23,7 +22,7 @@ void do_str(const char *src) {
         nlr_pop();
     } else {
         // uncaught exception
-        mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
     }
 }
 
diff --git a/cc3200/misc/pin_named_pins.c b/cc3200/misc/pin_named_pins.c
index 590e013d4..a3e99757e 100644
--- a/cc3200/misc/pin_named_pins.c
+++ b/cc3200/misc/pin_named_pins.c
@@ -37,9 +37,9 @@
 #include "pybpin.h"
 #include MICROPY_HAL_H
 
-STATIC void pin_named_pins_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pin_named_pins_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pin_named_pins_obj_t *self = self_in;
-    print(env, "<Pin.%s>", qstr_str(self->name));
+    mp_printf(print, "<Pin.%s>", qstr_str(self->name));
 }
 
 const mp_obj_type_t pin_cpu_pins_obj_type = {
diff --git a/cc3200/mods/modwlan.c b/cc3200/mods/modwlan.c
index 9a7a86775..cf7519a0d 100644
--- a/cc3200/mods/modwlan.c
+++ b/cc3200/mods/modwlan.c
@@ -691,19 +691,19 @@ STATIC mp_obj_t wlan_make_new (mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_k
     return &wlan_obj;
 }
 
-STATIC void wlan_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void wlan_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     wlan_obj_t *self = self_in;
-    print(env, "<WLAN, mode=%u", self->mode);
+    mp_printf(print, "<WLAN, mode=%u", self->mode);
 
     // only print the bssid if in station mode
     if (self->mode != ROLE_AP && GET_STATUS_BIT(self->status, STATUS_BIT_CONNECTION)) {
-        print(env, ", connected to: ssid=%s, bssid=%02x:%02x:%02x:%02x:%02x:%02x", self->ssid,
+        mp_printf(print, ", connected to: ssid=%s, bssid=%02x:%02x:%02x:%02x:%02x:%02x", self->ssid,
               self->bssid[0], self->bssid[1], self->bssid[2], self->bssid[3], self->bssid[4], self->bssid[5]);
     }
     else {
-        print(env, ", ssid=%s", self->ssid);
+        mp_printf(print, ", ssid=%s", self->ssid);
     }
-    print(env, ", security=%u>", self->security);
+    mp_printf(print, ", security=%u>", self->security);
 }
 
 /// \method connect(ssid, security=OPEN, key=None, bssid=None)
diff --git a/cc3200/mods/pybadc.c b/cc3200/mods/pybadc.c
index c377d5108..ae7de73eb 100644
--- a/cc3200/mods/pybadc.c
+++ b/cc3200/mods/pybadc.c
@@ -100,9 +100,9 @@ STATIC pyb_adc_obj_t pyb_adc_obj[PYB_ADC_NUM_CHANNELS];
 /******************************************************************************/
 /* Micro Python bindings : adc object                                         */
 
-STATIC void adc_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_adc_obj_t *self = self_in;
-    print(env, "<ADC, channel=%u>", self->num);
+    mp_printf(print, "<ADC, channel=%u>", self->num);
 }
 
 /// \classmethod \constructor(channel)
diff --git a/cc3200/mods/pybi2c.c b/cc3200/mods/pybi2c.c
index 6fd58b0c0..79a425d21 100644
--- a/cc3200/mods/pybi2c.c
+++ b/cc3200/mods/pybi2c.c
@@ -294,13 +294,13 @@ STATIC mp_obj_t pyb_i2c_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n
     return (mp_obj_t)self;
 }
 
-STATIC void pyb_i2c_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_i2c_obj_t *self = self_in;
     if (self->baudrate > 0) {
-        print(env, "<I2C0, I2C.MASTER, baudrate=%u>)", self->baudrate);
+        mp_printf(print, "<I2C0, I2C.MASTER, baudrate=%u>)", self->baudrate);
     }
     else {
-        print(env, "<I2C0>");
+        mp_print_str(print, "<I2C0>");
     }
 }
 
diff --git a/cc3200/mods/pybpin.c b/cc3200/mods/pybpin.c
index b8db31941..dbfc34866 100644
--- a/cc3200/mods/pybpin.c
+++ b/cc3200/mods/pybpin.c
@@ -429,14 +429,14 @@ STATIC mp_obj_t pin_obj_init_helper(pin_obj_t *self, mp_uint_t n_args, const mp_
 
 /// \method print()
 /// Return a string describing the pin object.
-STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pin_obj_t *self = self_in;
     uint32_t af = MAP_PinModeGet(self->pin_num);
     uint32_t type = pin_get_type(self);
     uint32_t strength = pin_get_strenght(self);
 
     // pin name
-    print(env, "<Pin.cpu.%s, af=%u", qstr_str(self->name), af);
+    mp_printf(print, "<Pin.cpu.%s, af=%u", qstr_str(self->name), af);
 
     if (af == PIN_MODE_0) {
         // IO mode
@@ -447,7 +447,7 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
         } else {
             mode_qst = MP_QSTR_OUT;
         }
-        print(env, ", mode=Pin.%s", qstr_str(mode_qst)); // safe because mode_qst has no formatting chars
+        mp_printf(print, ", mode=Pin.%s", qstr_str(mode_qst));
     }
 
     // pin type
@@ -465,7 +465,7 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
     } else {
         type_qst = MP_QSTR_OD_PD;
     }
-    print(env, ", pull=Pin.%s", qstr_str(type_qst));
+    mp_printf(print, ", pull=Pin.%s", qstr_str(type_qst));
 
     // Strength
     qstr str_qst;
@@ -476,7 +476,7 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
     } else {
         str_qst = MP_QSTR_S6MA;
     }
-    print(env, ", strength=Pin.%s>", qstr_str(str_qst));
+    mp_printf(print, ", strength=Pin.%s>", qstr_str(str_qst));
 }
 
 /// \classmethod \constructor(id, ...)
diff --git a/cc3200/mods/pybspi.c b/cc3200/mods/pybspi.c
index ceddd2280..bcd673225 100644
--- a/cc3200/mods/pybspi.c
+++ b/cc3200/mods/pybspi.c
@@ -163,15 +163,15 @@ STATIC void pybspi_transfer (pyb_spi_obj_t *self, const char *txdata, char *rxda
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 /******************************************************************************/
-STATIC void pyb_spi_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_spi_obj_t *self = self_in;
 
     if (self->baudrate > 0) {
-        print(env, "<SPI0, SPI.MASTER, baudrate=%u, config=%u, submode=%u, bits=%u>",
+        mp_printf(print, "<SPI0, SPI.MASTER, baudrate=%u, config=%u, submode=%u, bits=%u>",
               self->baudrate, self->config, self->submode, (self->wlen * 8));
     }
     else {
-        print(env, "<SPI0>");
+        mp_print_str(print, "<SPI0>");
     }
 }
 
diff --git a/cc3200/mods/pybuart.c b/cc3200/mods/pybuart.c
index 8b998872a..9465cef08 100644
--- a/cc3200/mods/pybuart.c
+++ b/cc3200/mods/pybuart.c
@@ -312,37 +312,37 @@ STATIC void uart_callback_disable (mp_obj_t self_in) {
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 
-STATIC void pyb_uart_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_uart_obj_t *self = self_in;
     if (self->baudrate > 0) {
-        print(env, "<UART%u, baudrate=%u, bits=", self->uart_id, self->baudrate);
+        mp_printf(print, "<UART%u, baudrate=%u, bits=", self->uart_id, self->baudrate);
         switch (self->config & UART_CONFIG_WLEN_MASK) {
         case UART_CONFIG_WLEN_5:
-            print(env, "5");
+            mp_print_str(print, "5");
             break;
         case UART_CONFIG_WLEN_6:
-            print(env, "6");
+            mp_print_str(print, "6");
             break;
         case UART_CONFIG_WLEN_7:
-            print(env, "7");
+            mp_print_str(print, "7");
             break;
         case UART_CONFIG_WLEN_8:
-            print(env, "8");
+            mp_print_str(print, "8");
             break;
         default:
             break;
         }
         if ((self->config & UART_CONFIG_PAR_MASK) == UART_CONFIG_PAR_NONE) {
-            print(env, ", parity=None");
+            mp_print_str(print, ", parity=None");
         } else {
-            print(env, ", parity=%u", (self->config & UART_CONFIG_PAR_MASK) == UART_CONFIG_PAR_EVEN ? 0 : 1);
+            mp_printf(print, ", parity=%u", (self->config & UART_CONFIG_PAR_MASK) == UART_CONFIG_PAR_EVEN ? 0 : 1);
         }
-        print(env, ", stop=%u, timeout=%u, timeout_char=%u, read_buf_len=%u>",
+        mp_printf(print, ", stop=%u, timeout=%u, timeout_char=%u, read_buf_len=%u>",
               (self->config & UART_CONFIG_STOP_MASK) == UART_CONFIG_STOP_ONE ? 1 : 2,
                self->timeout, self->timeout_char, self->read_buf_len);
     }
     else {
-        print(env, "<UART%u>", self->uart_id);
+        mp_printf(print, "<UART%u>", self->uart_id);
     }
 }
 
diff --git a/cc3200/mpconfigport.h b/cc3200/mpconfigport.h
index c5caa2120..fa1207d74 100644
--- a/cc3200/mpconfigport.h
+++ b/cc3200/mpconfigport.h
@@ -146,6 +146,9 @@ typedef void            *machine_ptr_t;             // must be of pointer size
 typedef const void      *machine_const_ptr_t;       // must be of pointer size
 typedef long            mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, uint32_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 #define MICROPY_BEGIN_ATOMIC_SECTION()              disable_irq()
 #define MICROPY_END_ATOMIC_SECTION(state)           enable_irq(state)
 
diff --git a/esp8266/modesp.c b/esp8266/modesp.c
index 29ef42ab2..3b5a0b7d7 100644
--- a/esp8266/modesp.c
+++ b/esp8266/modesp.c
@@ -32,7 +32,6 @@
 #include "py/obj.h"
 #include "py/gc.h"
 #include "py/runtime.h"
-#include "py/pfenv.h"
 #include MICROPY_HAL_H
 #include "queue.h"
 #include "user_interface.h"
@@ -52,7 +51,7 @@ mp_obj_t call_function_1_protected(mp_obj_t fun, mp_obj_t arg) {
     if (nlr_push(&nlr) == 0) {
         return mp_call_function_1(fun, arg);
     } else {
-        mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
         return (mp_obj_t)nlr.ret_val;
     }
 }
diff --git a/esp8266/modpybpin.c b/esp8266/modpybpin.c
index 0089e71ab..8b09c7f65 100644
--- a/esp8266/modpybpin.c
+++ b/esp8266/modpybpin.c
@@ -65,11 +65,11 @@ STATIC const pyb_pin_obj_t pyb_pin_obj[] = {
     {{&pyb_pin_type}, 15, 15, PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15},
 };
 
-STATIC void pyb_pin_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_pin_obj_t *self = self_in;
 
     // pin name
-    print(env, "Pin(%u)", self->pin_id);
+    mp_printf(print, "Pin(%u)", self->pin_id);
 }
 
 // pin.init(mode, pull=Pin.PULL_NONE, af=-1)
diff --git a/esp8266/mpconfigport.h b/esp8266/mpconfigport.h
index 7e7f0fdba..dcd9407f8 100644
--- a/esp8266/mpconfigport.h
+++ b/esp8266/mpconfigport.h
@@ -52,6 +52,9 @@ typedef void *machine_ptr_t; // must be of pointer size
 typedef const void *machine_const_ptr_t; // must be of pointer size
 typedef long mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 // extra built in names to add to the global namespace
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \
diff --git a/extmod/moductypes.c b/extmod/moductypes.c
index f9f0ca79b..dff8abd8d 100644
--- a/extmod/moductypes.c
+++ b/extmod/moductypes.c
@@ -137,7 +137,7 @@ STATIC mp_obj_t uctypes_struct_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_u
     return o;
 }
 
-STATIC void uctypes_struct_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_uctypes_struct_t *self = self_in;
     const char *typen = "unk";
@@ -154,7 +154,7 @@ STATIC void uctypes_struct_print(void (*print)(void *env, const char *fmt, ...),
     } else {
         typen = "ERROR";
     }
-    print(env, "<struct %s %p>", typen, self->addr);
+    mp_printf(print, "<struct %s %p>", typen, self->addr);
 }
 
 // Get size of any type descriptor
diff --git a/extmod/modujson.c b/extmod/modujson.c
index 747a86ac6..d6095cbb8 100644
--- a/extmod/modujson.c
+++ b/extmod/modujson.c
@@ -35,8 +35,9 @@
 
 STATIC mp_obj_t mod_ujson_dumps(mp_obj_t obj) {
     vstr_t vstr;
-    vstr_init(&vstr, 8);
-    mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))vstr_printf, &vstr, obj, PRINT_JSON);
+    mp_print_t print;
+    vstr_init_print(&vstr, 8, &print);
+    mp_obj_print_helper(&print, obj, PRINT_JSON);
     return mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_ujson_dumps_obj, mod_ujson_dumps);
diff --git a/extmod/modure.c b/extmod/modure.c
index 66fb51aef..9bc72add2 100644
--- a/extmod/modure.c
+++ b/extmod/modure.c
@@ -51,10 +51,10 @@ typedef struct _mp_obj_match_t {
 } mp_obj_match_t;
 
 
-STATIC void match_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void match_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_match_t *self = self_in;
-    print(env, "<match num=%d>", self->num_matches);
+    mp_printf(print, "<match num=%d>", self->num_matches);
 }
 
 STATIC mp_obj_t match_group(mp_obj_t self_in, mp_obj_t no_in) {
@@ -86,10 +86,10 @@ STATIC const mp_obj_type_t match_type = {
     .locals_dict = (mp_obj_t)&match_locals_dict,
 };
 
-STATIC void re_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void re_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_re_t *self = self_in;
-    print(env, "<re %p>", self);
+    mp_printf(print, "<re %p>", self);
 }
 
 STATIC mp_obj_t re_exec(bool is_anchored, uint n_args, const mp_obj_t *args) {
diff --git a/minimal/main.c b/minimal/main.c
index 1d600d584..29b5af1af 100644
--- a/minimal/main.c
+++ b/minimal/main.c
@@ -6,7 +6,6 @@
 #include "py/compile.h"
 #include "py/runtime.h"
 #include "py/repl.h"
-#include "py/pfenv.h"
 #include "py/gc.h"
 #include "pyexec.h"
 
@@ -26,7 +25,7 @@ void do_str(const char *src) {
         nlr_pop();
     } else {
         // uncaught exception
-        mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
     }
 }
 
diff --git a/minimal/mpconfigport.h b/minimal/mpconfigport.h
index 02ad5bd62..b08bdf8b7 100644
--- a/minimal/mpconfigport.h
+++ b/minimal/mpconfigport.h
@@ -60,6 +60,9 @@ typedef void *machine_ptr_t; // must be of pointer size
 typedef const void *machine_const_ptr_t; // must be of pointer size
 typedef long mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 // extra built in names to add to the global namespace
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \
diff --git a/pic16bit/modpybled.c b/pic16bit/modpybled.c
index c44bb1d24..1610dc568 100644
--- a/pic16bit/modpybled.c
+++ b/pic16bit/modpybled.c
@@ -41,9 +41,9 @@ STATIC const pyb_led_obj_t pyb_led_obj[] = {
 #define NUM_LED MP_ARRAY_SIZE(pyb_led_obj)
 #define LED_ID(obj) ((obj) - &pyb_led_obj[0] + 1)
 
-void pyb_led_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void pyb_led_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_led_obj_t *self = self_in;
-    print(env, "LED(%u)", LED_ID(self));
+    mp_printf(print, "LED(%u)", LED_ID(self));
 }
 
 STATIC mp_obj_t pyb_led_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
diff --git a/pic16bit/modpybswitch.c b/pic16bit/modpybswitch.c
index f5047b78f..901e478cc 100644
--- a/pic16bit/modpybswitch.c
+++ b/pic16bit/modpybswitch.c
@@ -40,9 +40,9 @@ STATIC const pyb_switch_obj_t pyb_switch_obj[] = {
 #define NUM_SWITCH MP_ARRAY_SIZE(pyb_switch_obj)
 #define SWITCH_ID(obj) ((obj) - &pyb_switch_obj[0] + 1)
 
-void pyb_switch_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void pyb_switch_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_switch_obj_t *self = self_in;
-    print(env, "Switch(%u)", SWITCH_ID(self));
+    mp_printf(print, "Switch(%u)", SWITCH_ID(self));
 }
 
 STATIC mp_obj_t pyb_switch_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
diff --git a/pic16bit/mpconfigport.h b/pic16bit/mpconfigport.h
index 06ed6669e..d58401a90 100644
--- a/pic16bit/mpconfigport.h
+++ b/pic16bit/mpconfigport.h
@@ -86,6 +86,9 @@ typedef void *machine_ptr_t; // must be pointer size
 typedef const void *machine_const_ptr_t; // must be pointer size
 typedef int mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 // extra builtin names to add to the global namespace
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \
diff --git a/py/misc.h b/py/misc.h
index f920d52a1..2c00b6880 100644
--- a/py/misc.h
+++ b/py/misc.h
@@ -147,6 +147,8 @@ typedef struct _vstr_t {
 void vstr_init(vstr_t *vstr, size_t alloc);
 void vstr_init_len(vstr_t *vstr, size_t len);
 void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf);
+struct _mp_print_t;
+void vstr_init_print(vstr_t *vstr, size_t alloc, struct _mp_print_t *print);
 void vstr_clear(vstr_t *vstr);
 vstr_t *vstr_new(void);
 vstr_t *vstr_new_size(size_t alloc);
diff --git a/py/modbuiltins.c b/py/modbuiltins.c
index 04141edec..f080f457b 100644
--- a/py/modbuiltins.c
+++ b/py/modbuiltins.c
@@ -35,7 +35,6 @@
 #include "py/runtime.h"
 #include "py/builtin.h"
 #include "py/stream.h"
-#include "py/pfenv.h"
 
 #if MICROPY_PY_BUILTINS_FLOAT
 #include <math.h>
@@ -413,9 +412,7 @@ STATIC mp_obj_t mp_builtin_print(mp_uint_t n_args, const mp_obj_t *args, mp_map_
         stream_obj = file_elem->value;
     }
 
-    pfenv_t pfenv;
-    pfenv.data = stream_obj;
-    pfenv.print_strn = (void (*)(void *, const char *, mp_uint_t))mp_stream_write;
+    mp_print_t print = {stream_obj, (mp_print_strn_t)mp_stream_write};
     #endif
     for (mp_uint_t i = 0; i < n_args; i++) {
         if (i > 0) {
@@ -426,7 +423,7 @@ STATIC mp_obj_t mp_builtin_print(mp_uint_t n_args, const mp_obj_t *args, mp_map_
             #endif
         }
         #if MICROPY_PY_IO
-        mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))pfenv_printf, &pfenv, args[i], PRINT_STR);
+        mp_obj_print_helper(&print, args[i], PRINT_STR);
         #else
         mp_obj_print(args[i], PRINT_STR);
         #endif
@@ -443,10 +440,8 @@ 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) {
         #if MICROPY_PY_IO
-        pfenv_t pfenv;
-        pfenv.data = &mp_sys_stdout_obj;
-        pfenv.print_strn = (void (*)(void *, const char *, mp_uint_t))mp_stream_write;
-        mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))pfenv_printf, &pfenv, o, PRINT_REPR);
+        mp_print_t print = {&mp_sys_stdout_obj, (mp_print_strn_t)mp_stream_write};
+        mp_obj_print_helper(&print, o, PRINT_REPR);
         mp_stream_write(&mp_sys_stdout_obj, "\n", 1);
         #else
         mp_obj_print(o, PRINT_REPR);
@@ -459,8 +454,9 @@ 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;
-    vstr_init(&vstr, 16);
-    mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))vstr_printf, &vstr, o_in, PRINT_REPR);
+    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);
diff --git a/py/modsys.c b/py/modsys.c
index 5051e7295..09e72e98a 100644
--- a/py/modsys.c
+++ b/py/modsys.c
@@ -31,7 +31,6 @@
 #include "py/objtuple.h"
 #include "py/objstr.h"
 #include "py/objint.h"
-#include "py/pfenv.h"
 #include "py/stream.h"
 
 #if MICROPY_PY_SYS
@@ -78,12 +77,10 @@ STATIC mp_obj_t mp_sys_print_exception(mp_uint_t n_args, const mp_obj_t *args) {
         stream_obj = args[1];
     }
 
-    pfenv_t pfenv;
-    pfenv.data = stream_obj;
-    pfenv.print_strn = (void (*)(void *, const char *, mp_uint_t))mp_stream_write;
-    mp_obj_print_exception((void (*)(void *env, const char *fmt, ...))pfenv_printf, &pfenv, args[0]);
+    mp_print_t print = {stream_obj, (mp_print_strn_t)mp_stream_write};
+    mp_obj_print_exception(&print, args[0]);
     #else
-    mp_obj_print_exception(printf_wrapper, NULL, args[0]);
+    mp_obj_print_exception(&mp_plat_print, args[0]);
     #endif
 
     return mp_const_none;
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 21ae0c3f5..c78221f3a 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -691,7 +691,7 @@ typedef double mp_float_t;
 #define MICROPY_MAKE_POINTER_CALLABLE(p) (p)
 #endif
 
-// If these MP_PLAT_* macros are overridden then the memory allocated by them
+// If these MP_PLAT_*_EXEC macros are overridden then the memory allocated by them
 // must be somehow reachable for marking by the GC, since the native code
 // generators store pointers to GC managed memory in the code.
 #ifndef MP_PLAT_ALLOC_EXEC
@@ -702,6 +702,11 @@ typedef double mp_float_t;
 #define MP_PLAT_FREE_EXEC(ptr, size) m_del(byte, ptr, size)
 #endif
 
+// This macro is used to do all output (except when MICROPY_PY_IO is defined)
+#ifndef MP_PLAT_PRINT_STRN
+#define MP_PLAT_PRINT_STRN(str, len) printf("%.*s", (int)len, str)
+#endif
+
 #ifndef MP_SSIZE_MAX
 #define MP_SSIZE_MAX SSIZE_MAX
 #endif
diff --git a/py/pfenv.c b/py/mpprint.c
similarity index 58%
rename from py/pfenv.c
rename to py/mpprint.c
index f0a894e49..cb39aa813 100644
--- a/py/pfenv.c
+++ b/py/mpprint.c
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (c) 2013, 2014 Damien P. George
+ * Copyright (c) 2013-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
@@ -24,11 +24,16 @@
  * THE SOFTWARE.
  */
 
+#include <assert.h>
+#include <stdarg.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <string.h>
 
+#include "py/mpprint.h"
+#include "py/obj.h"
 #include "py/objint.h"
-#include "py/pfenv.h"
+#include "py/runtime.h"
 
 #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
 #include <stdio.h>
@@ -41,11 +46,22 @@
 static const char pad_spaces[] = "                ";
 static const char pad_zeroes[] = "0000000000000000";
 
-void pfenv_vstr_add_strn(void *data, const char *str, mp_uint_t len) {
-    vstr_add_strn(data, str, len);
+STATIC void plat_print_strn(void *env, const char *str, mp_uint_t len) {
+    (void)env;
+    MP_PLAT_PRINT_STRN(str, len);
 }
 
-int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int flags, char fill, int width) {
+const mp_print_t mp_plat_print = {NULL, plat_print_strn};
+
+int mp_print_str(const mp_print_t *print, const char *str) {
+    mp_uint_t len = strlen(str);
+    if (len) {
+        print->print_strn(print->data, str, len);
+    }
+    return len;
+}
+
+int mp_print_strn(const mp_print_t *print, const char *str, mp_uint_t len, int flags, char fill, int width) {
     int left_pad = 0;
     int right_pad = 0;
     int pad = width - len;
@@ -53,7 +69,7 @@ int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int f
     int total_chars_printed = 0;
     const char *pad_chars;
 
-    if (!fill || fill == ' ' ) {
+    if (!fill || fill == ' ') {
         pad_chars = pad_spaces;
         pad_size = sizeof(pad_spaces) - 1;
     } else if (fill == '0') {
@@ -82,12 +98,12 @@ int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int f
             if (p > pad_size) {
                 p = pad_size;
             }
-            pfenv->print_strn(pfenv->data, pad_chars, p);
+            print->print_strn(print->data, pad_chars, p);
             left_pad -= p;
         }
     }
     if (len) {
-        pfenv->print_strn(pfenv->data, str, len);
+        print->print_strn(print->data, str, len);
         total_chars_printed += len;
     }
     if (right_pad > 0) {
@@ -97,7 +113,7 @@ int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int f
             if (p > pad_size) {
                 p = pad_size;
             }
-            pfenv->print_strn(pfenv->data, pad_chars, p);
+            print->print_strn(print->data, pad_chars, p);
             right_pad -= p;
         }
     }
@@ -109,8 +125,8 @@ int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int f
 #define INT_BUF_SIZE (sizeof(mp_int_t) * 4)
 
 // This function is used by stmhal port to implement printf.
-// It needs to be a separate function to pfenv_print_mp_int, since converting to a mp_int looses the MSB.
-int pfenv_print_int(const pfenv_t *pfenv, mp_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width) {
+// It needs to be a separate function to mp_print_mp_int, since converting to a mp_int looses the MSB.
+int mp_print_int(const mp_print_t *print, mp_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width) {
     char sign = 0;
     if (sgn) {
         if ((mp_int_t)x < 0) {
@@ -156,12 +172,12 @@ int pfenv_print_int(const pfenv_t *pfenv, mp_uint_t x, int sgn, int base, int ba
     int len = 0;
     if (flags & PF_FLAG_PAD_AFTER_SIGN) {
         if (sign) {
-            len += pfenv_print_strn(pfenv, &sign, 1, flags, fill, 1);
+            len += mp_print_strn(print, &sign, 1, flags, fill, 1);
             width--;
         }
         if (prefix_char) {
-            len += pfenv_print_strn(pfenv, "0", 1, flags, fill, 1);
-            len += pfenv_print_strn(pfenv, &prefix_char, 1, flags, fill, 1);
+            len += mp_print_strn(print, "0", 1, flags, fill, 1);
+            len += mp_print_strn(print, &prefix_char, 1, flags, fill, 1);
             width -= 2;
         }
     } else {
@@ -174,11 +190,11 @@ int pfenv_print_int(const pfenv_t *pfenv, mp_uint_t x, int sgn, int base, int ba
         }
     }
 
-    len += pfenv_print_strn(pfenv, b, buf + INT_BUF_SIZE - b, flags, fill, width);
+    len += mp_print_strn(print, b, buf + INT_BUF_SIZE - b, flags, fill, width);
     return len;
 }
 
-int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec) {
+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) {
     if (!MP_OBJ_IS_INT(x)) {
         // This will convert booleans to int, or raise an error for
         // non-integer types.
@@ -282,16 +298,16 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int base, int base_char
 
     int len = 0;
     if (spaces_before) {
-        len += pfenv_print_strn(pfenv, "", 0, 0, ' ', spaces_before);
+        len += mp_print_strn(print, "", 0, 0, ' ', spaces_before);
     }
     if (flags & PF_FLAG_PAD_AFTER_SIGN) {
         // pad after sign implies pad after prefix as well.
         if (sign) {
-            len += pfenv_print_strn(pfenv, &sign, 1, 0, 0, 1);
+            len += mp_print_strn(print, &sign, 1, 0, 0, 1);
             width--;
         }
         if (prefix_len) {
-            len += pfenv_print_strn(pfenv, prefix, prefix_len, 0, 0, 1);
+            len += mp_print_strn(print, prefix, prefix_len, 0, 0, 1);
             width -= prefix_len;
         }
     }
@@ -299,10 +315,10 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int base, int base_char
         width = prec;
     }
 
-    len += pfenv_print_strn(pfenv, str, fmt_size, flags, fill, width);
+    len += mp_print_strn(print, str, fmt_size, flags, fill, width);
 
     if (spaces_after) {
-        len += pfenv_print_strn(pfenv, "", 0, 0, ' ', spaces_after);
+        len += mp_print_strn(print, "", 0, 0, ' ', spaces_after);
     }
 
     if (buf != stack_buf) {
@@ -312,7 +328,7 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int base, int base_char
 }
 
 #if MICROPY_PY_BUILTINS_FLOAT
-int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec) {
+int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec) {
     char buf[32];
     char sign = '\0';
     int chrs = 0;
@@ -361,7 +377,7 @@ int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, c
         if (*s <= '9' || (flags & PF_FLAG_PAD_NAN_INF)) {
             // We have a number, or we have a inf/nan and PAD_NAN_INF is set
             // With '{:06e}'.format(float('-inf')) you get '-00inf'
-            chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 0, 1);
+            chrs += mp_print_strn(print, &buf[0], 1, 0, 0, 1);
             width--;
             len--;
         }
@@ -373,8 +389,168 @@ int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, c
         // so suppress the zero fill.
         fill = ' ';
     }
-    chrs += pfenv_print_strn(pfenv, s, len, flags, fill, width);
+    chrs += mp_print_strn(print, s, len, flags, fill, width);
 
     return chrs;
 }
 #endif
+
+int mp_printf(const mp_print_t *print, const char *fmt, ...) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = mp_vprintf(print, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) {
+    int chrs = 0;
+    for (;;) {
+        {
+            const char *f = fmt;
+            while (*f != '\0' && *f != '%') {
+                ++f; // XXX UTF8 advance char
+            }
+            if (f > fmt) {
+                print->print_strn(print->data, fmt, f - fmt);
+                chrs += f - fmt;
+                fmt = f;
+            }
+        }
+
+        if (*fmt == '\0') {
+            break;
+        }
+
+        // move past % character
+        ++fmt;
+
+        // parse flags, if they exist
+        int flags = 0;
+        char fill = ' ';
+        while (*fmt != '\0') {
+            if (*fmt == '-') flags |= PF_FLAG_LEFT_ADJUST;
+            else if (*fmt == '+') flags |= PF_FLAG_SHOW_SIGN;
+            else if (*fmt == ' ') flags |= PF_FLAG_SPACE_SIGN;
+            else if (*fmt == '!') flags |= PF_FLAG_NO_TRAILZ;
+            else if (*fmt == '0') {
+                flags |= PF_FLAG_PAD_AFTER_SIGN;
+                fill = '0';
+            } else break;
+            ++fmt;
+        }
+
+        // parse width, if it exists
+        int width = 0;
+        for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
+            width = width * 10 + *fmt - '0';
+        }
+
+        // parse precision, if it exists
+        int prec = -1;
+        if (*fmt == '.') {
+            ++fmt;
+            if (*fmt == '*') {
+                ++fmt;
+                prec = va_arg(args, int);
+            } else {
+                prec = 0;
+                for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
+                    prec = prec * 10 + *fmt - '0';
+                }
+            }
+            if (prec < 0) {
+                prec = 0;
+            }
+        }
+
+        // parse long specifiers (current not used)
+        //bool long_arg = false;
+        if (*fmt == 'l') {
+            ++fmt;
+            //long_arg = true;
+        }
+
+        if (*fmt == '\0') {
+            break;
+        }
+
+        switch (*fmt) {
+            case 'b':
+                if (va_arg(args, int)) {
+                    chrs += mp_print_strn(print, "true", 4, flags, fill, width);
+                } else {
+                    chrs += mp_print_strn(print, "false", 5, flags, fill, width);
+                }
+                break;
+            case 'c':
+            {
+                char str = va_arg(args, int);
+                chrs += mp_print_strn(print, &str, 1, flags, fill, width);
+                break;
+            }
+            case 's':
+            {
+                const char *str = va_arg(args, const char*);
+                if (str) {
+                    if (prec < 0) {
+                        prec = strlen(str);
+                    }
+                    chrs += mp_print_strn(print, str, prec, flags, fill, width);
+                } else {
+                    chrs += mp_print_strn(print, "(null)", 6, flags, fill, width);
+                }
+                break;
+            }
+            case 'u':
+                chrs += mp_print_int(print, va_arg(args, int), 0, 10, 'a', flags, fill, width);
+                break;
+            case 'd':
+                chrs += mp_print_int(print, va_arg(args, int), 1, 10, 'a', flags, fill, width);
+                break;
+            case 'x':
+                chrs += mp_print_int(print, va_arg(args, int), 0, 16, 'a', flags, fill, width);
+                break;
+            case 'X':
+                chrs += mp_print_int(print, va_arg(args, int), 0, 16, 'A', flags, fill, width);
+                break;
+            case 'p':
+            case 'P': // don't bother to handle upcase for 'P'
+                chrs += mp_print_int(print, va_arg(args, unsigned int), 0, 16, 'a', flags, fill, width);
+                break;
+#if MICROPY_PY_BUILTINS_FLOAT
+            case 'e':
+            case 'E':
+            case 'f':
+            case 'F':
+            case 'g':
+            case 'G':
+            {
+#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
+                mp_float_t f = va_arg(args, double);
+                chrs += mp_print_float(print, f, *fmt, flags, fill, width, prec);
+#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+                // Currently mp_print_float uses snprintf, but snprintf
+                // itself may be implemented in terms of mp_vprintf() for
+                // some ports. So, for extra caution, this case is handled
+                // with assert below. Note that currently ports which
+                // use MICROPY_FLOAT_IMPL_DOUBLE, don't call mp_vprintf()
+                // with float format specifier at all.
+                // TODO: resolve this completely
+                assert(0);
+//#error Calling mp_print_float with double not supported from within printf
+#else
+#error Unknown MICROPY FLOAT IMPL
+#endif
+                break;
+            }
+#endif
+            default:
+                print->print_strn(print->data, fmt, 1);
+                chrs += 1;
+                break;
+        }
+        ++fmt;
+    }
+    return chrs;
+}
diff --git a/py/pfenv.h b/py/mpprint.h
similarity index 66%
rename from py/pfenv.h
rename to py/mpprint.h
index 3d2b31b5d..60fa18acf 100644
--- a/py/pfenv.h
+++ b/py/mpprint.h
@@ -23,12 +23,10 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#ifndef __MICROPY_INCLUDED_PY_PFENV_H__
-#define __MICROPY_INCLUDED_PY_PFENV_H__
+#ifndef __MICROPY_INCLUDED_PY_MPPRINT_H__
+#define __MICROPY_INCLUDED_PY_MPPRINT_H__
 
-#include <stdarg.h>
-
-#include "py/obj.h"
+#include "py/mpconfig.h"
 
 #define PF_FLAG_LEFT_ADJUST       (0x001)
 #define PF_FLAG_SHOW_SIGN         (0x002)
@@ -42,24 +40,28 @@
 #define PF_FLAG_PAD_NAN_INF       (0x200)
 #define PF_FLAG_SHOW_OCTAL_LETTER (0x400)
 
-typedef struct _pfenv_t {
+typedef void (*mp_print_strn_t)(void *data, const char *str, mp_uint_t len);
+
+typedef struct _mp_print_t {
     void *data;
-    void (*print_strn)(void *, const char *str, mp_uint_t len);
-} pfenv_t;
+    mp_print_strn_t print_strn;
+} mp_print_t;
 
-void pfenv_vstr_add_strn(void *data, const char *str, mp_uint_t len);
+// Wrapper for platform print function, which wraps MP_PLAT_PRINT_STRN.
+// All (non-debug) prints go through this interface (except some which
+// go through mp_sys_stdout_obj if MICROPY_PY_IO is defined).
+extern const mp_print_t mp_plat_print;
 
-int pfenv_print_strn(const pfenv_t *pfenv, const char *str, mp_uint_t len, int flags, char fill, int width);
-int pfenv_print_int(const pfenv_t *pfenv, mp_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width);
-int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec);
+int mp_print_str(const mp_print_t *print, const char *str);
+int mp_print_strn(const mp_print_t *print, const char *str, mp_uint_t len, int flags, char fill, int width);
+int mp_print_int(const mp_print_t *print, mp_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width);
 #if MICROPY_PY_BUILTINS_FLOAT
-int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
+int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
 #endif
 
-int pfenv_vprintf(const pfenv_t *pfenv, const char *fmt, va_list args);
-int pfenv_printf(const pfenv_t *pfenv, const char *fmt, ...);
-
-// Wrapper for system printf
-void printf_wrapper(void *env, const char *fmt, ...);
+int mp_printf(const mp_print_t *print, const char *fmt, ...);
+#ifdef va_start
+int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args);
+#endif
 
-#endif // __MICROPY_INCLUDED_PY_PFENV_H__
+#endif // __MICROPY_INCLUDED_PY_MPPRINT_H__
diff --git a/py/obj.c b/py/obj.c
index 6c851dfb0..0d318fa14 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -36,7 +36,7 @@
 #include "py/runtime0.h"
 #include "py/runtime.h"
 #include "py/stackctrl.h"
-#include "py/pfenv.h"
+//#include "py/pfenv.h"
 #include "py/stream.h" // for mp_obj_print
 
 mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in) {
@@ -54,20 +54,20 @@ const char *mp_obj_get_type_str(mp_const_obj_t o_in) {
     return qstr_str(mp_obj_get_type(o_in)->name);
 }
 
-void mp_obj_print_helper(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     // There can be data structures nested too deep, or just recursive
     MP_STACK_CHECK();
 #ifndef NDEBUG
     if (o_in == NULL) {
-        print(env, "(nil)");
+        mp_print_str(print, "(nil)");
         return;
     }
 #endif
     mp_obj_type_t *type = mp_obj_get_type(o_in);
     if (type->print != NULL) {
-        type->print(print, env, o_in, kind);
+        type->print((mp_print_t*)print, o_in, kind);
     } else {
-        print(env, "<%s>", qstr_str(type->name));
+        mp_printf(print, "<%s>", qstr_str(type->name));
     }
 }
 
@@ -75,41 +75,41 @@ void mp_obj_print(mp_obj_t o_in, mp_print_kind_t kind) {
 #if MICROPY_PY_IO
     // defined per port; type of these is irrelevant, just need pointer
     extern struct _mp_dummy_t mp_sys_stdout_obj;
-    pfenv_t pfenv;
-    pfenv.data = &mp_sys_stdout_obj;
-    pfenv.print_strn = (void (*)(void *, const char *, mp_uint_t))mp_stream_write;
-    mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))pfenv_printf, &pfenv, o_in, kind);
+    mp_print_t print;
+    print.data = &mp_sys_stdout_obj;
+    print.print_strn = (mp_print_strn_t)mp_stream_write;
+    mp_obj_print_helper(&print, o_in, kind);
 #else
-    mp_obj_print_helper(printf_wrapper, NULL, o_in, kind);
+    mp_obj_print_helper(&mp_plat_print, o_in, kind);
 #endif
 }
 
 // helper function to print an exception with traceback
-void mp_obj_print_exception(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t exc) {
+void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc) {
     if (mp_obj_is_exception_instance(exc)) {
         mp_uint_t n, *values;
         mp_obj_exception_get_traceback(exc, &n, &values);
         if (n > 0) {
             assert(n % 3 == 0);
-            print(env, "Traceback (most recent call last):\n");
+            mp_print_str(print, "Traceback (most recent call last):\n");
             for (int i = n - 3; i >= 0; i -= 3) {
 #if MICROPY_ENABLE_SOURCE_LINE
-                print(env, "  File \"%s\", line %d", qstr_str(values[i]), (int)values[i + 1]);
+                mp_printf(print, "  File \"%s\", line %d", qstr_str(values[i]), (int)values[i + 1]);
 #else
-                print(env, "  File \"%s\"", qstr_str(values[i]));
+                mp_printf(print, "  File \"%s\"", qstr_str(values[i]));
 #endif
                 // the block name can be NULL if it's unknown
                 qstr block = values[i + 2];
                 if (block == MP_QSTR_NULL) {
-                    print(env, "\n");
+                    mp_print_str(print, "\n");
                 } else {
-                    print(env, ", in %s\n", qstr_str(block));
+                    mp_printf(print, ", in %s\n", qstr_str(block));
                 }
             }
         }
     }
-    mp_obj_print_helper(print, env, exc, PRINT_EXC);
-    print(env, "\n");
+    mp_obj_print_helper(print, exc, PRINT_EXC);
+    mp_print_str(print, "\n");
 }
 
 bool mp_obj_is_true(mp_obj_t arg) {
diff --git a/py/obj.h b/py/obj.h
index a5423f082..86f560ff5 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -29,6 +29,7 @@
 #include "py/mpconfig.h"
 #include "py/misc.h"
 #include "py/qstr.h"
+#include "py/mpprint.h"
 
 // All Micro Python objects are at least this type
 // It must be of pointer size
@@ -260,7 +261,7 @@ typedef enum {
     PRINT_EXC_SUBCLASS = 0x80, // Internal flag for printing exception subclasses
 } mp_print_kind_t;
 
-typedef void (*mp_print_fun_t)(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o, mp_print_kind_t kind);
+typedef void (*mp_print_fun_t)(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind);
 typedef mp_obj_t (*mp_make_new_fun_t)(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
 typedef mp_obj_t (*mp_call_fun_t)(mp_obj_t fun, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
 typedef mp_obj_t (*mp_unary_op_fun_t)(mp_uint_t op, mp_obj_t);
@@ -499,9 +500,9 @@ const char *mp_obj_get_type_str(mp_const_obj_t o_in);
 bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo); // arguments should be type objects
 mp_obj_t mp_instance_cast_to_native_base(mp_const_obj_t self_in, mp_const_obj_t native_type);
 
-void mp_obj_print_helper(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind);
+void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind);
 void mp_obj_print(mp_obj_t o, mp_print_kind_t kind);
-void mp_obj_print_exception(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t exc);
+void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc);
 
 bool mp_obj_is_true(mp_obj_t arg);
 bool mp_obj_is_callable(mp_obj_t o_in);
@@ -561,7 +562,7 @@ qstr mp_obj_str_get_qstr(mp_obj_t self_in); // use this if you will anyway conve
 const char *mp_obj_str_get_str(mp_obj_t self_in); // use this only if you need the string to be null terminated
 const char *mp_obj_str_get_data(mp_obj_t self_in, mp_uint_t *len);
 mp_obj_t mp_obj_str_intern(mp_obj_t str);
-void mp_str_print_quoted(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, mp_uint_t str_len, bool is_bytes);
+void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, mp_uint_t str_len, bool is_bytes);
 
 #if MICROPY_PY_BUILTINS_FLOAT
 // float
diff --git a/py/objarray.c b/py/objarray.c
index cb2b2b6a1..33cd7ecc3 100644
--- a/py/objarray.c
+++ b/py/objarray.c
@@ -76,26 +76,26 @@ STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_ui
 // array
 
 #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY
-STATIC void array_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+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 = o_in;
     if (o->typecode == BYTEARRAY_TYPECODE) {
-        print(env, "bytearray(b");
-        mp_str_print_quoted(print, env, o->items, o->len, true);
+        mp_print_str(print, "bytearray(b");
+        mp_str_print_quoted(print, o->items, o->len, true);
     } else {
-        print(env, "array('%c'", o->typecode);
+        mp_printf(print, "array('%c'", o->typecode);
         if (o->len > 0) {
-            print(env, ", [");
+            mp_print_str(print, ", [");
             for (mp_uint_t i = 0; i < o->len; i++) {
                 if (i > 0) {
-                    print(env, ", ");
+                    mp_print_str(print, ", ");
                 }
-                mp_obj_print_helper(print, env, mp_binary_get_val_array(o->typecode, o->items, i), PRINT_REPR);
+                mp_obj_print_helper(print, mp_binary_get_val_array(o->typecode, o->items, i), PRINT_REPR);
             }
-            print(env, "]");
+            mp_print_str(print, "]");
         }
     }
-    print(env, ")");
+    mp_print_str(print, ")");
 }
 #endif
 
diff --git a/py/objbool.c b/py/objbool.c
index d60dc8c0b..478491bfc 100644
--- a/py/objbool.c
+++ b/py/objbool.c
@@ -35,19 +35,19 @@ typedef struct _mp_obj_bool_t {
     bool value;
 } mp_obj_bool_t;
 
-STATIC void bool_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void bool_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     mp_obj_bool_t *self = self_in;
     if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
         if (self->value) {
-            print(env, "true");
+            mp_print_str(print, "true");
         } else {
-            print(env, "false");
+            mp_print_str(print, "false");
         }
     } else {
         if (self->value) {
-            print(env, "True");
+            mp_print_str(print, "True");
         } else {
-            print(env, "False");
+            mp_print_str(print, "False");
         }
     }
 }
diff --git a/py/objboundmeth.c b/py/objboundmeth.c
index 0f9ff08c8..b96159235 100644
--- a/py/objboundmeth.c
+++ b/py/objboundmeth.c
@@ -36,14 +36,14 @@ typedef struct _mp_obj_bound_meth_t {
 } mp_obj_bound_meth_t;
 
 #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED
-STATIC void bound_meth_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void bound_meth_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_bound_meth_t *o = o_in;
-    print(env, "<bound_method %p ", o);
-    mp_obj_print_helper(print, env, o->self, PRINT_REPR);
-    print(env, ".");
-    mp_obj_print_helper(print, env, o->meth, PRINT_REPR);
-    print(env, ">");
+    mp_printf(print, "<bound_method %p ", o);
+    mp_obj_print_helper(print, o->self, PRINT_REPR);
+    mp_print_str(print, ".");
+    mp_obj_print_helper(print, o->meth, PRINT_REPR);
+    mp_print_str(print, ">");
 }
 #endif
 
diff --git a/py/objcell.c b/py/objcell.c
index 9adbc16ae..97f1ecd63 100644
--- a/py/objcell.c
+++ b/py/objcell.c
@@ -42,16 +42,16 @@ void mp_obj_cell_set(mp_obj_t self_in, mp_obj_t obj) {
 }
 
 #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED
-STATIC void cell_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void cell_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_cell_t *o = o_in;
-    print(env, "<cell %p ", o->obj);
+    mp_printf(print, "<cell %p ", o->obj);
     if (o->obj == MP_OBJ_NULL) {
-        print(env, "(nil)");
+        mp_print_str(print, "(nil)");
     } else {
-        mp_obj_print_helper(print, env, o->obj, PRINT_REPR);
+        mp_obj_print_helper(print, o->obj, PRINT_REPR);
     }
-    print(env, ">");
+    mp_print_str(print, ">");
 }
 #endif
 
diff --git a/py/objclosure.c b/py/objclosure.c
index 95c1adde0..a4000e5dd 100644
--- a/py/objclosure.c
+++ b/py/objclosure.c
@@ -60,21 +60,21 @@ STATIC mp_obj_t closure_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw,
 }
 
 #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED
-STATIC void closure_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void closure_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_closure_t *o = o_in;
-    print(env, "<closure ");
-    mp_obj_print_helper(print, env, o->fun, PRINT_REPR);
-    print(env, " at %p, n_closed=%u ", o, o->n_closed);
+    mp_print_str(print, "<closure ");
+    mp_obj_print_helper(print, o->fun, PRINT_REPR);
+    mp_printf(print, " at %p, n_closed=%u ", o, o->n_closed);
     for (mp_uint_t i = 0; i < o->n_closed; i++) {
         if (o->closed[i] == MP_OBJ_NULL) {
-            print(env, "(nil)");
+            mp_print_str(print, "(nil)");
         } else {
-            mp_obj_print_helper(print, env, o->closed[i], PRINT_REPR);
+            mp_obj_print_helper(print, o->closed[i], PRINT_REPR);
         }
-        print(env, " ");
+        mp_print_str(print, " ");
     }
-    print(env, ">");
+    mp_print_str(print, ">");
 }
 #endif
 
diff --git a/py/objcomplex.c b/py/objcomplex.c
index f4a68851a..12f10ec10 100644
--- a/py/objcomplex.c
+++ b/py/objcomplex.c
@@ -48,36 +48,36 @@ typedef struct _mp_obj_complex_t {
     mp_float_t imag;
 } mp_obj_complex_t;
 
-STATIC void complex_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void complex_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_complex_t *o = o_in;
 #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
     char buf[16];
     if (o->real == 0) {
         mp_format_float(o->imag, buf, sizeof(buf), 'g', 7, '\0');
-        print(env, "%sj", buf);
+        mp_printf(print, "%sj", buf);
     } else {
         mp_format_float(o->real, buf, sizeof(buf), 'g', 7, '\0');
-        print(env, "(%s", buf);
+        mp_printf(print, "(%s", buf);
         if (o->imag >= 0) {
-            print(env, "+");
+            mp_print_str(print, "+");
         }
         mp_format_float(o->imag, buf, sizeof(buf), 'g', 7, '\0');
-        print(env, "%sj)", buf);
+        mp_printf(print, "%sj)", buf);
     }
 #else
     char buf[32];
     if (o->real == 0) {
         sprintf(buf, "%.16g", (double)o->imag);
-        print(env, "%sj", buf);
+        mp_printf(print, "%sj", buf);
     } else {
         sprintf(buf, "%.16g", (double)o->real);
-        print(env, "(%s", buf);
+        mp_printf(print, "(%s", buf);
         if (o->imag >= 0) {
-            print(env, "+");
+            mp_print_str(print, "+");
         }
         sprintf(buf, "%.16g", (double)o->imag);
-        print(env, "%sj)", buf);
+        mp_printf(print, "%sj)", buf);
     }
 #endif
 }
diff --git a/py/objdict.c b/py/objdict.c
index 3e255ec88..23024d549 100644
--- a/py/objdict.c
+++ b/py/objdict.c
@@ -55,30 +55,30 @@ STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, mp_uint_t *cur) {
     return NULL;
 }
 
-STATIC void dict_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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_CAST(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) {
-        print(env, "%s(", qstr_str(self->base.type->name));
+        mp_printf(print, "%s(", qstr_str(self->base.type->name));
     }
-    print(env, "{");
+    mp_print_str(print, "{");
     mp_uint_t cur = 0;
     mp_map_elem_t *next = NULL;
     while ((next = dict_iter_next(self, &cur)) != NULL) {
         if (!first) {
-            print(env, ", ");
+            mp_print_str(print, ", ");
         }
         first = false;
-        mp_obj_print_helper(print, env, next->key, kind);
-        print(env, ": ");
-        mp_obj_print_helper(print, env, next->value, kind);
+        mp_obj_print_helper(print, next->key, kind);
+        mp_print_str(print, ": ");
+        mp_obj_print_helper(print, next->value, kind);
     }
-    print(env, "}");
+    mp_print_str(print, "}");
     if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict) {
-        print(env, ")");
+        mp_print_str(print, ")");
     }
 }
 
@@ -473,23 +473,23 @@ STATIC mp_obj_t dict_view_getiter(mp_obj_t view_in) {
     return o_out;
 }
 
-STATIC void dict_view_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void dict_view_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     assert(MP_OBJ_IS_TYPE(self_in, &dict_view_type));
     mp_obj_dict_view_t *self = MP_OBJ_CAST(self_in);
     bool first = true;
-    print(env, mp_dict_view_names[self->kind]);
-    print(env, "([");
+    mp_print_str(print, mp_dict_view_names[self->kind]);
+    mp_print_str(print, "([");
     mp_obj_t self_iter = dict_view_getiter(self_in);
     mp_obj_t next = MP_OBJ_NULL;
     while ((next = dict_view_it_iternext(self_iter)) != MP_OBJ_STOP_ITERATION) {
         if (!first) {
-            print(env, ", ");
+            mp_print_str(print, ", ");
         }
         first = false;
-        mp_obj_print_helper(print, env, next, PRINT_REPR);
+        mp_obj_print_helper(print, next, PRINT_REPR);
     }
-    print(env, "])");
+    mp_print_str(print, "])");
 }
 
 STATIC mp_obj_t dict_view_binary_op(mp_uint_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
diff --git a/py/objexcept.c b/py/objexcept.c
index 2d23f160a..8a008d1ff 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -90,28 +90,28 @@ mp_obj_t mp_alloc_emergency_exception_buf(mp_obj_t size_in) {
 // definition module-private so far, have it here.
 const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, 0, 0, MP_OBJ_NULL, mp_const_empty_tuple};
 
-STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     mp_obj_exception_t *o = o_in;
     mp_print_kind_t k = kind & ~PRINT_EXC_SUBCLASS;
     bool is_subclass = kind & PRINT_EXC_SUBCLASS;
     if (!is_subclass && (k == PRINT_REPR || k == PRINT_EXC)) {
-        print(env, "%s", qstr_str(o->base.type->name));
+        mp_printf(print, "%s", qstr_str(o->base.type->name));
     }
 
     if (k == PRINT_EXC) {
-        print(env, ": ");
+        mp_print_str(print, ": ");
     }
 
     if (k == PRINT_STR || k == PRINT_EXC) {
         if (o->args == NULL || o->args->len == 0) {
-            print(env, "");
+            mp_print_str(print, "");
             return;
         } else if (o->args->len == 1) {
-            mp_obj_print_helper(print, env, o->args->items[0], PRINT_STR);
+            mp_obj_print_helper(print, o->args->items[0], PRINT_STR);
             return;
         }
     }
-    mp_obj_tuple_print(print, env, o->args, kind);
+    mp_obj_tuple_print(print, o->args, kind);
 }
 
 mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
diff --git a/py/objfloat.c b/py/objfloat.c
index b94caf3c0..eb59cc5b7 100644
--- a/py/objfloat.c
+++ b/py/objfloat.c
@@ -42,24 +42,24 @@
 #include "py/formatfloat.h"
 #endif
 
-STATIC void float_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_float_t *o = o_in;
 #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
     char buf[16];
     mp_format_float(o->value, buf, sizeof(buf), 'g', 7, '\0');
-    print(env, "%s", buf);
+    mp_print_str(print, buf);
     if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL) {
         // Python floats always have decimal point
-        print(env, ".0");
+        mp_print_str(print, ".0");
     }
 #else
     char buf[32];
     sprintf(buf, "%.16g", (double) o->value);
-    print(env, buf);
+    mp_print_str(print, buf);
     if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL) {
         // Python floats always have decimal point
-        print(env, ".0");
+        mp_print_str(print, ".0");
     }
 #endif
 }
diff --git a/py/objfun.c b/py/objfun.c
index f00a90a08..8b9d3f553 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -125,10 +125,10 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) {
 }
 
 #if MICROPY_CPYTHON_COMPAT
-STATIC void fun_bc_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+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 = o_in;
-    print(env, "<function %s at 0x%x>", qstr_str(mp_obj_fun_get_name(o)), o);
+    mp_printf(print, "<function %s at 0x%x>", qstr_str(mp_obj_fun_get_name(o)), o);
 }
 #endif
 
diff --git a/py/objgenerator.c b/py/objgenerator.c
index f672b8d99..ed4e1fd15 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -95,10 +95,10 @@ mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun) {
 /******************************************************************************/
 /* generator instance                                                         */
 
-STATIC void gen_instance_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_gen_instance_t *self = self_in;
-    print(env, "<generator object '%s' at %p>", qstr_str(mp_obj_code_get_name(self->code_state.code_info)), self_in);
+    mp_printf(print, "<generator object '%s' at %p>", qstr_str(mp_obj_code_get_name(self->code_state.code_info)), self_in);
 }
 
 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) {
diff --git a/py/objint.c b/py/objint.c
index 26e1cc048..64faed636 100644
--- a/py/objint.c
+++ b/py/objint.c
@@ -124,7 +124,7 @@ mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) {
 #undef MP_FLOAT_EXP_SHIFT_I32
 #endif
 
-void mp_obj_int_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void mp_obj_int_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     // The size of this buffer is rather arbitrary. If it's not large
     // enough, a dynamic one will be allocated.
@@ -134,7 +134,7 @@ void mp_obj_int_print(void (*print)(void *env, const char *fmt, ...), void *env,
     mp_uint_t fmt_size;
 
     char *str = mp_obj_int_formatted(&buf, &buf_size, &fmt_size, self_in, 10, NULL, '\0', '\0');
-    print(env, "%s", str);
+    mp_print_str(print, str);
 
     if (buf != stack_buf) {
         m_del(char, buf, buf_size);
diff --git a/py/objint.h b/py/objint.h
index 63cd91a69..daeb3c499 100644
--- a/py/objint.h
+++ b/py/objint.h
@@ -50,7 +50,7 @@ typedef enum {
 mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val);
 #endif // MICROPY_PY_BUILTINS_FLOAT
 
-void mp_obj_int_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind);
+void mp_obj_int_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
 char *mp_obj_int_formatted(char **buf, mp_uint_t *buf_size, mp_uint_t *fmt_size, mp_const_obj_t self_in,
                            int base, const char *prefix, char base_char, char comma);
 char *mp_obj_int_formatted_impl(char **buf, mp_uint_t *buf_size, mp_uint_t *fmt_size, mp_const_obj_t self_in,
diff --git a/py/objlist.c b/py/objlist.c
index e1343d9b3..940403212 100644
--- a/py/objlist.c
+++ b/py/objlist.c
@@ -44,19 +44,19 @@ STATIC mp_obj_t list_pop(mp_uint_t n_args, const mp_obj_t *args);
 /******************************************************************************/
 /* list                                                                       */
 
-STATIC void list_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+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_CAST(o_in);
     if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) {
         kind = PRINT_REPR;
     }
-    print(env, "[");
+    mp_print_str(print, "[");
     for (mp_uint_t i = 0; i < o->len; i++) {
         if (i > 0) {
-            print(env, ", ");
+            mp_print_str(print, ", ");
         }
-        mp_obj_print_helper(print, env, o->items[i], kind);
+        mp_obj_print_helper(print, o->items[i], kind);
     }
-    print(env, "]");
+    mp_print_str(print, "]");
 }
 
 STATIC mp_obj_t list_extend_from_iter(mp_obj_t list, mp_obj_t iterable) {
diff --git a/py/objmodule.c b/py/objmodule.c
index 971c7f38c..84d97f494 100644
--- a/py/objmodule.c
+++ b/py/objmodule.c
@@ -33,7 +33,7 @@
 #include "py/runtime.h"
 #include "py/builtin.h"
 
-STATIC void module_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_module_t *self = self_in;
     const char *name = qstr_str(self->name);
@@ -43,12 +43,12 @@ STATIC void module_print(void (*print)(void *env, const char *fmt, ...), void *e
     // symbol to give more information about the module.
     mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_MAP_LOOKUP);
     if (elem != NULL) {
-        print(env, "<module '%s' from '%s'>", name, mp_obj_str_get_str(elem->value));
+        mp_printf(print, "<module '%s' from '%s'>", name, mp_obj_str_get_str(elem->value));
         return;
     }
 #endif
 
-    print(env, "<module '%s'>", name);
+    mp_printf(print, "<module '%s'>", name);
 }
 
 STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c
index 9cc6da1b7..ef635a7a9 100644
--- a/py/objnamedtuple.c
+++ b/py/objnamedtuple.c
@@ -53,19 +53,19 @@ STATIC mp_uint_t namedtuple_find_field(mp_obj_namedtuple_type_t *type, qstr name
     return -1;
 }
 
-STATIC void namedtuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void namedtuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_namedtuple_t *o = o_in;
-    print(env, "%s(", qstr_str(o->tuple.base.type->name));
+    mp_printf(print, "%s(", qstr_str(o->tuple.base.type->name));
     const qstr *fields = ((mp_obj_namedtuple_type_t*)o->tuple.base.type)->fields;
     for (mp_uint_t i = 0; i < o->tuple.len; i++) {
         if (i > 0) {
-            print(env, ", ");
+            mp_print_str(print, ", ");
         }
-        print(env, "%s=", qstr_str(fields[i]));
-        mp_obj_print_helper(print, env, o->tuple.items[i], PRINT_REPR);
+        mp_printf(print, "%s=", qstr_str(fields[i]));
+        mp_obj_print_helper(print, o->tuple.items[i], PRINT_REPR);
     }
-    print(env, ")");
+    mp_print_str(print, ")");
 }
 
 STATIC void namedtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
diff --git a/py/objnone.c b/py/objnone.c
index 523158d00..d47452bb4 100644
--- a/py/objnone.c
+++ b/py/objnone.c
@@ -34,12 +34,12 @@ typedef struct _mp_obj_none_t {
     mp_obj_base_t base;
 } mp_obj_none_t;
 
-STATIC void none_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void none_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)self_in;
     if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
-        print(env, "null");
+        mp_print_str(print, "null");
     } else {
-        print(env, "None");
+        mp_print_str(print, "None");
     }
 }
 
diff --git a/py/objrange.c b/py/objrange.c
index 9f7e6591e..58d90a75e 100644
--- a/py/objrange.c
+++ b/py/objrange.c
@@ -79,14 +79,14 @@ typedef struct _mp_obj_range_t {
     mp_int_t step;
 } mp_obj_range_t;
 
-STATIC void range_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void range_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_range_t *self = self_in;
-    print(env, "range(%d, %d", self->start, self->stop);
+    mp_printf(print, "range(%d, %d", self->start, self->stop);
     if (self->step == 1) {
-        print(env, ")");
+        mp_print_str(print, ")");
     } else {
-        print(env, ", %d)", self->step);
+        mp_printf(print, ", %d)", self->step);
     }
 }
 
diff --git a/py/objset.c b/py/objset.c
index c4e59280e..6e303fe4b 100644
--- a/py/objset.c
+++ b/py/objset.c
@@ -79,7 +79,7 @@ STATIC void check_set(mp_obj_t o) {
     }
 }
 
-STATIC void set_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void set_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_set_t *self = self_in;
     #if MICROPY_PY_BUILTINS_FROZENSET
@@ -88,37 +88,36 @@ STATIC void set_print(void (*print)(void *env, const char *fmt, ...), void *env,
     if (self->set.used == 0) {
         #if MICROPY_PY_BUILTINS_FROZENSET
         if (is_frozen) {
-            print(env, "frozen");
+            mp_print_str(print, "frozen");
         }
         #endif
-        print(env, "set()");
+        mp_print_str(print, "set()");
         return;
     }
     bool first = true;
     #if MICROPY_PY_BUILTINS_FROZENSET
     if (is_frozen) {
-        print(env, "frozenset(");
+        mp_print_str(print, "frozenset(");
     }
     #endif
-    print(env, "{");
+    mp_print_str(print, "{");
     for (mp_uint_t i = 0; i < self->set.alloc; i++) {
         if (MP_SET_SLOT_IS_FILLED(&self->set, i)) {
             if (!first) {
-                print(env, ", ");
+                mp_print_str(print, ", ");
             }
             first = false;
-            mp_obj_print_helper(print, env, self->set.table[i], PRINT_REPR);
+            mp_obj_print_helper(print, self->set.table[i], PRINT_REPR);
         }
     }
-    print(env, "}");
+    mp_print_str(print, "}");
     #if MICROPY_PY_BUILTINS_FROZENSET
     if (is_frozen) {
-        print(env, ")");
+        mp_print_str(print, ")");
     }
     #endif
 }
 
-
 STATIC mp_obj_t set_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
     mp_arg_check_num(n_args, n_kw, 0, 1, false);
 
diff --git a/py/objslice.c b/py/objslice.c
index 4f3d054c6..7983864b2 100644
--- a/py/objslice.c
+++ b/py/objslice.c
@@ -38,10 +38,10 @@ typedef struct _mp_obj_ellipsis_t {
     mp_obj_base_t base;
 } mp_obj_ellipsis_t;
 
-STATIC void ellipsis_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void ellipsis_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     (void)self_in;
     (void)kind;
-    print(env, "Ellipsis");
+    mp_print_str(print, "Ellipsis");
 }
 
 const mp_obj_type_t mp_type_ellipsis = {
@@ -66,16 +66,16 @@ typedef struct _mp_obj_slice_t {
     mp_obj_t step;
 } mp_obj_slice_t;
 
-STATIC void slice_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void slice_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     (void)kind;
     mp_obj_slice_t *o = o_in;
-    print(env, "slice(");
-    mp_obj_print_helper(print, env, o->start, PRINT_REPR);
-    print(env, ", ");
-    mp_obj_print_helper(print, env, o->stop, PRINT_REPR);
-    print(env, ", ");
-    mp_obj_print_helper(print, env, o->step, PRINT_REPR);
-    print(env, ")");
+    mp_print_str(print, "slice(");
+    mp_obj_print_helper(print, o->start, PRINT_REPR);
+    mp_print_str(print, ", ");
+    mp_obj_print_helper(print, o->stop, PRINT_REPR);
+    mp_print_str(print, ", ");
+    mp_obj_print_helper(print, o->step, PRINT_REPR);
+    mp_print_str(print, ")");
 }
 
 const mp_obj_type_t mp_type_slice = {
diff --git a/py/objstr.c b/py/objstr.c
index e35fc2976..50fe10378 100644
--- a/py/objstr.c
+++ b/py/objstr.c
@@ -34,7 +34,6 @@
 #include "py/objlist.h"
 #include "py/runtime0.h"
 #include "py/runtime.h"
-#include "py/pfenv.h"
 
 STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, mp_uint_t n_args, const mp_obj_t *args, mp_obj_t dict);
 
@@ -45,8 +44,7 @@ STATIC NORETURN void bad_implicit_conversion(mp_obj_t self_in);
 /******************************************************************************/
 /* str                                                                        */
 
-void mp_str_print_quoted(void (*print)(void *env, const char *fmt, ...), void *env,
-                         const byte *str_data, mp_uint_t str_len, bool is_bytes) {
+void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, mp_uint_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;
@@ -61,72 +59,72 @@ void mp_str_print_quoted(void (*print)(void *env, const char *fmt, ...), void *e
     if (has_single_quote && !has_double_quote) {
         quote_char = '"';
     }
-    print(env, "%c", 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) {
-            print(env, "\\%c", quote_char);
+            mp_printf(print, "\\%c", quote_char);
         } else if (*s == '\\') {
-            print(env, "\\\\");
+            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.)
-            print(env, "%c", *s);
+            mp_printf(print, "%c", *s);
         } else if (*s == '\n') {
-            print(env, "\\n");
+            mp_print_str(print, "\\n");
         } else if (*s == '\r') {
-            print(env, "\\r");
+            mp_print_str(print, "\\r");
         } else if (*s == '\t') {
-            print(env, "\\t");
+            mp_print_str(print, "\\t");
         } else {
-            print(env, "\\x%02x", *s);
+            mp_printf(print, "\\x%02x", *s);
         }
     }
-    print(env, "%c", quote_char);
+    mp_printf(print, "%c", quote_char);
 }
 
 #if MICROPY_PY_UJSON
-void mp_str_print_json(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, mp_uint_t str_len) {
+void mp_str_print_json(const mp_print_t *print, const byte *str_data, mp_uint_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
-    print(env, "\"");
+    mp_print_str(print, "\"");
     for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) {
         if (*s == '"' || *s == '\\') {
-            print(env, "\\%c", *s);
+            mp_printf(print, "\\%c", *s);
         } else if (*s >= 32) {
             // this will handle normal and utf-8 encoded chars
-            print(env, "%c", *s);
+            mp_printf(print, "%c", *s);
         } else if (*s == '\n') {
-            print(env, "\\n");
+            mp_print_str(print, "\\n");
         } else if (*s == '\r') {
-            print(env, "\\r");
+            mp_print_str(print, "\\r");
         } else if (*s == '\t') {
-            print(env, "\\t");
+            mp_print_str(print, "\\t");
         } else {
             // this will handle control chars
-            print(env, "\\u%04x", *s);
+            mp_printf(print, "\\u%04x", *s);
         }
     }
-    print(env, "\"");
+    mp_print_str(print, "\"");
 }
 #endif
 
-STATIC void str_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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, env, str_data, str_len);
+        mp_str_print_json(print, str_data, str_len);
         return;
     }
     #endif
     bool is_bytes = MP_OBJ_IS_TYPE(self_in, &mp_type_bytes);
     if (kind == PRINT_STR && !is_bytes) {
-        print(env, "%.*s", str_len, str_data);
+        mp_printf(print, "%.*s", str_len, str_data);
     } else {
         if (is_bytes) {
-            print(env, "b");
+            mp_print_str(print, "b");
         }
-        mp_str_print_quoted(print, env, str_data, str_len, is_bytes);
+        mp_str_print_quoted(print, str_data, str_len, is_bytes);
     }
 }
 
@@ -145,8 +143,9 @@ mp_obj_t mp_obj_str_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw,
 
         case 1: {
             vstr_t vstr;
-            vstr_init(&vstr, 16);
-            mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, &vstr, args[0], PRINT_STR);
+            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_in, &vstr);
         }
 
@@ -840,10 +839,8 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
     GET_STR_DATA_LEN(args[0], str, len);
     int arg_i = 0;
     vstr_t vstr;
-    vstr_init(&vstr, 16);
-    pfenv_t pfenv_vstr;
-    pfenv_vstr.data = &vstr;
-    pfenv_vstr.print_strn = pfenv_vstr_add_strn;
+    mp_print_t print;
+    vstr_init_print(&vstr, 16, &print);
 
     for (const byte *top = str + len; str < top; str++) {
         if (*str == '}') {
@@ -998,8 +995,9 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
                 }
             }
             vstr_t arg_vstr;
-            vstr_init(&arg_vstr, 16);
-            mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, &arg_vstr, arg, print_kind);
+            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);
         }
 
@@ -1108,20 +1106,20 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
         if (arg_looks_integer(arg)) {
             switch (type) {
                 case 'b':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 2, 'a', flags, fill, width, 0);
+                    mp_print_mp_int(&print, arg, 2, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'c':
                 {
                     char ch = mp_obj_get_int(arg);
-                    pfenv_print_strn(&pfenv_vstr, &ch, 1, flags, fill, width);
+                    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':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 10, 'a', flags, fill, width, 0);
+                    mp_print_mp_int(&print, arg, 10, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'o':
@@ -1129,12 +1127,12 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
                         flags |= PF_FLAG_SHOW_OCTAL_LETTER;
                     }
 
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 8, 'a', flags, fill, width, 0);
+                    mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'X':
                 case 'x':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 16, type - ('X' - 'A'), flags, fill, width, 0);
+                    mp_print_mp_int(&print, arg, 16, type - ('X' - 'A'), flags, fill, width, 0);
                     continue;
 
                 case 'e':
@@ -1206,7 +1204,7 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
                 case 'F':
                 case 'g':
                 case 'G':
-                    pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg), type, flags, fill, width, precision);
+                    mp_print_float(&print, mp_obj_get_float(arg), type, flags, fill, width, precision);
                     break;
 
                 case '%':
@@ -1216,7 +1214,7 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
                     #else
                     #define F100 100.0
                     #endif
-                    pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg) * F100, 'f', flags, fill, width, precision);
+                    mp_print_float(&print, mp_obj_get_float(arg) * F100, 'f', flags, fill, width, precision);
                     #undef F100
                     break;
 #endif
@@ -1244,7 +1242,7 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
 
             switch (type) {
                 case '\0':
-                    mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, &vstr, arg, PRINT_STR);
+                    mp_obj_print_helper(&print, arg, PRINT_STR);
                     break;
 
                 case 's': {
@@ -1256,7 +1254,7 @@ mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwa
                     if (slen > (mp_uint_t)precision) {
                         slen = precision;
                     }
-                    pfenv_print_strn(&pfenv_vstr, s, slen, flags, fill, width);
+                    mp_print_strn(&print, s, slen, flags, fill, width);
                     break;
                 }
 
@@ -1282,10 +1280,8 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, mp_uint_t n_args, const mp_o
     const byte *start_str = str;
     int arg_i = 0;
     vstr_t vstr;
-    vstr_init(&vstr, 16);
-    pfenv_t pfenv_vstr;
-    pfenv_vstr.data = &vstr;
-    pfenv_vstr.print_strn = pfenv_vstr_add_strn;
+    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;
@@ -1389,10 +1385,10 @@ not_enough_args:
                         nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError,
                             "%%c requires int or char"));
                     }
-                    pfenv_print_strn(&pfenv_vstr, s, 1, flags, ' ', width);
+                    mp_print_strn(&print, s, 1, flags, ' ', width);
                 } else if (arg_looks_integer(arg)) {
                     char ch = mp_obj_get_int(arg);
-                    pfenv_print_strn(&pfenv_vstr, &ch, 1, flags, ' ', width);
+                    mp_print_strn(&print, &ch, 1, flags, ' ', width);
                 } else {
                     nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError,
                         "integer required"));
@@ -1402,7 +1398,7 @@ not_enough_args:
             case 'd':
             case 'i':
             case 'u':
-                pfenv_print_mp_int(&pfenv_vstr, arg_as_int(arg), 10, 'a', flags, fill, width, prec);
+                mp_print_mp_int(&print, arg_as_int(arg), 10, 'a', flags, fill, width, prec);
                 break;
 
 #if MICROPY_PY_BUILTINS_FLOAT
@@ -1412,7 +1408,7 @@ not_enough_args:
             case 'F':
             case 'g':
             case 'G':
-                pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg), *str, flags, fill, width, prec);
+                mp_print_float(&print, mp_obj_get_float(arg), *str, flags, fill, width, prec);
                 break;
 #endif
 
@@ -1420,16 +1416,16 @@ not_enough_args:
                 if (alt) {
                     flags |= (PF_FLAG_SHOW_PREFIX | PF_FLAG_SHOW_OCTAL_LETTER);
                 }
-                pfenv_print_mp_int(&pfenv_vstr, arg, 8, 'a', flags, fill, width, prec);
+                mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, prec);
                 break;
 
             case 'r':
             case 's':
             {
                 vstr_t arg_vstr;
-                vstr_init(&arg_vstr, 16);
-                mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf,
-                                    &arg_vstr, arg, *str == 'r' ? PRINT_REPR : PRINT_STR);
+                mp_print_t arg_print;
+                vstr_init_print(&arg_vstr, 16, &arg_print);
+                mp_obj_print_helper(&arg_print, arg, *str == 'r' ? PRINT_REPR : PRINT_STR);
                 uint vlen = arg_vstr.len;
                 if (prec < 0) {
                     prec = vlen;
@@ -1437,14 +1433,14 @@ not_enough_args:
                 if (vlen > (uint)prec) {
                     vlen = prec;
                 }
-                pfenv_print_strn(&pfenv_vstr, arg_vstr.buf, vlen, flags, ' ', width);
+                mp_print_strn(&print, arg_vstr.buf, vlen, flags, ' ', width);
                 vstr_clear(&arg_vstr);
                 break;
             }
 
             case 'X':
             case 'x':
-                pfenv_print_mp_int(&pfenv_vstr, arg, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec);
+                mp_print_mp_int(&print, arg, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec);
                 break;
 
             default:
diff --git a/py/objstr.h b/py/objstr.h
index 291f49a09..9c00c914b 100644
--- a/py/objstr.h
+++ b/py/objstr.h
@@ -55,7 +55,7 @@ typedef struct _mp_obj_str_t {
     else { str_len = ((mp_obj_str_t*)str_obj_in)->len; str_data = ((mp_obj_str_t*)str_obj_in)->data; }
 
 mp_obj_t mp_obj_str_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
-void mp_str_print_json(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, mp_uint_t str_len);
+void mp_str_print_json(const mp_print_t *print, const byte *str_data, mp_uint_t str_len);
 mp_obj_t mp_obj_str_format(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kwargs);
 mp_obj_t mp_obj_str_split(mp_uint_t n_args, const mp_obj_t *args);
 mp_obj_t mp_obj_new_str_of_type(const mp_obj_type_t *type, const byte* data, mp_uint_t len);
diff --git a/py/objstringio.c b/py/objstringio.c
index c7e8395b4..1823ed398 100644
--- a/py/objstringio.c
+++ b/py/objstringio.c
@@ -52,10 +52,10 @@ STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) {
 #define check_stringio_is_open(o)
 #endif
 
-STATIC void stringio_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, self->base.type == &mp_type_stringio ? "<io.StringIO 0x%x>" : "<io.BytesIO 0x%x>", self);
+    mp_printf(print, self->base.type == &mp_type_stringio ? "<io.StringIO 0x%x>" : "<io.BytesIO 0x%x>", self);
 }
 
 STATIC mp_uint_t stringio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) {
diff --git a/py/objstrunicode.c b/py/objstrunicode.c
index 406eaad49..63ca6a8bf 100644
--- a/py/objstrunicode.c
+++ b/py/objstrunicode.c
@@ -33,7 +33,6 @@
 #include "py/objlist.h"
 #include "py/runtime0.h"
 #include "py/runtime.h"
-#include "py/pfenv.h"
 
 #if MICROPY_PY_BUILTINS_STR_UNICODE
 
@@ -42,7 +41,7 @@ STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str);
 /******************************************************************************/
 /* str                                                                        */
 
-STATIC void uni_print_quoted(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, uint str_len) {
+STATIC void uni_print_quoted(const mp_print_t *print, const byte *str_data, uint str_len) {
     // this escapes characters, but it will be very slow to print (calling print many times)
     bool has_single_quote = false;
     bool has_double_quote = false;
@@ -57,47 +56,47 @@ STATIC void uni_print_quoted(void (*print)(void *env, const char *fmt, ...), voi
     if (has_single_quote && !has_double_quote) {
         quote_char = '"';
     }
-    print(env, "%c", quote_char);
+    mp_printf(print, "%c", quote_char);
     const byte *s = str_data, *top = str_data + str_len;
     while (s < top) {
         unichar ch;
         ch = utf8_get_char(s);
         s = utf8_next_char(s);
         if (ch == quote_char) {
-            print(env, "\\%c", quote_char);
+            mp_printf(print, "\\%c", quote_char);
         } else if (ch == '\\') {
-            print(env, "\\\\");
+            mp_print_str(print, "\\\\");
         } else if (32 <= ch && ch <= 126) {
-            print(env, "%c", ch);
+            mp_printf(print, "%c", ch);
         } else if (ch == '\n') {
-            print(env, "\\n");
+            mp_print_str(print, "\\n");
         } else if (ch == '\r') {
-            print(env, "\\r");
+            mp_print_str(print, "\\r");
         } else if (ch == '\t') {
-            print(env, "\\t");
+            mp_print_str(print, "\\t");
         } else if (ch < 0x100) {
-            print(env, "\\x%02x", ch);
+            mp_printf(print, "\\x%02x", ch);
         } else if (ch < 0x10000) {
-            print(env, "\\u%04x", ch);
+            mp_printf(print, "\\u%04x", ch);
         } else {
-            print(env, "\\U%08x", ch);
+            mp_printf(print, "\\U%08x", ch);
         }
     }
-    print(env, "%c", quote_char);
+    mp_printf(print, "%c", quote_char);
 }
 
-STATIC void uni_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void uni_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     GET_STR_DATA_LEN(self_in, str_data, str_len);
     #if MICROPY_PY_UJSON
     if (kind == PRINT_JSON) {
-        mp_str_print_json(print, env, str_data, str_len);
+        mp_str_print_json(print, str_data, str_len);
         return;
     }
     #endif
     if (kind == PRINT_STR) {
-        print(env, "%.*s", str_len, str_data);
+        mp_printf(print, "%.*s", str_len, str_data);
     } else {
-        uni_print_quoted(print, env, str_data, str_len);
+        uni_print_quoted(print, str_data, str_len);
     }
 }
 
diff --git a/py/objtuple.c b/py/objtuple.c
index d575decfd..3ba37d027 100644
--- a/py/objtuple.c
+++ b/py/objtuple.c
@@ -37,27 +37,27 @@ STATIC mp_obj_t mp_obj_new_tuple_iterator(mp_obj_tuple_t *tuple, mp_uint_t cur);
 /******************************************************************************/
 /* tuple                                                                      */
 
-void mp_obj_tuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
     mp_obj_tuple_t *o = o_in;
     if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
-        print(env, "[");
+        mp_print_str(print, "[");
     } else {
-        print(env, "(");
+        mp_print_str(print, "(");
         kind = PRINT_REPR;
     }
     for (mp_uint_t i = 0; i < o->len; i++) {
         if (i > 0) {
-            print(env, ", ");
+            mp_print_str(print, ", ");
         }
-        mp_obj_print_helper(print, env, o->items[i], kind);
+        mp_obj_print_helper(print, o->items[i], kind);
     }
     if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
-        print(env, "]");
+        mp_print_str(print, "]");
     } else {
         if (o->len == 1) {
-            print(env, ",");
+            mp_print_str(print, ",");
         }
-        print(env, ")");
+        mp_print_str(print, ")");
     }
 }
 
diff --git a/py/objtuple.h b/py/objtuple.h
index 4cac48755..5fb82ba52 100644
--- a/py/objtuple.h
+++ b/py/objtuple.h
@@ -34,7 +34,7 @@ typedef struct _mp_obj_tuple_t {
     mp_obj_t items[];
 } mp_obj_tuple_t;
 
-void mp_obj_tuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind);
+void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind);
 mp_obj_t mp_obj_tuple_unary_op(mp_uint_t op, mp_obj_t self_in);
 mp_obj_t mp_obj_tuple_binary_op(mp_uint_t op, mp_obj_t lhs, mp_obj_t rhs);
 mp_obj_t mp_obj_tuple_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value);
diff --git a/py/objtype.c b/py/objtype.c
index cb7110572..5b6a011ed 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -196,7 +196,7 @@ STATIC void mp_obj_class_lookup(struct class_lookup_data  *lookup, const mp_obj_
     }
 }
 
-STATIC void instance_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     mp_obj_instance_t *self = self_in;
     qstr meth = (kind == PRINT_STR) ? MP_QSTR___str__ : MP_QSTR___repr__;
     mp_obj_t member[2] = {MP_OBJ_NULL};
@@ -219,23 +219,23 @@ STATIC void instance_print(void (*print)(void *env, const char *fmt, ...), void
         // Handle Exception subclasses specially
         if (mp_obj_is_native_exception_instance(self->subobj[0])) {
             if (kind != PRINT_STR) {
-                print(env, "%s", qstr_str(self->base.type->name));
+                mp_print_str(print, qstr_str(self->base.type->name));
             }
-            mp_obj_print_helper(print, env, self->subobj[0], kind | PRINT_EXC_SUBCLASS);
+            mp_obj_print_helper(print, self->subobj[0], kind | PRINT_EXC_SUBCLASS);
         } else {
-            mp_obj_print_helper(print, env, self->subobj[0], kind);
+            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, env, r, PRINT_STR);
+        mp_obj_print_helper(print, r, PRINT_STR);
         return;
     }
 
     // TODO: CPython prints fully-qualified type name
-    print(env, "<%s object at %p>", mp_obj_get_type_str(self_in), self_in);
+    mp_printf(print, "<%s object at %p>", mp_obj_get_type_str(self_in), self_in);
 }
 
 mp_obj_t instance_make_new(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
@@ -737,10 +737,10 @@ STATIC mp_int_t instance_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo,
 //  - 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
 
-STATIC void type_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<class '%s'>", qstr_str(self->name));
+    mp_printf(print, "<class '%s'>", qstr_str(self->name));
 }
 
 STATIC mp_obj_t type_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
@@ -911,14 +911,14 @@ typedef struct _mp_obj_super_t {
     mp_obj_t obj;
 } mp_obj_super_t;
 
-STATIC void super_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<super: ");
-    mp_obj_print_helper(print, env, self->type, PRINT_STR);
-    print(env, ", ");
-    mp_obj_print_helper(print, env, self->obj, PRINT_STR);
-    print(env, ">");
+    mp_print_str(print, "<super: ");
+    mp_obj_print_helper(print, self->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(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
diff --git a/py/pfenv_printf.c b/py/pfenv_printf.c
deleted file mode 100644
index 599700edc..000000000
--- a/py/pfenv_printf.c
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * This file is part of the Micro Python 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 <assert.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdio.h>
-
-#include "py/pfenv.h"
-
-#if MICROPY_PY_BUILTINS_FLOAT
-#include "py/formatfloat.h"
-#endif
-
-int pfenv_vprintf(const pfenv_t *pfenv, const char *fmt, va_list args) {
-    int chrs = 0;
-    for (;;) {
-        {
-            const char *f = fmt;
-            while (*f != '\0' && *f != '%') {
-                ++f; // XXX UTF8 advance char
-            }
-            if (f > fmt) {
-                pfenv->print_strn(pfenv->data, fmt, f - fmt);
-                chrs += f - fmt;
-                fmt = f;
-            }
-        }
-
-        if (*fmt == '\0') {
-            break;
-        }
-
-        // move past % character
-        ++fmt;
-
-        // parse flags, if they exist
-        int flags = 0;
-        char fill = ' ';
-        while (*fmt != '\0') {
-            if (*fmt == '-') flags |= PF_FLAG_LEFT_ADJUST;
-            else if (*fmt == '+') flags |= PF_FLAG_SHOW_SIGN;
-            else if (*fmt == ' ') flags |= PF_FLAG_SPACE_SIGN;
-            else if (*fmt == '!') flags |= PF_FLAG_NO_TRAILZ;
-            else if (*fmt == '0') {
-                flags |= PF_FLAG_PAD_AFTER_SIGN;
-                fill = '0';
-            } else break;
-            ++fmt;
-        }
-
-        // parse width, if it exists
-        int width = 0;
-        for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
-            width = width * 10 + *fmt - '0';
-        }
-
-        // parse precision, if it exists
-        int prec = -1;
-        if (*fmt == '.') {
-            ++fmt;
-            if (*fmt == '*') {
-                ++fmt;
-                prec = va_arg(args, int);
-            } else {
-                prec = 0;
-                for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
-                    prec = prec * 10 + *fmt - '0';
-                }
-            }
-            if (prec < 0) {
-                prec = 0;
-            }
-        }
-
-        // parse long specifiers (current not used)
-        //bool long_arg = false;
-        if (*fmt == 'l') {
-            ++fmt;
-            //long_arg = true;
-        }
-
-        if (*fmt == '\0') {
-            break;
-        }
-
-        switch (*fmt) {
-            case 'b':
-                if (va_arg(args, int)) {
-                    chrs += pfenv_print_strn(pfenv, "true", 4, flags, fill, width);
-                } else {
-                    chrs += pfenv_print_strn(pfenv, "false", 5, flags, fill, width);
-                }
-                break;
-            case 'c':
-            {
-                char str = va_arg(args, int);
-                chrs += pfenv_print_strn(pfenv, &str, 1, flags, fill, width);
-                break;
-            }
-            case 's':
-            {
-                const char *str = va_arg(args, const char*);
-                if (str) {
-                    if (prec < 0) {
-                        prec = strlen(str);
-                    }
-                    chrs += pfenv_print_strn(pfenv, str, prec, flags, fill, width);
-                } else {
-                    chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, fill, width);
-                }
-                break;
-            }
-            case 'u':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, fill, width);
-                break;
-            case 'd':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, fill, width);
-                break;
-            case 'x':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, fill, width);
-                break;
-            case 'X':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, fill, width);
-                break;
-            case 'p':
-            case 'P': // don't bother to handle upcase for 'P'
-                chrs += pfenv_print_int(pfenv, va_arg(args, unsigned int), 0, 16, 'a', flags, fill, width);
-                break;
-#if MICROPY_PY_BUILTINS_FLOAT
-            case 'e':
-            case 'E':
-            case 'f':
-            case 'F':
-            case 'g':
-            case 'G':
-            {
-#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
-                mp_float_t f = va_arg(args, double);
-                chrs += pfenv_print_float(pfenv, f, *fmt, flags, fill, width, prec);
-#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
-                // Currently pfenv_print_float uses snprintf, but snprintf
-                // itself may be implemented in terms of pfenv_vprintf() for
-                // some ports. So, for extra caution, this case is handled
-                // with assert below. Note that currently ports which
-                // use MICROPY_FLOAT_IMPL_DOUBLE, don't call pfenv_vprintf()
-                // with float format specifier at all.
-                // TODO: resolve this completely
-                assert(0);
-//#error Calling pfenv_print_float with double not supported from within printf
-#else
-#error Unknown MICROPY FLOAT IMPL
-#endif
-                break;
-            }
-#endif
-            default:
-                pfenv->print_strn(pfenv->data, fmt, 1);
-                chrs += 1;
-                break;
-        }
-        ++fmt;
-    }
-    return chrs;
-}
-
-int pfenv_printf(const pfenv_t *pfenv, const char *fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    int ret = pfenv_vprintf(pfenv, fmt, ap);
-    va_end(ap);
-    return ret;
-}
-
-void printf_wrapper(void *env, const char *fmt, ...) {
-    (void)env;
-    va_list args;
-    va_start(args, fmt);
-    vprintf(fmt, args);
-    va_end(args);
-}
diff --git a/py/py.mk b/py/py.mk
index e120f8be4..98c604115 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -22,6 +22,7 @@ PY_O_BASENAME = \
 	gc.o \
 	qstr.o \
 	vstr.o \
+	mpprint.o \
 	unicode.o \
 	mpz.o \
 	lexer.o \
@@ -108,8 +109,6 @@ PY_O_BASENAME = \
 	showbc.o \
 	repl.o \
 	smallint.o \
-	pfenv.o \
-	pfenv_printf.o \
 	frozenmod.o \
 	../extmod/moductypes.o \
 	../extmod/modujson.o \
diff --git a/py/runtime.h b/py/runtime.h
index f05a7d0a8..d171f1582 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -63,6 +63,9 @@ extern const qstr mp_binary_op_method_name[];
 void mp_init(void);
 void mp_deinit(void);
 
+// 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);
+
 void mp_arg_check_num(mp_uint_t n_args, mp_uint_t n_kw, mp_uint_t n_args_min, mp_uint_t n_args_max, bool takes_kw);
 void mp_arg_parse_all(mp_uint_t n_pos, const mp_obj_t *pos, mp_map_t *kws, mp_uint_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals);
 void mp_arg_parse_all_kw_array(mp_uint_t n_pos, mp_uint_t n_kw, const mp_obj_t *args, mp_uint_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals);
diff --git a/py/vstr.c b/py/vstr.c
index 953e8edb5..45915e7c4 100644
--- a/py/vstr.c
+++ b/py/vstr.c
@@ -31,6 +31,7 @@
 
 #include "py/mpconfig.h"
 #include "py/misc.h"
+#include "py/mpprint.h"
 
 // returned value is always at least 1 greater than argument
 #define ROUND_ALLOC(a) (((a) & ((~0) - 7)) + 8)
@@ -65,6 +66,12 @@ void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf) {
     vstr->fixed_buf = true;
 }
 
+void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) {
+    vstr_init(vstr, alloc);
+    print->data = vstr;
+    print->print_strn = (mp_print_strn_t)vstr_add_strn;
+}
+
 void vstr_clear(vstr_t *vstr) {
     if (!vstr->fixed_buf) {
         m_del(char, vstr->buf, vstr->alloc);
@@ -322,32 +329,6 @@ void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) {
         return;
     }
 
-    while (1) {
-        // try to print in the allocated space
-        // need to make a copy of the va_list because we may call vsnprintf multiple times
-        size_t size = vstr->alloc - vstr->len;
-        va_list ap2;
-        va_copy(ap2, ap);
-        int n = vsnprintf(vstr->buf + vstr->len, size, fmt, ap2);
-        va_end(ap2);
-
-        // if that worked, return
-        if (n > -1 && (size_t)n < size) {
-            vstr->len += n;
-            return;
-        }
-
-        // else try again with more space
-        if (n > -1) { // glibc 2.1
-            // n + 1 is precisely what is needed
-            if (!vstr_ensure_extra(vstr, n + 1)) {
-                return;
-            }
-        } else { // glibc 2.0
-            // increase to twice the old size
-            if (!vstr_ensure_extra(vstr, size * 2)) {
-                return;
-            }
-        }
-    }
+    mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn};
+    mp_vprintf(&print, fmt, ap);
 }
diff --git a/qemu-arm/main.c b/qemu-arm/main.c
index 860014493..1673c82af 100644
--- a/qemu-arm/main.c
+++ b/qemu-arm/main.c
@@ -11,7 +11,6 @@
 #include "py/stackctrl.h"
 #include "py/gc.h"
 #include "py/repl.h"
-#include "py/pfenv.h"
 
 void do_str(const char *src) {
     mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
@@ -28,7 +27,7 @@ void do_str(const char *src) {
         nlr_pop();
     } else {
         // uncaught exception
-        mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+        mp_obj_print_exception(&mp_extern_printf_wrapper, (mp_obj_t)nlr.ret_val);
     }
 }
 
diff --git a/qemu-arm/test_main.c b/qemu-arm/test_main.c
index a48bcd322..a52913353 100644
--- a/qemu-arm/test_main.c
+++ b/qemu-arm/test_main.c
@@ -11,7 +11,6 @@
 #include "py/stackctrl.h"
 #include "py/gc.h"
 #include "py/repl.h"
-#include "py/pfenv.h"
 
 #include "tinytest.h"
 #include "tinytest_macros.h"
@@ -39,7 +38,7 @@ inline void do_str(const char *src) {
             tinytest_set_test_skipped_();
             return;
         }
-        mp_obj_print_exception(printf_wrapper, NULL, exc);
+        mp_obj_print_exception(&mp_extern_printf_wrapper, exc);
         tt_abort_msg("Uncaught exception");
     }
 end:
diff --git a/stmhal/adc.c b/stmhal/adc.c
index e88119567..bc48f7b17 100644
--- a/stmhal/adc.c
+++ b/stmhal/adc.c
@@ -138,11 +138,11 @@ STATIC uint32_t adc_read_channel(ADC_HandleTypeDef *adcHandle) {
 /******************************************************************************/
 /* Micro Python bindings : adc object (single channel)                        */
 
-STATIC void adc_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_obj_adc_t *self = self_in;
-    print(env, "<ADC on ");
-    mp_obj_print_helper(print, env, self->pin_name, PRINT_STR);
-    print(env, " channel=%lu>", self->channel);
+    mp_print_str(print, "<ADC on ");
+    mp_obj_print_helper(print, self->pin_name, PRINT_STR);
+    mp_printf(print, " channel=%lu>", self->channel);
 }
 
 /// \classmethod \constructor(pin)
diff --git a/stmhal/can.c b/stmhal/can.c
index 654daddf6..66693f30d 100644
--- a/stmhal/can.c
+++ b/stmhal/can.c
@@ -33,7 +33,6 @@
 #include "py/objtuple.h"
 #include "py/runtime.h"
 #include "py/gc.h"
-#include "py/pfenv.h"
 #include "bufhelper.h"
 #include "can.h"
 #include "pybioctl.h"
@@ -174,12 +173,12 @@ STATIC void can_clearfilter(uint32_t f) {
 /******************************************************************************/
 // Micro Python bindings
 
-STATIC void pyb_can_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_can_obj_t *self = self_in;
     if (!self->is_enabled) {
-        print(env, "CAN(%u)", self->can_id);
+        mp_printf(print, "CAN(%u)", self->can_id);
     } else {
-        print(env, "CAN(%u, CAN.", self->can_id);
+        mp_printf(print, "CAN(%u, CAN.", self->can_id);
         qstr mode;
         switch (self->can.Init.Mode) {
             case CAN_MODE_NORMAL: mode = MP_QSTR_NORMAL; break;
@@ -187,13 +186,13 @@ STATIC void pyb_can_print(void (*print)(void *env, const char *fmt, ...), void *
             case CAN_MODE_SILENT: mode = MP_QSTR_SILENT; break;
             case CAN_MODE_SILENT_LOOPBACK: default: mode = MP_QSTR_SILENT_LOOPBACK; break;
         }
-        print(env, "%s, extframe=", qstr_str(mode));
+        mp_printf(print, "%s, extframe=", qstr_str(mode));
         if (self->extframe) {
             mode = MP_QSTR_True;
         } else {
             mode = MP_QSTR_False;
         }
-        print(env, "%s)", qstr_str(mode));
+        mp_printf(print, "%s)", qstr_str(mode));
     }
 }
 
@@ -714,7 +713,7 @@ void can_rx_irq_handler(uint can_id, uint fifo_id) {
             // Uncaught exception; disable the callback so it doesn't run again.
             pyb_can_rxcallback(self, MP_OBJ_NEW_SMALL_INT(fifo_id), mp_const_none);
             printf("uncaught exception in CAN(%u) rx interrupt handler\n", self->can_id);
-            mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+            mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
         }
         gc_unlock();
     }
diff --git a/stmhal/extint.c b/stmhal/extint.c
index 40254ac4d..cda393ed8 100644
--- a/stmhal/extint.c
+++ b/stmhal/extint.c
@@ -31,7 +31,7 @@
 #include "py/nlr.h"
 #include "py/runtime.h"
 #include "py/gc.h"
-#include "py/pfenv.h"
+#include MICROPY_HAL_H
 #include "pin.h"
 #include "extint.h"
 
@@ -296,9 +296,9 @@ STATIC mp_obj_t extint_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_
     return self;
 }
 
-STATIC void extint_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void extint_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     extint_obj_t *self = self_in;
-    print(env, "<ExtInt line=%u>", self->line);
+    mp_printf(print, "<ExtInt line=%u>", self->line);
 }
 
 STATIC const mp_map_elem_t extint_locals_dict_table[] = {
@@ -356,7 +356,7 @@ void Handle_EXTI_Irq(uint32_t line) {
                     *cb = mp_const_none;
                     extint_disable(line);
                     printf("Uncaught exception in ExtInt interrupt handler line %lu\n", line);
-                    mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+                    mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
                 }
                 gc_unlock();
             }
diff --git a/stmhal/file.c b/stmhal/file.c
index 62e582874..3f7342bd6 100644
--- a/stmhal/file.c
+++ b/stmhal/file.c
@@ -65,8 +65,8 @@ typedef struct _pyb_file_obj_t {
     FIL fp;
 } pyb_file_obj_t;
 
-void file_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
-    print(env, "<io.%s %p>", mp_obj_get_type_str(self_in), self_in);
+void file_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+    mp_printf(print, "<io.%s %p>", mp_obj_get_type_str(self_in), self_in);
 }
 
 STATIC mp_uint_t file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
diff --git a/stmhal/i2c.c b/stmhal/i2c.c
index 80b40b887..e742b2100 100644
--- a/stmhal/i2c.c
+++ b/stmhal/i2c.c
@@ -183,7 +183,7 @@ STATIC const pyb_i2c_obj_t pyb_i2c_obj[] = {
     {{&pyb_i2c_type}, &I2CHandle2}
 };
 
-STATIC void pyb_i2c_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_i2c_obj_t *self = self_in;
 
     uint i2c_num;
@@ -191,12 +191,12 @@ STATIC void pyb_i2c_print(void (*print)(void *env, const char *fmt, ...), void *
     else { i2c_num = 2; }
 
     if (self->i2c->State == HAL_I2C_STATE_RESET) {
-        print(env, "I2C(%u)", i2c_num);
+        mp_printf(print, "I2C(%u)", i2c_num);
     } else {
         if (in_master_mode(self)) {
-            print(env, "I2C(%u, I2C.MASTER, baudrate=%u)", i2c_num, self->i2c->Init.ClockSpeed);
+            mp_printf(print, "I2C(%u, I2C.MASTER, baudrate=%u)", i2c_num, self->i2c->Init.ClockSpeed);
         } else {
-            print(env, "I2C(%u, I2C.SLAVE, addr=0x%02x)", i2c_num, (self->i2c->Instance->OAR1 >> 1) & 0x7f);
+            mp_printf(print, "I2C(%u, I2C.SLAVE, addr=0x%02x)", i2c_num, (self->i2c->Instance->OAR1 >> 1) & 0x7f);
         }
     }
 }
diff --git a/stmhal/led.c b/stmhal/led.c
index 31665ed5a..cd553c391 100644
--- a/stmhal/led.c
+++ b/stmhal/led.c
@@ -210,9 +210,9 @@ void led_debug(int n, int delay) {
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 
-void led_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void led_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_led_obj_t *self = self_in;
-    print(env, "LED(%lu)", self->led_id);
+    mp_printf(print, "LED(%lu)", self->led_id);
 }
 
 /// \classmethod \constructor(id)
diff --git a/stmhal/modstm.c b/stmhal/modstm.c
index 72f4c67a7..b795d296b 100644
--- a/stmhal/modstm.c
+++ b/stmhal/modstm.c
@@ -68,9 +68,9 @@ typedef struct _stm_mem_obj_t {
     uint32_t elem_size; // in bytes
 } stm_mem_obj_t;
 
-STATIC void stm_mem_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void stm_mem_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     stm_mem_obj_t *self = self_in;
-    print(env, "<%u-bit memory>", 8 * self->elem_size);
+    mp_printf(print, "<%u-bit memory>", 8 * self->elem_size);
 }
 
 STATIC mp_obj_t stm_mem_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h
index 07db263d5..0f38eb030 100644
--- a/stmhal/mpconfigport.h
+++ b/stmhal/mpconfigport.h
@@ -183,6 +183,9 @@ typedef void *machine_ptr_t; // must be of pointer size
 typedef const void *machine_const_ptr_t; // must be of pointer size
 typedef long mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 // We have inlined IRQ functions for efficiency (they are generally
 // 1 machine instruction).
 //
diff --git a/stmhal/pin.c b/stmhal/pin.c
index 0d28ffd54..f655aaaa5 100644
--- a/stmhal/pin.c
+++ b/stmhal/pin.c
@@ -180,17 +180,17 @@ const pin_obj_t *pin_find(mp_obj_t user_obj) {
 
 /// \method __str__()
 /// Return a string describing the pin object.
-STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pin_obj_t *self = self_in;
 
     // pin name
-    print(env, "Pin(Pin.cpu.%s, mode=Pin.", qstr_str(self->name));
+    mp_printf(print, "Pin(Pin.cpu.%s, mode=Pin.", qstr_str(self->name));
 
     uint32_t mode = pin_get_mode(self);
 
     if (mode == GPIO_MODE_ANALOG) {
         // analog
-        print(env, "ANALOG)");
+        mp_print_str(print, "ANALOG)");
 
     } else {
         // IO mode
@@ -210,7 +210,7 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
                 mode_qst = MP_QSTR_AF_OD;
             }
         }
-        print(env, qstr_str(mode_qst)); // safe because mode_qst has no formating chars
+        mp_print_str(print, qstr_str(mode_qst));
 
         // pull mode
         qstr pull_qst = MP_QSTR_NULL;
@@ -221,7 +221,7 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
             pull_qst = MP_QSTR_PULL_DOWN;
         }
         if (pull_qst != MP_QSTR_NULL) {
-            print(env, ", pull=Pin.%s", qstr_str(pull_qst));
+            mp_printf(print, ", pull=Pin.%s", qstr_str(pull_qst));
         }
 
         // AF mode
@@ -229,12 +229,12 @@ STATIC void pin_print(void (*print)(void *env, const char *fmt, ...), void *env,
             mp_uint_t af_idx = pin_get_af(self);
             const pin_af_obj_t *af_obj = pin_find_af_by_index(self, af_idx);
             if (af_obj == NULL) {
-                print(env, ", af=%d)", af_idx);
+                mp_printf(print, ", af=%d)", af_idx);
             } else {
-                print(env, ", af=Pin.%s)", qstr_str(af_obj->name));
+                mp_printf(print, ", af=Pin.%s)", qstr_str(af_obj->name));
             }
         } else {
-            print(env, ")");
+            mp_print_str(print, ")");
         }
     }
 }
@@ -616,9 +616,9 @@ const mp_obj_type_t pin_type = {
 
 /// \method __str__()
 /// Return a string describing the alternate function.
-STATIC void pin_af_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pin_af_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pin_af_obj_t *self = self_in;
-    print(env, "Pin.%s", qstr_str(self->name));
+    mp_printf(print, "Pin.%s", qstr_str(self->name));
 }
 
 /// \method index()
diff --git a/stmhal/pin_named_pins.c b/stmhal/pin_named_pins.c
index f6ee16571..973c080f1 100644
--- a/stmhal/pin_named_pins.c
+++ b/stmhal/pin_named_pins.c
@@ -31,9 +31,9 @@
 #include MICROPY_HAL_H
 #include "pin.h"
 
-STATIC void pin_named_pins_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pin_named_pins_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pin_named_pins_obj_t *self = self_in;
-    print(env, "<Pin.%s>", qstr_str(self->name));
+    mp_printf(print, "<Pin.%s>", qstr_str(self->name));
 }
 
 const mp_obj_type_t pin_cpu_pins_obj_type = {
diff --git a/stmhal/printf.c b/stmhal/printf.c
index 038ee9937..fed57a776 100644
--- a/stmhal/printf.c
+++ b/stmhal/printf.c
@@ -29,7 +29,6 @@
 #include <stdarg.h>
 
 #include "py/obj.h"
-#include "py/pfenv.h"
 #ifdef MICROPY_HAL_H
 #include MICROPY_HAL_H
 #endif
@@ -38,22 +37,16 @@
 #include "py/formatfloat.h"
 #endif
 
-STATIC void stdout_print_strn(void *dummy_env, const char *str, mp_uint_t len) {
-    mp_hal_stdout_tx_strn_cooked(str, len);
-}
-
-STATIC const pfenv_t pfenv_stdout = {0, stdout_print_strn};
-
 int printf(const char *fmt, ...) {
     va_list ap;
     va_start(ap, fmt);
-    int ret = pfenv_vprintf(&pfenv_stdout, fmt, ap);
+    int ret = mp_vprintf(&mp_plat_print, fmt, ap);
     va_end(ap);
     return ret;
 }
 
 int vprintf(const char *fmt, va_list ap) {
-    return pfenv_vprintf(&pfenv_stdout, fmt, ap);
+    return mp_vprintf(&mp_plat_print, fmt, ap);
 }
 
 #if MICROPY_DEBUG_PRINTERS
@@ -62,7 +55,7 @@ mp_uint_t mp_verbose_flag = 1;
 int DEBUG_printf(const char *fmt, ...) {
     va_list ap;
     va_start(ap, fmt);
-    int ret = pfenv_vprintf(&pfenv_stdout, fmt, ap);
+    int ret = mp_vprintf(&mp_plat_print, fmt, ap);
     va_end(ap);
     return ret;
 }
@@ -71,47 +64,43 @@ int DEBUG_printf(const char *fmt, ...) {
 // need this because gcc optimises printf("%c", c) -> putchar(c), and printf("a") -> putchar('a')
 int putchar(int c) {
     char chr = c;
-    stdout_print_strn(0, &chr, 1);
+    mp_hal_stdout_tx_strn_cooked(&chr, 1);
     return chr;
 }
 
 // need this because gcc optimises printf("string\n") -> puts("string")
 int puts(const char *s) {
-    stdout_print_strn(0, s, strlen(s));
+    mp_hal_stdout_tx_strn_cooked(s, strlen(s));
     char chr = '\n';
-    stdout_print_strn(0, &chr, 1);
+    mp_hal_stdout_tx_strn_cooked(&chr, 1);
     return 1;
 }
 
-typedef struct _strn_pfenv_t {
+typedef struct _strn_print_env_t {
     char *cur;
     size_t remain;
-} strn_pfenv_t;
+} strn_print_env_t;
 
 STATIC void strn_print_strn(void *data, const char *str, mp_uint_t len) {
-    strn_pfenv_t *strn_pfenv = data;
-    if (len > strn_pfenv->remain) {
-        len = strn_pfenv->remain;
+    strn_print_env_t *strn_print_env = data;
+    if (len > strn_print_env->remain) {
+        len = strn_print_env->remain;
     }
-    memcpy(strn_pfenv->cur, str, len);
-    strn_pfenv->cur += len;
-    strn_pfenv->remain -= len;
+    memcpy(strn_print_env->cur, str, len);
+    strn_print_env->cur += len;
+    strn_print_env->remain -= len;
 }
 
 int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) {
-    strn_pfenv_t strn_pfenv;
-    strn_pfenv.cur = str;
-    strn_pfenv.remain = size;
-    pfenv_t pfenv;
-    pfenv.data = &strn_pfenv;
-    pfenv.print_strn = strn_print_strn;
-    int len = pfenv_vprintf(&pfenv, fmt, ap);
+    strn_print_env_t strn_print_env = {str, size};
+    mp_print_t print = {&strn_print_env, strn_print_strn};
+    int len = mp_vprintf(&print, fmt, ap);
     // add terminating null byte
     if (size > 0) {
-        if (strn_pfenv.remain == 0) {
-            strn_pfenv.cur[-1] = 0;
+        if (strn_print_env.remain == 0) {
+            strn_print_env.cur[-1] = 0;
         } else {
-            strn_pfenv.cur[0] = 0;
+            strn_print_env.cur[0] = 0;
         }
     }
     return len;
diff --git a/stmhal/pybstdio.c b/stmhal/pybstdio.c
index 6b9904981..bf8b3717d 100644
--- a/stmhal/pybstdio.c
+++ b/stmhal/pybstdio.c
@@ -48,9 +48,9 @@ typedef struct _pyb_stdio_obj_t {
     int fd;
 } pyb_stdio_obj_t;
 
-void stdio_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void stdio_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_stdio_obj_t *self = self_in;
-    print(env, "<io.FileIO %d>", self->fd);
+    mp_printf(print, "<io.FileIO %d>", self->fd);
 }
 
 STATIC mp_uint_t stdio_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
diff --git a/stmhal/pyexec.c b/stmhal/pyexec.c
index 27c206ce3..95776f3ec 100644
--- a/stmhal/pyexec.c
+++ b/stmhal/pyexec.c
@@ -33,7 +33,6 @@
 #include "py/runtime.h"
 #include "py/repl.h"
 #include "py/gc.h"
-#include "py/pfenv.h"
 #ifdef MICROPY_HAL_H
 #include MICROPY_HAL_H
 #endif
@@ -87,7 +86,7 @@ STATIC int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_ki
             // at the moment, the value of SystemExit is unused
             ret = PYEXEC_FORCED_EXIT;
         } else {
-            mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+            mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
             ret = 0;
         }
     }
diff --git a/stmhal/servo.c b/stmhal/servo.c
index a9b37a54f..4c4f95da4 100644
--- a/stmhal/servo.c
+++ b/stmhal/servo.c
@@ -178,9 +178,9 @@ STATIC mp_obj_t pyb_pwm_set(mp_obj_t period, mp_obj_t pulse) {
 
 MP_DEFINE_CONST_FUN_OBJ_2(pyb_pwm_set_obj, pyb_pwm_set);
 
-STATIC void pyb_servo_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_servo_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_servo_obj_t *self = self_in;
-    print(env, "<Servo %lu at %luus>", self->servo_id, 10 * self->pulse_cur);
+    mp_printf(print, "<Servo %lu at %luus>", self->servo_id, 10 * self->pulse_cur);
 }
 
 /// \classmethod \constructor(id)
diff --git a/stmhal/spi.c b/stmhal/spi.c
index 6f8b8f1db..19f62e42a 100644
--- a/stmhal/spi.c
+++ b/stmhal/spi.c
@@ -335,7 +335,7 @@ SPI_HandleTypeDef *spi_get_handle(mp_obj_t o) {
     return self->spi;
 }
 
-STATIC void pyb_spi_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_spi_obj_t *self = self_in;
 
     uint spi_num;
@@ -344,7 +344,7 @@ STATIC void pyb_spi_print(void (*print)(void *env, const char *fmt, ...), void *
     else { spi_num = 3; }
 
     if (self->spi->State == HAL_SPI_STATE_RESET) {
-        print(env, "SPI(%u)", spi_num);
+        mp_printf(print, "SPI(%u)", spi_num);
     } else {
         if (self->spi->Init.Mode == SPI_MODE_MASTER) {
             // compute baudrate
@@ -358,15 +358,15 @@ STATIC void pyb_spi_print(void (*print)(void *env, const char *fmt, ...), void *
             }
             uint log_prescaler = (self->spi->Init.BaudRatePrescaler >> 3) + 1;
             uint baudrate = spi_clock >> log_prescaler;
-            print(env, "SPI(%u, SPI.MASTER, baudrate=%u, prescaler=%u", spi_num, baudrate, 1 << log_prescaler);
+            mp_printf(print, "SPI(%u, SPI.MASTER, baudrate=%u, prescaler=%u", spi_num, baudrate, 1 << log_prescaler);
         } else {
-            print(env, "SPI(%u, SPI.SLAVE", spi_num);
+            mp_printf(print, "SPI(%u, SPI.SLAVE", spi_num);
         }
-        print(env, ", polarity=%u, phase=%u, bits=%u", self->spi->Init.CLKPolarity == SPI_POLARITY_LOW ? 0 : 1, self->spi->Init.CLKPhase == SPI_PHASE_1EDGE ? 0 : 1, self->spi->Init.DataSize == SPI_DATASIZE_8BIT ? 8 : 16);
+        mp_printf(print, ", polarity=%u, phase=%u, bits=%u", self->spi->Init.CLKPolarity == SPI_POLARITY_LOW ? 0 : 1, self->spi->Init.CLKPhase == SPI_PHASE_1EDGE ? 0 : 1, self->spi->Init.DataSize == SPI_DATASIZE_8BIT ? 8 : 16);
         if (self->spi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLED) {
-            print(env, ", crc=0x%x", self->spi->Init.CRCPolynomial);
+            mp_printf(print, ", crc=0x%x", self->spi->Init.CRCPolynomial);
         }
-        print(env, ")");
+        mp_print_str(print, ")");
     }
 }
 
diff --git a/stmhal/timer.c b/stmhal/timer.c
index 606852659..0180cf442 100644
--- a/stmhal/timer.c
+++ b/stmhal/timer.c
@@ -35,7 +35,7 @@
 #include "py/nlr.h"
 #include "py/runtime.h"
 #include "py/gc.h"
-#include "py/pfenv.h"
+#include MICROPY_HAL_H
 #include "timer.h"
 #include "servo.h"
 #include "pin.h"
@@ -468,17 +468,17 @@ STATIC void config_deadtime(pyb_timer_obj_t *self, mp_int_t ticks) {
     HAL_TIMEx_ConfigBreakDeadTime(&self->tim, &deadTimeConfig);
 }
 
-STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_timer_obj_t *self = self_in;
 
     if (self->tim.State == HAL_TIM_STATE_RESET) {
-        print(env, "Timer(%u)", self->tim_id);
+        mp_printf(print, "Timer(%u)", self->tim_id);
     } else {
         uint32_t prescaler = self->tim.Instance->PSC & 0xffff;
         uint32_t period = __HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self);
         // for efficiency, we compute and print freq as an int (not a float)
         uint32_t freq = timer_get_source_freq(self->tim_id) / ((prescaler + 1) * (period + 1));
-        print(env, "Timer(%u, freq=%u, prescaler=%u, period=%u, mode=%s, div=%u",
+        mp_printf(print, "Timer(%u, freq=%u, prescaler=%u, period=%u, mode=%s, div=%u",
             self->tim_id,
             freq,
             prescaler,
@@ -488,9 +488,10 @@ STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void
             self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV4 ? 4 :
             self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV2 ? 2 : 1);
         if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) {
-            print(env, ", deadtime=%u", compute_ticks_from_dtg(self->tim.Instance->BDTR & TIM_BDTR_DTG));
+            mp_printf(print, ", deadtime=%u",
+                compute_ticks_from_dtg(self->tim.Instance->BDTR & TIM_BDTR_DTG));
         }
-        print(env, ")");
+        mp_print_str(print, ")");
     }
 }
 
@@ -1166,10 +1167,10 @@ const mp_obj_type_t pyb_timer_type = {
 /// Timer channels are used to generate/capture a signal using a timer.
 ///
 /// TimerChannel objects are created using the Timer.channel() method.
-STATIC void pyb_timer_channel_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_timer_channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_timer_channel_obj_t *self = self_in;
 
-    print(env, "TimerChannel(timer=%u, channel=%u, mode=%s)",
+    mp_printf(print, "TimerChannel(timer=%u, channel=%u, mode=%s)",
           self->timer->tim_id,
           self->channel,
           qstr_str(channel_mode_info[self->mode].name));
@@ -1308,7 +1309,7 @@ STATIC void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_o
                     } else {
                         printf("uncaught exception in Timer(%u) channel %u interrupt handler\n", tim->tim_id, channel);
                     }
-                    mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+                    mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
                 }
                 gc_unlock();
             }
diff --git a/stmhal/uart.c b/stmhal/uart.c
index 4821da95f..2da99c221 100644
--- a/stmhal/uart.c
+++ b/stmhal/uart.c
@@ -330,23 +330,23 @@ void uart_irq_handler(mp_uint_t uart_id) {
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 
-STATIC void pyb_uart_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_uart_obj_t *self = self_in;
     if (!self->is_enabled) {
-        print(env, "UART(%u)", self->uart_id);
+        mp_printf(print, "UART(%u)", self->uart_id);
     } else {
         mp_int_t bits = (self->uart.Init.WordLength == UART_WORDLENGTH_8B ? 8 : 9);
         if (self->uart.Init.Parity != UART_PARITY_NONE) {
             bits -= 1;
         }
-        print(env, "UART(%u, baudrate=%u, bits=%u, parity=",
+        mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=",
             self->uart_id, self->uart.Init.BaudRate, bits);
         if (self->uart.Init.Parity == UART_PARITY_NONE) {
-            print(env, "None");
+            mp_print_str(print, "None");
         } else {
-            print(env, "%u", self->uart.Init.Parity == UART_PARITY_EVEN ? 0 : 1);
+            mp_printf(print, "%u", self->uart.Init.Parity == UART_PARITY_EVEN ? 0 : 1);
         }
-        print(env, ", stop=%u, timeout=%u, timeout_char=%u, read_buf_len=%u)",
+        mp_printf(print, ", stop=%u, timeout=%u, timeout_char=%u, read_buf_len=%u)",
             self->uart.Init.StopBits == UART_STOPBITS_1 ? 1 : 2,
             self->timeout, self->timeout_char, self->read_buf_len);
     }
diff --git a/stmhal/usb.c b/stmhal/usb.c
index 96833175f..cb08f9c5d 100644
--- a/stmhal/usb.c
+++ b/stmhal/usb.c
@@ -314,8 +314,8 @@ typedef struct _pyb_usb_vcp_obj_t {
 
 STATIC const pyb_usb_vcp_obj_t pyb_usb_vcp_obj = {{&pyb_usb_vcp_type}};
 
-STATIC void pyb_usb_vcp_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
-    print(env, "USB_VCP()");
+STATIC void pyb_usb_vcp_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+    mp_print_str(print, "USB_VCP()");
 }
 
 /// \classmethod \constructor()
diff --git a/stmhal/usrsw.c b/stmhal/usrsw.c
index b6320054a..821df6010 100644
--- a/stmhal/usrsw.c
+++ b/stmhal/usrsw.c
@@ -75,8 +75,8 @@ typedef struct _pyb_switch_obj_t {
 
 STATIC const pyb_switch_obj_t pyb_switch_obj = {{&pyb_switch_type}};
 
-void pyb_switch_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
-    print(env, "Switch()");
+void pyb_switch_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+    mp_print_str(print, "Switch()");
 }
 
 /// \classmethod \constructor()
diff --git a/teensy/led.c b/teensy/led.c
index fc60167b9..500900ffc 100644
--- a/teensy/led.c
+++ b/teensy/led.c
@@ -83,10 +83,10 @@ void led_toggle(pyb_led_t led) {
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 
-void led_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+void led_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_led_obj_t *self = self_in;
     (void)kind;
-    print(env, "<LED %lu>", self->led_id);
+    mp_printf(print, "<LED %lu>", self->led_id);
 }
 
 STATIC mp_obj_t led_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
diff --git a/teensy/mpconfigport.h b/teensy/mpconfigport.h
index 57e4c36b0..d4bfd1cd3 100644
--- a/teensy/mpconfigport.h
+++ b/teensy/mpconfigport.h
@@ -66,6 +66,9 @@ typedef void *machine_ptr_t; // must be of pointer size
 typedef const void *machine_const_ptr_t; // must be of pointer size
 typedef long mp_off_t;
 
+void mp_hal_stdout_tx_strn_cooked(const char *str, uint32_t len);
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+
 // We have inlined IRQ functions for efficiency (they are generally
 // 1 machine instruction).
 //
diff --git a/teensy/timer.c b/teensy/timer.c
index e048f61d8..5d6237d00 100644
--- a/teensy/timer.c
+++ b/teensy/timer.c
@@ -31,7 +31,6 @@
 
 #include "py/nlr.h"
 #include "py/runtime.h"
-#include "py/pfenv.h"
 #include "py/gc.h"
 #include MICROPY_HAL_H
 #include "pin.h"
@@ -188,13 +187,13 @@ STATIC mp_obj_t compute_percent_from_pwm_value(uint32_t period, uint32_t cmp) {
     #endif
 }
 
-STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_timer_obj_t *self = self_in;
 
     if (self->ftm.State == HAL_FTM_STATE_RESET) {
-        print(env, "Timer(%u)", self->tim_id);
+        mp_printf(print, "Timer(%u)", self->tim_id);
     } else {
-        print(env, "Timer(%u, prescaler=%u, period=%u, mode=%s)",
+        mp_printf(print, "Timer(%u, prescaler=%u, period=%u, mode=%s)",
             self->tim_id,
             1 << (self->ftm.Instance->SC & 7),
             self->ftm.Instance->MOD & 0xffff,
@@ -752,10 +751,10 @@ const mp_obj_type_t pyb_timer_type = {
 /// Timer channels are used to generate/capture a signal using a timer.
 ///
 /// TimerChannel objects are created using the Timer.channel() method.
-STATIC void pyb_timer_channel_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_timer_channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_timer_channel_obj_t *self = self_in;
 
-    print(env, "TimerChannel(timer=%u, channel=%u, mode=%s)",
+    mp_printf(print, "TimerChannel(timer=%u, channel=%u, mode=%s)",
           self->timer->tim_id,
           self->channel,
           qstr_str(channel_mode_info[self->mode].name));
@@ -913,7 +912,7 @@ STATIC bool ftm_handle_irq_callback(pyb_timer_obj_t *self, mp_uint_t channel, mp
             printf("Uncaught exception in Timer(" UINT_FMT ") channel "
                    UINT_FMT " interrupt handler\n", self->tim_id, channel);
         }
-        mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
+        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
     }
     gc_unlock();
     return handled;
diff --git a/teensy/uart.c b/teensy/uart.c
index 7e08366b2..1a04bb0d6 100644
--- a/teensy/uart.c
+++ b/teensy/uart.c
@@ -224,20 +224,20 @@ void uart_tx_strn_cooked(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
 /******************************************************************************/
 /* Micro Python bindings                                                      */
 
-STATIC void pyb_uart_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
     pyb_uart_obj_t *self = self_in;
     if (!self->is_enabled) {
-        print(env, "UART(%lu)", self->uart_id);
+        mp_printf(print, "UART(%lu)", self->uart_id);
     } else {
 #if 0
-        print(env, "UART(%lu, baudrate=%u, bits=%u, stop=%u",
+        mp_printf(print, "UART(%lu, baudrate=%u, bits=%u, stop=%u",
             self->uart_id, self->uart.Init.BaudRate,
             self->uart.Init.WordLength == UART_WORDLENGTH_8B ? 8 : 9,
             self->uart.Init.StopBits == UART_STOPBITS_1 ? 1 : 2);
         if (self->uart.Init.Parity == UART_PARITY_NONE) {
-            print(env, ", parity=None)");
+            mp_print_str(print, ", parity=None)");
         } else {
-            print(env, ", parity=%u)", self->uart.Init.Parity == UART_PARITY_EVEN ? 0 : 1);
+            mp_printf(print, ", parity=%u)", self->uart.Init.Parity == UART_PARITY_EVEN ? 0 : 1);
         }
 #endif
     }
diff --git a/unix-cpy/main.c b/unix-cpy/main.c
index 058de06d5..219e3e604 100644
--- a/unix-cpy/main.c
+++ b/unix-cpy/main.c
@@ -32,7 +32,6 @@
 #include "py/nlr.h"
 #include "py/compile.h"
 #include "py/runtime.h"
-#include "py/pfenv.h"
 
 void do_file(const char *file) {
     mp_lexer_t *lex = mp_lexer_new_from_file(file);
diff --git a/unix/file.c b/unix/file.c
index 6688028a4..dfe2d6e11 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -60,10 +60,10 @@ STATIC void check_fd_is_open(const mp_obj_fdfile_t *o) {
 extern const mp_obj_type_t mp_type_fileio;
 extern const mp_obj_type_t mp_type_textio;
 
-STATIC void fdfile_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<io.%s %d>", mp_obj_get_type_str(self), self->fd);
+    mp_printf(print, "<io.%s %d>", mp_obj_get_type_str(self), self->fd);
 }
 
 STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) {
diff --git a/unix/main.c b/unix/main.c
index 02809d89b..4729b63f6 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -43,7 +43,6 @@
 #include "py/repl.h"
 #include "py/gc.h"
 #include "py/stackctrl.h"
-#include "py/pfenv.h"
 #include "genhdr/py-version.h"
 #include "input.h"
 
@@ -91,7 +90,7 @@ STATIC int handle_uncaught_exception(mp_obj_t exc) {
     }
 
     // Report all other exceptions
-    mp_obj_print_exception(printf_wrapper, NULL, exc);
+    mp_obj_print_exception(&mp_plat_print, exc);
     return 1;
 }
 
diff --git a/unix/modffi.c b/unix/modffi.c
index b3dbaa050..9e81b1e33 100644
--- a/unix/modffi.c
+++ b/unix/modffi.c
@@ -167,10 +167,10 @@ STATIC mp_obj_t return_ffi_value(ffi_arg val, char type)
 
 // FFI module
 
-STATIC void ffimod_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<ffimod %p>", self->handle);
+    mp_printf(print, "<ffimod %p>", self->handle);
 }
 
 STATIC mp_obj_t ffimod_close(mp_obj_t self_in) {
@@ -338,10 +338,10 @@ STATIC const mp_obj_type_t ffimod_type = {
 
 // FFI function
 
-STATIC void ffifunc_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<ffifunc %p>", self->func);
+    mp_printf(print, "<ffifunc %p>", self->func);
 }
 
 STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
@@ -410,10 +410,10 @@ STATIC const mp_obj_type_t ffifunc_type = {
 
 // FFI callback for Python function
 
-STATIC void fficallback_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<fficallback %p>", self->func);
+    mp_printf(print, "<fficallback %p>", self->func);
 }
 
 STATIC const mp_obj_type_t fficallback_type = {
@@ -424,11 +424,11 @@ STATIC const mp_obj_type_t fficallback_type = {
 
 // FFI variable
 
-STATIC void ffivar_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
     // Variable value printed as cast to int
-    print(env, "<ffivar @%p: 0x%x>", self->var, *(int*)self->var);
+    mp_printf(print, "<ffivar @%p: 0x%x>", self->var, *(int*)self->var);
 }
 
 STATIC mp_obj_t ffivar_get(mp_obj_t self_in) {
diff --git a/unix/modsocket.c b/unix/modsocket.c
index 8f6ac26c6..738581a0a 100644
--- a/unix/modsocket.c
+++ b/unix/modsocket.c
@@ -82,10 +82,10 @@ STATIC mp_obj_socket_t *socket_new(int fd) {
 }
 
 
-STATIC void socket_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
+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 = self_in;
-    print(env, "<_socket %d>", self->fd);
+    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) {
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index 00cb12139..694db88fc 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -170,6 +170,8 @@ 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)
 
+#define MP_PLAT_PRINT_STRN(str, len) fwrite(str, 1, len, stdout)
+
 extern const struct _mp_obj_fun_builtin_t mp_builtin_input_obj;
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \
diff --git a/windows/mpconfigport.h b/windows/mpconfigport.h
index 01f8775fa..2de523c78 100644
--- a/windows/mpconfigport.h
+++ b/windows/mpconfigport.h
@@ -115,6 +115,8 @@ typedef long mp_off_t;
 typedef void *machine_ptr_t; // must be of pointer size
 typedef const void *machine_const_ptr_t; // must be of pointer size
 
+#define MP_PLAT_PRINT_STRN(str, len) fwrite(str, 1, len, stdout)
+
 extern const struct _mp_obj_fun_builtin_t mp_builtin_input_obj;
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \
-- 
GitLab