diff --git a/py/gc.c b/py/gc.c
index 054a3cc3153665b0da61814c7a864a6ae19851df..70b071ebc8a581c080f1426ad5eb9fc73b52c44d 100644
--- a/py/gc.c
+++ b/py/gc.c
@@ -8,15 +8,8 @@
 
 #if MICROPY_ENABLE_GC
 
-// a machine word is big enough to hold a pointer
-/*
-#define BYTES_PER_WORD (8)
-typedef unsigned long machine_uint_t;
-*/
 typedef unsigned char byte;
 
-#define BITS_PER_BYTE (8)
-#define BITS_PER_WORD (BITS_PER_BYTE * BYTES_PER_WORD)
 #define WORDS_PER_BLOCK (4)
 #define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
 #define STACK_SIZE (64) // tunable; minimum is 1
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 2017ba366a64704ca808bd8708a0c107d011c063..ada4aa2ea42f2d784f7002650496b9f8414fdb59 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -62,6 +62,18 @@
 #define MICROPY_ENABLE_LEXER_UNIX (0)
 #endif
 
+// Long int implementation
+#define MICROPY_LONGINT_IMPL_NONE (0)
+#define MICROPY_LONGINT_IMPL_LONGLONG (1)
+
+#ifndef MICROPY_LONGINT_IMPL
+#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE)
+#endif
+
+#if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG
+typedef long long mp_longint_impl_t;
+#endif
+
 // Whether to support float and complex types
 #ifndef MICROPY_ENABLE_FLOAT
 #define MICROPY_ENABLE_FLOAT (0)
@@ -76,6 +88,11 @@
 /*****************************************************************************/
 /* Miscellaneous settings                                                    */
 
+#define BITS_PER_BYTE (8)
+#define BITS_PER_WORD (BITS_PER_BYTE * BYTES_PER_WORD)
+// machine_int_t value with most significant bit set
+#define WORD_MSBIT_HIGH (1 << (BYTES_PER_WORD * 8 - 1))
+
 // printf format spec to use for machine_int_t and friends
 #ifndef INT_FMT
 #ifdef __LP64__
diff --git a/py/obj.h b/py/obj.h
index b92f1e2a7eb0cbd039f6af6bb68fa22d478a27bb..a9a5827ee7ff513db0576eb235a8179349dc7527 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -34,6 +34,8 @@ typedef struct _mp_obj_base_t mp_obj_base_t;
 //  - xxxx...xx10: a qstr, bits 2 and above are the value
 //  - xxxx...xx00: a pointer to an mp_obj_base_t
 
+// In SMALL_INT, next-to-highest bits is used as sign, so both must match for value in range
+#define MP_OBJ_FITS_SMALL_INT(n) ((((n) ^ ((n) << 1)) & WORD_MSBIT_HIGH) == 0)
 #define MP_OBJ_IS_SMALL_INT(o) ((((mp_small_int_t)(o)) & 1) != 0)
 #define MP_OBJ_IS_QSTR(o) ((((mp_small_int_t)(o)) & 3) == 2)
 #define MP_OBJ_IS_OBJ(o) ((((mp_small_int_t)(o)) & 3) == 0)
@@ -196,6 +198,8 @@ mp_obj_t mp_obj_new_none(void);
 mp_obj_t mp_obj_new_bool(bool value);
 mp_obj_t mp_obj_new_cell(mp_obj_t obj);
 mp_obj_t mp_obj_new_int(machine_int_t value);
+mp_obj_t mp_obj_new_int_from_uint(machine_uint_t value);
+mp_obj_t mp_obj_new_int_from_long_str(const char *s);
 mp_obj_t mp_obj_new_str(qstr qstr);
 #if MICROPY_ENABLE_FLOAT
 mp_obj_t mp_obj_new_float(mp_float_t val);
diff --git a/py/objint.c b/py/objint.c
index 9cd5ebae295a9156a30e47872e1d39a59b95b850..26d3c0e337185f99e20c2d9a82a388624203336c 100644
--- a/py/objint.c
+++ b/py/objint.c
@@ -11,8 +11,16 @@
 
 typedef struct _mp_obj_int_t {
     mp_obj_base_t base;
+#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE
+    mp_longint_impl_t val;
+#endif
 } mp_obj_int_t;
 
+void int_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in);
+mp_obj_t int_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in);
+
+// This dispatcher function is expected to be independent of the implementation
+// of long int
 static mp_obj_t int_make_new(mp_obj_t type_in, int n_args, const mp_obj_t *args) {
     switch (n_args) {
         case 0:
@@ -20,7 +28,7 @@ static mp_obj_t int_make_new(mp_obj_t type_in, int n_args, const mp_obj_t *args)
 
         case 1:
             // TODO allow string as arg and parse it
-            return MP_OBJ_NEW_SMALL_INT(mp_obj_get_int(args[0]));
+            return mp_obj_new_int(mp_obj_get_int(args[0]));
 
         //case 2:
             // TODO, parse with given base
@@ -33,9 +41,41 @@ static mp_obj_t int_make_new(mp_obj_t type_in, int n_args, const mp_obj_t *args)
 const mp_obj_type_t int_type = {
     { &mp_const_type },
     "int",
+    .print = int_print,
     .make_new = int_make_new,
+    .binary_op = int_binary_op,
 };
 
+#if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE
+// This is called only for non-SMALL_INT
+void int_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
+}
+
+// This is called only for non-SMALL_INT
+mp_obj_t int_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
+    assert(0);
+}
+
+// This is called only with strings whose value doesn't fit in SMALL_INT
+mp_obj_t mp_obj_new_int_from_long_str(const char *s) {
+    assert(0);
+}
+
+mp_obj_t mp_obj_new_int_from_uint(machine_uint_t value) {
+    // SMALL_INT accepts only signed numbers, of one bit less size
+    // then word size, which totals 2 bits less for unsigned numbers.
+    if ((value & (WORD_MSBIT_HIGH | (WORD_MSBIT_HIGH >> 1))) == 0) {
+        return MP_OBJ_NEW_SMALL_INT(value);
+    }
+    // TODO: Raise exception
+    assert(0);
+}
+
 mp_obj_t mp_obj_new_int(machine_int_t value) {
-    return MP_OBJ_NEW_SMALL_INT(value);
+    if (MP_OBJ_FITS_SMALL_INT(value)) {
+        return MP_OBJ_NEW_SMALL_INT(value);
+    }
+    // TODO: Raise exception
+    assert(0);
 }
+#endif
diff --git a/py/parse.c b/py/parse.c
index a619c90507e45138c2c72c626dcf6674d011088a..e2c9520736a1bba302c91fed8680f7c17b00847d 100644
--- a/py/parse.c
+++ b/py/parse.c
@@ -196,7 +196,7 @@ static void push_result_token(parser_t *parser, const mp_lexer_t *lex) {
     } else if (tok->kind == MP_TOKEN_NUMBER) {
         bool dec = false;
         bool small_int = true;
-        int int_val = 0;
+        machine_int_t int_val = 0;
         int len = tok->len;
         const char *str = tok->str;
         int base = 10;
@@ -216,7 +216,9 @@ static void push_result_token(parser_t *parser, const mp_lexer_t *lex) {
                 i = 2;
             }
         }
+        bool overflow = false;
         for (; i < len; i++) {
+            machine_int_t old_val = int_val;
             if (unichar_isdigit(str[i]) && str[i] - '0' < base) {
                 int_val = base * int_val + str[i] - '0';
             } else if (base == 16 && 'a' <= str[i] && str[i] <= 'f') {
@@ -230,10 +232,17 @@ static void push_result_token(parser_t *parser, const mp_lexer_t *lex) {
                 small_int = false;
                 break;
             }
+            if (int_val < old_val) {
+                // If new value became less than previous, it's overflow
+                overflow = true;
+            } else if ((old_val ^ int_val) & WORD_MSBIT_HIGH) {
+                // If signed number changed sign - it's overflow
+                overflow = true;
+            }
         }
         if (dec) {
             pn = mp_parse_node_new_leaf(MP_PARSE_NODE_DECIMAL, qstr_from_strn_copy(str, len));
-        } else if (small_int && MP_FIT_SMALL_INT(int_val)) {
+        } else if (small_int && !overflow && MP_FIT_SMALL_INT(int_val)) {
             pn = mp_parse_node_new_leaf(MP_PARSE_NODE_SMALL_INT, int_val);
         } else {
             pn = mp_parse_node_new_leaf(MP_PARSE_NODE_INTEGER, qstr_from_strn_copy(str, len));
diff --git a/py/runtime.c b/py/runtime.c
index 8d3e9002861240e80dbc591947f63a5ed5005aef..2af86b6abd641121eb9ce2fdceebc0b3ca946cda 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -268,10 +268,6 @@ void rt_assign_inline_asm_code(int unique_code_id, void *fun, uint len, int n_ar
 #endif
 }
 
-static bool fit_small_int(mp_small_int_t o) {
-    return true;
-}
-
 int rt_is_true(mp_obj_t arg) {
     DEBUG_OP_printf("is true %p\n", arg);
     if (MP_OBJ_IS_SMALL_INT(arg)) {
@@ -436,13 +432,10 @@ mp_obj_t rt_unary_op(int op, mp_obj_t arg) {
             case RT_UNARY_OP_INVERT: val = ~val; break;
             default: assert(0); val = 0;
         }
-        if (fit_small_int(val)) {
+        if (MP_OBJ_FITS_SMALL_INT(val)) {
             return MP_OBJ_NEW_SMALL_INT(val);
-        } else {
-            // TODO make a bignum
-            assert(0);
-            return mp_const_none;
         }
+        return mp_obj_new_int(val);
     } else { // will be an object (small ints are caught in previous if)
         mp_obj_base_t *o = arg;
         if (o->type->unary_op != NULL) {
@@ -551,11 +544,11 @@ mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) {
 
                 default: assert(0);
             }
-            if (fit_small_int(lhs_val)) {
+            // TODO: We just should make mp_obj_new_int() inline and use that
+            if (MP_OBJ_FITS_SMALL_INT(lhs_val)) {
                 return MP_OBJ_NEW_SMALL_INT(lhs_val);
             }
-            // TODO: return long int
-            assert(0);
+            return mp_obj_new_int(lhs_val);
         } else if (MP_OBJ_IS_TYPE(rhs, &float_type)) {
             return mp_obj_float_binary_op(op, lhs_val, rhs);
         } else if (MP_OBJ_IS_TYPE(rhs, &complex_type)) {
diff --git a/tests/basics/tests/int-small.py b/tests/basics/tests/int-small.py
new file mode 100644
index 0000000000000000000000000000000000000000..be338c4a4cf365087a7c08292affae47198ecf7f
--- /dev/null
+++ b/tests/basics/tests/int-small.py
@@ -0,0 +1,26 @@
+# This test small int range for 32-bit machine
+
+a = 0x3fffff
+print(a)
+a *= 0x10
+print(a)
+a *= 0x10
+print(a)
+a += 0xff
+print(a)
+# This would overflow
+#a += 1
+
+a = -0x3fffff
+print(a)
+a *= 0x10
+print(a)
+a *= 0x10
+print(a)
+a -= 0xff
+print(a)
+# This still doesn't overflow
+a -= 1
+print(a)
+# This would overflow
+#a -= 1