Mojo🔥 changelog

A history of significant Mojo changes.

This file contains a running log of important changes to the Mojo language that are visible to end programmers. It doesn’t contain most internal implementation detail changes.

This is currently organized by week, including the changes that occur on the Monday through Sunday starting on the specific date. This is in reverse chronological order, so newer entries are added to the top.

May 2023


⭐️ New

  • Mojo Playground now includes the following Python packages (in response to popular demand): torch, tensorflow, polars, opencv-python, keras, Pillow, plotly, seaborn, sympy, transformers.

  • A new optimization is applied to non-trivial copyable values that are passed as an owned value without using the transfer (^) operator. Consider code like this:

      var someValue : T = ...

    When takeValueAsOwned() takes its argument as an owned value (this is common in initializers for example), it is allowed to do whatever it wants with the value and destroy it when it is finished. In order to support this, the Mojo compiler is forced to make a temporary copy of the someValue value, and pass that value instead of someValue, because there may be other uses of someValue after the call.

    The Mojo compiler is now smart enough to detect when there are no uses of someValue later, and it will elide the copy just as if you had manually specified the transfer operator like takeValueAsOwned(someValue^). This provides a nice “it just works” behavior for non-trivial types without requiring manual management of transfers.

    If you’d like to take full control and expose full ownership for your type, just doesn’t make it copyable. Move-only types require the explicit transfer operator so you can see in your code where all ownership transfer happen.

  • Similarly, the Mojo compiler now transforms calls to __copyinit__ methods into calls to __moveinit__ when that is the last use of the source value along a control flow path. This allows types which are both copyable and movable to get transparent move optimization. For example, the following code is compiled into moves instead of copies even without the use of the transfer operator:

      var someValue = somethingCopyableAndMovable()
      let otherValue = someValue      # Last use of someValue
      var yetAnother = otherValue     # Last use of otherValue

    This is a significant performance optimization for things like PythonObject (and more complex value semantic types) that are commonly used in a fluid programming style. These don’t want extraneous reference counting operations performed by its copy constructor.

    If you want explicit control over copying, it is recommended to use a non-dunder .copy() method instead of __copyinit__, and recall that non-copyable types must always use of the transfer operator for those that want fully explicit behavior.

🛠️ Fixed

  • Issue #231 - Unexpected error when a Python expression raises an exception
  • Issue #119 - The REPL fails when a python variable is redefined


⭐️ New

  • finally clauses are now supported on try statements. In addition, try statements no longer require except clauses, allowing try-finally blocks. finally clauses contain code that is always executed from control-flow leaves any of the other clauses of a try statement by any means.

🦋 Changed

  • with statement emission changed to use the new finally logic so that

    with ContextMgr():

    Will correctly execute ContextMgr.__exit__ before returning.

🛠️ Fixed

  • Issue #204 - Mojo REPL crash when returning a String at compile-time
  • Issue #143 - synthesized init in @register_passable type doesn’t get correct convention.
  • Issue #201 - String literal concatenation is too eager.
  • Issue #209 - [QoI] Terrible error message trying to convert a type to itself.
  • Issue #32 - Include struct fields in docgen
  • Issue #50 - Int to string conversion crashes due to buffer overflow
  • Issue #132 - PythonObject to_int method has a misleading name
  • Issue #189 - PythonObject bool conversion is incorrect
  • Issue #65 - Add SIMD constructor from Bool
  • Issue #153 - Meaning of function result is unclear
  • Issue #165 - Type in documentation
  • Issue #210 - Parameter results cannot be declared outside top-level in function
  • Issue #214 - Pointer offset calculations at compile-time are incorrect
  • Issue #115 - Float printing does not include the right number of digits
  • Issue #202 - kgen.unreachable inside nested functions is illegal
  • Issue #235 - Crash when register passable struct field is not register passable
  • Issue #237 - Parameter closure sharp edges are not documented


⭐️ New

  • Added missing dunder methods to PythonObject, enabling the use of common arithmetic and logical operators on imported Python values.

  • PythonObject is now printable from Mojo, instead of requiring you to import Python’s print function.

🛠️ Fixed

  • Issue #98: Incorrect error with lifetime tracking in loop.

  • Issue #49: Type inference issue (?) in ‘ternary assignment’ operation (FloatLiteral vs. ‘SIMD[f32, 1]’).

  • Issue #48: and/or don’t work with memory-only types.

  • Issue #11: setitem Support for PythonObject.


⭐️ New

  • NDBuffer and Buffer are now constructable via Pointer and DTypePointer.

  • String now supports indexing with either integers or slices.

  • Added factorial function to the Math module.

🦋 Changed

  • The “byref” syntax with the & sigil has changed to use an inout keyword to be more similar to the borrowed and owned syntax in arguments. Please see Issue #7 for more information.

  • Optimized the Matrix multiplication implementation in the notebook. Initially we were optimizing for expandability rather than performance. We have found a way to get the best of both worlds and now the performance of the optimized Matmul implementation is 3x faster.

  • Renamed the ^ postfix operator from “consume” to “transfer.”

🛠️ Fixed

  • Fixed missing overloads for Testing.assertEqual so that they work on Integer and String values.

  • Issue #6: Playground stops evaluating cells when a simple generic is defined.

  • Issue #18: Memory leak in Python interoperability was removed.


📢 Released

⭐️ New

  • Added a Base64 encoding function to perform base64 encoding on strings.

🦋 Changed

  • Decreased memory usage of serialization of integers to strings.

  • Speedup the sort function.

🛠️ Fixed

  • Fixed time unit in the sleep function.

April 2023

Week of 2023-04-24

  • 📢 The default behavior of nested functions has been changed. Mojo nested functions that capture are by default are non-parametric, runtime closures, meaning that:

    def foo(x):
        # This:
        def bar(y): return x*y
        # Is the same as:
        let bar = lambda y: x*y

    These closures cannot have input or result parameters, because they are always materialized as runtime values. Values captured in the closure (x in the above example), are captured by copy: values with copy constructors cannot be copied and captures are immutable in the closure.

    Nested functions that don’t capture anything are by default “parametric” closures: they can have parameters and they can be used as parameter values. To restore the previous behavior for capturing closures, “parametric, capture-by-unsafe-reference closures”, tag the nested function with the @parameter decorator.

  • 📢 Mojo now has full support for “runtime” closures: nested functions that capture state materialized as runtime values. This includes taking the address of functions, indirect calls, and passing closures around through function arguments. Note that capture-by-reference is still unsafe!

    You can also take references to member functions with instances of that class using foo.member_function, which creates a closure with foo bound to the self argument.

  • 📢 Mojo now supports Python style with statements and context managers.

    These things are very helpful for implementing things like our trace region support and things like Runtime support.

    A context manager in Mojo implements three methods:

    fn __enter__(self) -> T:
    fn __exit__(self):
    fn __exit__(self, err: Error) -> Bool:

    The first is invoked when the context is entered, and returns a value that may optionally be bound to a target for use in the with body. If the with block exits normally, the second method is invoked to clean it up. If an error is raised, the third method is invoked with the Error value. If that method returns true, the error is considered handled, if it returns false, the error is re-thrown so propagation continues out of the ‘with’ block.

  • 📢 Mojo functions now support variable scopes! Explicit var and let declarations inside functions can shadow declarations from higher “scopes”, where a scope is defined as any new indentation block. In addition, the for loop iteration variable is now scoped to the loop body, so it is finally possible to write

    for i in range(1): pass
    for i in range(2): pass
  • 📢 Mojo now supports an @value decorator on structs to reduce boilerplate and encourage best practices in value semantics. The @value decorator looks to see the struct has a memberwise initializer (which has arguments for each field of the struct), a __copyinit__ method, and a __moveinit__ method, and synthesizes the missing ones if possible. For example, if you write:

    struct MyPet:
      var name: String
      var age: Int

    The @value decorator will synthesize the following members for you:

      fn __init__(inout self, owned name: String, age: Int): = name^
         self.age = age
      fn __copyinit__(inout self, existing: Self): =
         self.age = existing.age
      fn __moveinit__(inout self, owned existing: Self): =^
         self.age = existing.age

    This decorator can greatly reduce the boilerplate needed to define common aggregates, and gives you best practices in ownership management automatically. The @value decorator can be used with types that need custom copy constructors (your definition wins). We can explore having the decorator take arguments to further customize its behavior in the future.

  • 📚 Memcpy and memcmp now consistently use count as the byte count.

  • 📚 Add a variadic sting join on strings.

  • 📚 Introduce a reduce_bit_count method to count the number of 1 across all elements in a SIMD vector.

  • 📚 Optimize the pow function if the exponent is integral.

  • 📚 Add a len function which dispatches to __len__ across the different structs that support it.

Week of 2023-04-17

  • 📢 Error messages have been significantly improved, thanks to prettier printing for Mojo types in diagnostics.

  • 📢 Variadic values can now be indexed directly without wrapping them in a VariadicList!

  • 📢 let declarations in a function can now be lazily initialized, and var declarations that are never mutated get a warning suggesting they be converted to a let declaration. Lazy initialization allows more flexible patterns of initialization than requiring the initializer be inline, e.g.:

    let x : Int
    if cond:
        x = foo()
        x = bar()
  • 📢 Functions defined with def now return object by default, instead of None. This means you can return values (convertible to object) inside def functions without specifying a return type.

  • 📢 The @raises decorator has been removed. Raising fn should be declared by specifying raises after the function argument list. The rationale is that raises is part of the type system, instead of a function modifier.

  • 📢 The BoolLiteral type has been removed. Mojo now emits True and False directly as Bool.

  • 📢 Syntax for function types has been added. You can now write function types with fn(Int) -> String or async def(&String, *Int) -> None. No more writing !kgen.signature types by hand!

  • 📢 Float literals are not emitted as FloatLiteral instead of an MLIR f64 type!

  • 📢 Automatic destructors are now supported by Mojo types, currently spelled fn __del___(owned self): (the extra underscore will be dropped shortly). These destructors work like Python object destructors and similar to C++ destructors, with the major difference being that they run “as soon as possible” after the last use of a value. This means they are not suitable for use in C++-style RAII patterns (use the with statement for that, which is currently unsupported).

    These should be generally reliable for both memory-only and register-passable types, with the caveat that closures are known to not capture values correctly. Be very careful with interesting types in the vicinity of a closure!

  • A new (extremely dangerous!) builtin function is available for low-level ownership muckery. The __get_address_as_owned_value(x) builtin takes a low-level address value (of !pop.pointer type) and returns an owned value for the memory that is pointed to. This value is assumed live at the invocation of the builtin, but is “owned” so it needs to be consumed by the caller, otherwise it will be automatically destroyed. This is an effective way to do a “placement delete” on a pointer.

    # "Placement delete": destroy the initialized object begin pointed to.
    _ = __get_address_as_owned_value(somePointer.value)
    # Result value can be consumed by anything that takes it as an 'owned'
    # argument as well.
  • Another magic operator, named __get_address_as_uninit_lvalue(x) joins the magic LValue operator family. This operator projects a pointer to an LValue like __get_address_as_lvalue(x). The difference is that __get_address_as_uninit_lvalue(x) tells the compiler that the pointee is uninitialized on entry and initialized on exit, which means that you can use it as a “placement new” in C++ sense. __get_address_as_lvalue(x) tells the compiler that the pointee is initialized already, so reassigning over it will run the destructor.

    # "*Re*placement new": destroy the existing SomeHeavy value in the memory,
    # then initialize a new value into the slot.
    __get_address_as_lvalue(somePointer.value) = SomeHeavy(4, 5)
    # Ok to use an lvalue, convert to borrow etc.
    # "Placement new": Initialize a new value into uninitialied memory.
    __get_address_as_uninit_lvalue(somePointer.value) = SomeHeavy(4, 5)
    # Error, cannot read from uninitialized memory.

    Note that __get_address_as_lvalue assumes that there is already a value at the specified address, so the assignment above will run the SomeHeavy destructor (if any) before reassigning over the value.

  • 📢 Implement full support for __moveinit__ (aka move constructors)

    This implements the ability for memory-only types to define two different types of move ctors if they’d like:

    1. fn __moveinit__(inout self, owned existing: Self): Traditional Rust style moving constructors that shuffles data around while taking ownership of the source binding.
    2. fn __moveinit__(inout self, inout existing: Self):: C++ style “stealing” move constructors that can be used to take from an arbitrary lvalue.

    This gives us great expressive capability (better than Rust/C++/Swift) and composes naturally into our lifetime tracking and value categorization system.

  • The __call__ method of a callable type has been relaxed to take self by borrow, allow non-copyable callees to be called.

  • Implicit conversions are now invoked in raise statements properly, allowing converting strings to Error type.

  • Automatic destructors are turned on for __del__ instead of __del___.

  • 📚 Add the builtin FloatLiteral type.

  • 📚 Add integral floordiv and mod for the SIMD type that handle negative values.

  • 📚 Add an F64 to String converter.

  • 📚 Make the print function take variadic inputs.

Week of 2023-04-10

  • 📢 Introduce consume operator x^

    This introduces the postfix consume operator, which produces an rvalue given a lifetime tracked object (and, someday, a movable lvalue).

  • Mojo now automatically synthesizes empty destructor methods for certain types when needed.

  • The object type has been built out into a fully-dynamic type, with dynamic function dispatch, with full error handling support.

    def foo(a) -> object:
        return (a + 3.45) < [1, 2, 3] # raises a TypeError
  • 📢 The @always_inline decorator is no longer required for passing capturing closures as parameters, for both the functions themselves as functions with capturing closures in their parameters. These functions are still inlined but it is an implementation detail of capturing parameter closures. Mojo now distinguishes between capturing and non-capturing closures. Nested functions are capturing by default and can be made non-capturing with the @noncapturing decorator. A top-level function can be passed as a capturing closure by marking it with the @closure decorator.

  • 📢 Support for list literals has been added. List literals [1, 2, 3] generate a variadic heterogenous list type.

  • Variadics have been extended to work with memory-primary types.

  • Slice syntax is now fully-supported with a new builtin slice object, added to the compiler builtins. Slice indexing with a[1:2:3] now emits calls to __setitem__ and __getitem__ with a slice object.

  • Call syntax has been wired up to __call__. You can now f() on custom types!

  • Closures are now explicitly typed as capturing or non-captuiring. If a function intends to accept a capturing closure, it must specify the capturing function effect.

  • 📚 Add a Tile2D function to enable generic 2D tiling optimizations.

  • 📚 Add the slice struct to enable getting/setting spans of elements via getitem/setitem.

  • 📚 Add syntax sugar to autotuning for both specifying the autotuned values, searching, and declaring the evaluation function.

Week of 2023-04-03

  • The AnyType and NoneType aliases were added and auto-imported in all files.

  • 📢 The Mojo vscode plugin has been improved with docstring validation. It will now warn when a function’s docstring has a wrong argument name, for example.

  • 📢 A new built-in literal type TupleLiteral was added in _CompilerBuiltin. It represents literal tuple values such as (1, 2.0) or ().

  • 📢 The Int type has been moved to a new Builtin module and is auto-imported in all code. The type of integer literals has been changed from the MLIR index type to the Int type.

  • Mojo now has a powerful flow-sensitive uninitialized variable checker. This means that you need to initialize values before using them, even if you overwrite all subcomponents. This enables the compiler to reason about the true lifetime of values, which is an important stepping stone to getting automatic value destruction in place.

  • 📢 Call syntax support has been added. Now you can directly call an object that implements the __call__ method, like foo(5).

  • 📢 The name for copy constructors got renamed from __copy__ to __copyinit__. Furthermore, non-@register_passable types now implement it like they do an init method where you fill in a by-reference self, for example:

    fn __copyinit__(inout self, existing: Self):
        self.first = existing.first
        self.second = existing.second

    This makes copy construction work more similarly to initialization, and still keeps copies x = y distinct from initialization x = T(y).

  • 📢 Initializers for memory-primary types are now required to be in the form __init__(inout self, ...): with a None result type, but for register primary types, it remains in the form __init__(...) -> Self:. The T{} initializer syntax has been removed for memory-primary types.

  • Mojo String literals now emit a builtin StringLiteral type! One less MLIR type to worry about.

  • New __getattr__ and __setattr__ dunder methods were added. Mojo calls these methods on a type when attempting member lookup of a non-static member. This allows writing dynamic objects like where foo is not a member of x.

  • Early destructor support has been added. Types can now define a special destructor method __del___ (note three underscores). This is an early feature and it is still being built out. There are many caveats, bugs, and missing pieces. Stay tuned!

  • 📚 Integer division and mod have been corrected for rounding in the presence of negative numbers.

  • 📚 Add scalar types (UI8, SI32, F32, F64, etc.) which are aliases to SIMD[1, type].

March 2023

Week of 2023-03-27

  • 📢 Parameter names are no longer load-bearing in function signatures. This gives more flexibility in defining higher-order functions, because the functions passed as parameters do not need their parameter names to match.

    # Define a higher-order function...
    fn generator[
       func: __mlir_type[`!kgen.signature<`, Int, `>() -> !lit.none`]
    # Int parameter is named "foo".
    fn f0[foo: Int]():
    # Int parameter is named "bar".
    fn f1[bar: Int]():
    fn main():
       # Both can be used as `func`!

    Stay tuned for improved function type syntax…

  • 📢 Two magic operators, named __get_lvalue_as_address(x) and __get_address_as_lvalue convert stored LValues to and from !pop.pointer types (respectively). This is most useful when using the Pointer[T] library type. The Pointer.address_of(lvalue) method uses the first one internally. The second one must currently be used explicitly, and can be used to project a pointer to a reference that you can pass around and use as a self value, for example:

    # "Replacement new" SomeHeavy value into the memory pointed to by a
    # Pointer[SomeHeavy].
    __get_address_as_lvalue(somePointer.value) = SomeHeavy(4, 5)

    Note that __get_address_as_lvalue assumes that there is already a value at the specified address, so the assignment above will run the SomeHeavy destructor (if any) before reassigning over the value.

  • The (((x))) syntax is __mlir_op has been removed in favor of __get_lvalue_as_address which solves the same problem and is more general.

  • 📢 When using a mutable self argument to a struct __init__ method, it now must be declared with &, like any other mutable method. This clarifies the mutation model by making __init__ consistent with other mutating methods.

  • 📚 Add variadic string join function.

  • 📚 Default initialize values with 0 or null if possible.

  • 📚 Add compressed, aligned, and mask store intrinsics.

Week of 2023-03-20

  • Initial String type is added to the standard library with some very basic methods.

  • Add DimList to remove the need to use an MLIR list type throughout the standard library.

  • 📢 The __clone__ method for copying a value is now named __copy__ to better follow Python term of art.

  • 📢 The __copy__ method now takes its self argument as a “borrowed” value, instead of taking it by reference. This makes it easier to write, works for @register_passable types, and exposes more optimization opportunities to the early optimizer and dataflow analysis passes.

    # Before:
    fn __clone__(inout self) -> Self: ...
    # After:
    fn __copy__(self) -> Self: ...
  • 📢 A new @register_passable("trivial") may be applied to structs that have no need for a custom __copy__ or __del__ method, and whose state is only made up of @register_passable("trivial") types. This eliminates the need to define __copy__ boilerplate and reduces the amount of IR generated by the compiler for trivial types like Int.

  • You can now write back to attributes of structs that are produced by a computed lvalue expression. For example a[i].x = .. works when a[i] is produced with a __getitem__/__setitem__ call. This is implemented by performing a read of a[i], updating the temporary, then doing a writeback.

  • The remaining hurdles to using non-parametric, @register_passable types as parameter values have been cleared. Types like Int should enjoy full use as parameter values.

  • Parameter pack inference has been added to function calls. Calls to functions with parameter packs can now elide the pack types:

    fn foo[*Ts: AnyType](*args: *Ts): pass
    foo(1, 1.2, True, "hello")

    Note that the syntax for parameter packs has been changed as well.

  • 📚 Add the runtime string type.

  • 📚 Introduce the DimList struct to remove the need to use low-level MLIR operations.

Week of 2023-03-13

  • 📢 Initializers for structs now use __init__ instead of __new__, following standard practice in Python. You can write them in one of two styles, either traditional where you mutate self:

    fn __init__(self, x: Int):
        self.x = x

    or as a function that returns an instance:

    fn __init__(x: Int) -> Self:
        return Self {x: x}

    Note that @register_passable types must use the later style.

  • 📢 The default argument convention is now the borrowed convention. A “borrowed” argument is passed like a C++ const& so it doesn’t need to invoke the copy constructor (aka the __clone__ method) when passing a value to the function. There are two differences from C++ const&:

    1. A future borrow checker will make sure there are no mutable aliases with an immutable borrow.
    2. @register_passable values are passed directly in an SSA register (and thus, usually in a machine register) instead of using an extra reference wrapper. This is more efficient and is the ‘right default’ for @register_passable values like integers and pointers.

    This also paves the way to remove the reference requirement from __clone__ method arguments, which will allow us to fill in more support for them.

  • Support for variadic pack arguments has been added to Mojo. You can now write heterogenous variadic packs like:

    fn foo[*Ts: AnyType](args*: Ts): pass
    foo[Int, F32, String, Bool](1, 1.5, "hello", True)
  • The owned argument convention has been added. This argument convention indicates that the function takes ownership of the argument and is responsible for managing its lifetime.

  • The borrowed argument convention has been added. This convention signifies the callee gets an immutable shared reference to a value in the caller’s context.

  • 📚 Add the getenv function to the OS module to enable getting environment variables.

  • 📚 Enable the use of dynamic strides in NDBuffer.

Week of 2023-03-06

  • 📢 Support added for using capturing async functions as parameters.

  • 📢 Returning result parameters has been moved from return statements to a new param_return statement. This allows returning result parameters from throwing functions:

    fn foo[() -> out: Int]():
        raise Error()

    And returning different parameters along @parameter if branches:

    fn bar[in: Bool -> out: Int]():
        if in:
  • 📢 Mojo now supports omitting returns at the end of functions when they would not reachable. For instance,

    fn foo(cond: Bool) -> Int:
        if cond:
            return 0
            return 1
    fn bar() -> Int:
        while True:
  • String literals now support concatenation, so "hello " "world" is treated the same as "hello world".

  • Empty bodies on functions, structs, and control flow statements are no longer allowed. Please use pass in them to explicitly mark that they are empty, just like in Python.

  • 📢 Structs in Mojo now default to living in memory instead of being passed around in registers. This is the right default for generality (large structures, structures whose pointer identity matters, etc) and is a key technology that enables the borrow model. For simple types like Int and SIMD, they can be marked as @register_passable.

    Note that memory-only types currently have some limitations: they cannot be used in generic algorithms that take and return a !mlirtype argument, and they cannot be used in parameter expressions. Because of this, a lot of types have to be marked @register_passable just to work around the limitations. We expect to enable these use-cases over time.

  • 📢 Mojo now supports computed lvalues, which means you can finally assign to subscript expressions instead of having to call __setitem__ explicitly.

    Some details on this: Mojo allows you to define multiple __setitem__ overloads, but will pick the one that matches your __getitem__ type if present. It allows you to pass computed lvalues into inout arguments by introducing a temporary copy of the value in question.

  • Mojo now has much better support for using register-primary struct types in parameter expressions and as the types of parameter values. This will allow migration of many standard library types away from using bare MLIR types like __mlir_type.index and towards using Int. This moves us towards getting rid of MLIR types everywhere and makes struct types first-class citizens in the parameter system.

  • 📚 Add a sort function.

  • 📚 Add non-temporal store to enable cache bypass.

February 2023

Week of 2023-02-27

  • 📢 The @interface, @implements, and @evaluator trio of decorators have been removed, replaced by the @parameter if and @adaptive features.

  • 📢 Parameter inference can now infer the type of variadic lists.

  • 📢 Memory primary types are now supported in function results. A result slot is allocated in the caller, and the callee writes the result of the function into that slow. This is more efficient for large types that don’t fit into registers neatly! And initializers for memory-primary types now initialize the value in-place, instead of emitting a copy!

  • Support for let decls of memory primary types has been implemented. These are constant, ready-only values of memory primary types but which are allocated on the function stack.

  • Overload conversion resolution and parameter inference has been improved:

    1. Inference now works with let decls in some scenarios that weren’t working before.
    2. Parameter bindings can now infer types into parameter expressions. This helps resolve higher-order functions in parameter expressions.
  • 📚 Optimize floor, ceil, and ldexp on X86 hardware.

  • 📚 Implement the log math function.

Week of 2023-02-20

  • 📢 A new @__memory_primary struct decorator has been introduced. Memory primary types must always have an address. For instance, they are always stack-allocated when declared in a function and their values are passed into function calls by address instead of copy. This is in contract with register primary types that may not have an address, and which are passed by value in function calls. Memory-primary fields are not allowed inside register-primary structs, because struct elements are stored in-line.

  • 📢 A new _CompilerBuiltin module was added. This module defines core types and functions of the language that are referenced by the parser, and hence, is auto-imported by all other modules. For example new types for literal values like the boolean True/False will be included in _CompilerBuiltin.

  • 📢 A special __adaptive_set property can be accessed on a function reference marked as @adaptive. The property returns the adaptive overload set of that function. The return type is a !kgen.variadic. This feature is useful to implement a generic evaluate function in the standard library.

  • 📢 A new built-in literal type BoolLiteral was added in _CompilerBuiltin. It represents the literal boolean values True and False. This is the first Mojo literal to be emitted as a standard library type!

  • 📚 Add the prefetch intrinsic to enable HW prefetching a cache line.

  • 📚 Add the InlinedFixedVector which is optimized for small vectors and stores values on both the stack and the heap.

Week of 2023-02-13

  • Unqualified lookups of struct members apply contextual parameters. This means for instance that you can refer to static methods without binding the struct parameters.

    struct Foo[x: Int]:
        bar(): pass
            bar()         # implicitly binds to Foo[x].bar()
            Foo[2].bar()  # explicitly bind to another parameter
  • 📢 A new Self type refers to the enclosing type with all parameters bound to their current values. This is useful when working with complex parametric types, e.g.:

    struct MyArray[size: Int, element_type: type]:
       fn __new__() -> Self:
           return Self {...}

    which is a lot nicer than having to say MyArray[size, element_type] over and over again.

  • 📢 Mojo now supports an @adaptive decorator. This decorator will supercede interfaces, and it represents an overloaded function that is allowed to resolve to multiple valid candidates. In that case, the call is emitted as a fork, resulting in multiple function candidates to search over.

    fn sort(arr: ArraySlice[Int]):
    fn sort(arr: ArraySlice[Int]):
    fn concat_and_sort(lhs: ArraySlice[Int], rhs: ArraySlice[Int]):
        let arr = lhs + rhs
        sort(arr) # this forks compilation, creating two instances
                  # of the surrounding function
  • 📢 Mojo now requires that types implement the __clone__ special member in order to copy them. This allows the safe definition of non-copyable types like Atomic. Note that Mojo still doesn’t implement destructors, and (due to the absence of non-mutable references) it doesn’t actually invoke the __clone__ member when copying a let value. As such, this forces to you as a Mojo user to write maximal boilerplate without getting much value out of it.

    In the future, we will reduce the boilerplate with decorators, and we will actually start using it. This will take some time to build out though.

  • 📢 A special __mlir_region statement was added to provide stronger invariants around defining MLIR operation regions in Mojo. It similar syntax to function declarations, except it there are no results and no input conventions.

  • 📚 Implement the log math function.

  • 📚 Improve the DType struct to enable compile-time equality checks.

  • 📚 Add the Complex struct class.

Week of 2023-02-06

  • 📢 The if statement now supports a @parameter decorator, which requires its condition to be a parameter expression, but which only emits the ‘True’ side of the condition to the binary, providing a “static if” functionality. This should eliminate many uses of @interface that are just used to provide different constraint on the implementations.

  • 📢 fn main(): is now automatically exported and directly runnable by the command-line mojo tool. This is a stop-gap solution to enable script-like use cases until we have more of the language built out.

  • 🪦 The @nodebug_inline feature has been removed, please use @alwaysinline("nodebug") for methods that must be inlined and that we don’t want to step into.

  • 📢 Python chained comparisons, ex. a < b < c, are now supported in Mojo.

  • 📢 Functions can now be defined with default argument values, such as def f(x: Int, y: Int = 5):. The default argument value is used when callers do not provide a value for that argument: f(3), for example, uses the default argument value of y = 5.

  • Unused coroutine results are now nicely diagnosed as “missing await” warnings.

  • 📚 Introduce a vectorized reduction operations to the SIMD type.

January 2023

Week of 2023-01-30

  • A basic Mojo language server has been added to the vscode plugin, which parses your code as you write it, and provides warnings, errors, and fix-it suggestions!

  • 💯 The Mojo standard library is now implicitly imported by default.

  • The coroutine lowering support was reworked and a new Coroutine[T] type was implemented. Now, the result of a call to an async function MUST be wrapped in a Coroutine[T], or else memory will leak. In the future, when Mojo supports destructors and library types as literal types, the results of async function calls will automatically wrapped in a Coroutine[T]. But today, it must be done manually. This type implements all the expected hooks, such as __await__, and get() to retrieve the result. Typical usage:

    async fn add_three(a: Int, b: Int, c: Int) -> Int:
        return a + b + c
    async fn call_it():
        let task: Coroutine[Int] = add_three(1, 2, 3)
        print(await task)
  • ⭐️ We now diagnose unused expression values at statement context in fn declarations (but not in defs). This catches bugs with unused values, e.g. when you forget the parens to call a function.

  • 📢 An @always_inline("nodebug") function decorator can be used on functions that need to be force inlined, but when they should not have debug info in the result. This should be used on methods like Int.__add__ which should be treated as builtin.

  • 📢 The @export decorator now supports an explicit symbol name to export to, for example:

    @export("baz") # exported as 'baz'
    fn some_mojo_fn_name():
  • 📢 🚧 Subscript syntax is now wired up to the __getitem__ dunder method.

    This allows type authors to implement the __getitem__ method to enable values to be subscripted. This is an extended version of the Python semantics (given we support overloading) that allows you to define N indices instead of a single version that takes a tuple (also convenient because we don’t have tuples yet).

    Note that this has a very, very important limitation: subscripts are NOT wired up to __setitem__ yet. This means that you can read values with .. = v[i] but you cannot store to them with v[i] = ... For this, please continue to call __setitem__ directly.

  • 📢 Function calls support parameter inference.

    For calls to functions that have an insufficient number of parameters specified at the callsite, we can now infer them from the argument list. We do this by matching up the parallel type structure to infer what the parameters must be.

    Note that this works left to right in the parameter list, applying explicitly specified parameters before trying to infer new ones. This is similar to how C++ does things, which means that you may want to reorder the list of parameters with this in mind. For example, a dyn_cast-like function will be more elegant when implemented as:

    fn dyn_cast[DstType: type, SrcType: type](src: SrcType) -> DstType:

    Than with the SrcType/DstType parameters flipped around.

  • 📚 Add the growable Dynamic vector struct.

Week of 2023-01-23

  • Inplace operations like +=/__iadd__ may now take self by-val if they want to, instead of requiring it to be by-ref.

  • ⭐️ Inplace operations are no longer allowed to return a non-None value. The corresponding syntax is a statement, not an expression.

  • A new TaskGroup type was added to the standard library. This type can be used to schedule multiple tasks on a multi-threaded workqueue to be executed in parallel. An async function can await all the tasks at once with the taskgroup.

  • 📢 We now support for loops! A type that defines an __iter__ method that returns a type that defines __next__ and __len__ methods is eligible to be used in the statement for el in X(). Control flow exits the loop when the length is zero.

    This means things like this now work:

    for item in range(start, end, step):
  • Result parameters now have names. This is useful for referring to result parameters in the return types of a function:

    fn return_simd[() -> nelts: Int]() -> SIMD[f32, nelts]:
  • 📢 We now support homogenous variadics in value argument lists, using the standard Python fn thing(*args: Int): syntax! Variadics also have support in parameter lists:

    fn variadic_params_and_args[*a: Int](*b: Int):
  • 📚 Add the range struct to enable for ... range(...) loops.

  • 📚 Introduce the unroll generator to allow one to unroll loops via a library function.

Week of 2023-01-16

  • 📢 Struct field references are now supported in parameter context, so you can use someInt.value to get the underlying MLIR thing out of it. This should allow using first-class types in parameters more widely.

  • 📢 We now support “pretty” initialization syntax for structs, e.g.:

    struct Int:
        var value: __mlir_type.index
        fn __new__(value: __mlir_type.index) -> Int:
            return Int {value: value}

    This eliminates the need to directly use the MLIR lit.struct.create op in struct initializers. This syntax may change in the future when ownership comes in, because we will be able to support the standard __init__ model then.

  • 📢 It is now possible to attach regions to __mlir_op operations. This is done with a hack that allows an optional _region attribute that lists references to the region bodies (max 1 region right now due to lack of list [] literal).

  • Nested functions now parse, e.g.:

    fn foo():
        fn bar():
  • Python-style async functions should now work and the await expression prefix is now supported. This provides the joy of async/await syntactic sugar when working with asynchronous functions. This is still somewhat dangerous to use because we don’t have proper memory ownership support yet.

  • String literals are now supported.

  • Return processing is now handled by a dataflow pass inside the compiler, so it is possible to return early out of if statements.

  • The parser now supports generating ‘fixit’ hints on diagnostics, and uses them when a dictionary literal uses a colon instead of equal, e.g.:

    x.mojo:8:48: error: expected ':' in subscript slice, not '='
        return __mlir_op.`lit.struct.create`[value = 42]()
  • 📚 Add reduction methods which operate on buffers.

  • 📚 Add more math functions like sigmoid, sqrt, rsqrt, etc.

  • 📚 Add partial load / store which enable loads and stores that are predicated on a condition.

Week of 2023-01-09

  • The / and * markers in function signatures are now parsed and their invariants are checked. We do not yet support keyword arguments yet though, so they aren’t very useful.

  • Functions now support a new @nodebug_inline decorator. (Historical note: this was later replaced with @alwaysinline("nodebug")).

    Many of the things at the bottom level of the Mojo stack are trivial zero-abstraction wrappers around MLIR things, for example, the + operator on Int or the __bool__ method on Bool itself. These operators need to be force inlined even at -O0, but they have some additional things that we need to wrestle with:

    1. In no case would a user actually want to step into the bool method on Bool or the + method on Int. This would be terrible debugger QoI for unless you’re debugging Int itself. We need something like __always_inline__, __nodebug__ attributes that clang uses in headers like xmmintrin.h.

    2. Similarly, these “operators” should be treated by users as primitives: they don’t want to know about MLIR or internal implementation details of Int.

    3. These trivial zero abstraction things should be eliminated early in the compiler pipeline so they don’t slow down the compiler, bloating out the call graph with trivial leaves. Such thing slows down the elaborator, interferes with basic MLIR things like fold(), bloats out the IR, or bloats out generated debug info.

    4. In a parameter context, we want some of these things to get inlined so they can be simplified by the attribute logic and play more nicely with canonical types. This is just a nice to have thing those of us who have to stare at generated IR.

    The solution to this is a new @nodebug_inline decorator. This decorator causes the parser to force-inline the callee instead of generating a call to it. While doing so, it gives the operations the location of the call itself (that’s the “nodebug” part) and strips out let decls that were part of the internal implementation details.

    This is a super-power-user-feature intended for those building the standard library itself, so it is intentionally limited in power and scope: It can only be used on small functions, it doesn’t support regions, by-ref, throws, async, etc.

  • Separately, we now support an @alwaysInline decorator on functions. This is a general decorator that works on any function, and indicates that the function must be inlined. Unlike @nodebug_inline, this kind of inlining is performed later in the compilation pipeline.

  • The __include hack has been removed now that we have proper import support.

  • __mlir_op can now get address of l-value:

    You can use magic (((x))) syntax in __mlir_op that forces the x expression to be an lvalue, and yields its address. This provides an escape hatch (isolated off in __mlir_op land) that allows unsafe access to lvalue addresses.

  • We now support __rlshift__ and __rtruediv__.

  • 📢 The parser now resolves scoped alias references. This allows us to support things like SomeType.someAlias, forward substituting the value. This unblocks use of aliases in types like DType. We’d like to eventually preserve the reference in the AST, but this unblocks library development.

  • 📚 Add a now function and Benchmark struct to enable timing and benchmarking.

  • 📚 Move more of the computation in NDBuffer from runtime to compile time if possible (e.g. when the dimensions are known at compile time).

Week of 2023-01-02

  • 📚 Added the print function which works on Integers and SIMD values.

  • The frontend now has a new diagnostic subsystem used by the kgen tool (but not by kgen-translate for tests) that supports source ranges on diagnostics. Before we’d emit an error like:

    x.mojo:13:3: error: invalid call to 'callee': in argument #0, value of type '$F32::F32' cannot be converted to expected type '$Int::Int'
    x.lit:4:1: note: function declared here
    fn callee(a: Int):

    now we produce:

    x.mojo:13:3: error: invalid call to 'callee': in argument #0, value of type '$F32::F32' cannot be converted to expected type '$Int::Int'
      ^      ~~~~~~~~~~~~
    x.lit:4:1: note: function declared here
    fn callee(a: Int):
  • 📢 Parameter results are now supported in a proper way. They are now forward declared with an alias declaration and then bound in a call with an arrow, e.g.:

    alias a : __mlir_type.index
    alias b : __mlir_type.index
    idx_result_params[xyz*2 -> a, b]()
  • Various minor issues with implicit conversions are fixed. For instances, implicit conversions are now supported in parameter binding contexts and alias declarations with explicit types.

  • Doc strings are allowed on functions and structs, but they are currently discarded by the parser.

  • 📚 Add a print method!!!

  • 📚 Demonstrate a naive matmul in Mojo.

  • 📚 Initial work on functions that depend on types (e.g. FPUtils, nan, inf, etc.)

  • 📚 Allow one to query hardware properties such as simd_width, os, etc. via TargetInfo at compile time.

December 2022

Week of 2022-12-26

  • 📢 You can now call functions in a parameter context! Calling a function in a parameter context will evaluate the function at compile time. The result can then be used as parameter values. For example,

    fn fma(x: Int, y: Int, z: Int) -> Int:
        return a + b * c
    fn parameter_call():
        alias nelts = fma(32, 2, 16)
        var x: SIMD[f32, nelts]
  • You can now disable printing of types in an __mlir_attr substitution by using unary + expression.

  • 📢 let declarations are now supported in functions. let declarations are local run-time constant values, which are always rvalues. They complement ‘var’ decls (which are mutable lvalues) and are the normal thing to use in most cases. They also generate less IR and are always in SSA form when initialized.

    We will want to extend this to support ‘let’ decls in structs at some point and support lazy initialized ‘let’ declarations (using dataflow analysis) but that isn’t supported yet.

  • 📚 Add the NDBuffer struct.

  • Happy new year.

Week of 2022-12-19

  • 📚 Start of the Standard library:

    1. Added Integer and SIMD structs to bootstrap the standard library.
    2. Added very basic buffer data structure.
  • We have basic support for parsing parameter results in function calls! Result parameters are an important Mojo metaprogramming feature. They allow functions to return compile-time constants.

    fn get_preferred_simd_width[() -> nelts: Int]():
    fn vectorized_function():
        get_preferred_simd_width[() -> nelts]()
        var x: SIMD[f32, nelts]
  • Types can now be used as parameters of !kgen.mlirtype in many more cases.

  • MLIR operations with zero results don’t need to specify _type: [] anymore.

  • We support parsing triple quoted strings, for writing docstrings for your functions and structs!

  • A new __mlir_type[a,b,c] syntax is available for substituting into MLIR types and attributes is available, and the old placeholder approach is removed. This approach has a few advantages beyond what placeholders do:

    1. It’s simpler.
    2. It doesn’t form the intermediate result with placeholders, which gets rejected by MLIR’s semantic analysis, e.g. the complex case couldn’t be expressed before.
    3. It provides a simple way to break long attrs/types across multiple lines.
  • We now support an @evaluator decorator on functions for KGEN evaluators. This enables specifying user-defined interface evaluators when performing search during compilation.

  • 📢 import syntax is now supported!

    This handles packaging imported modules into file ops, enables effective isolation from the other decls. “import” into the desired context is just aliasing decls, with the proper symbols references handle automatically during IR generation. As a starting point, this doesn’t handle any notion of packages (as those haven’t been sketched out enough).

  • 📢 Reversed binary operators (like __radd__) are now looked up and used if the forward version (like __add__) doesn’t work for some reason.

  • 📢 Implicit conversions are now generally available, e.g. in assign statements, variable initializers etc. There are probably a few more places they should work, but we can start eliminating all the extraneous explicit casts from literals now.

  • Happy Holidays

Week of 2022-12-12

  • 📢 Function overloading now works. Call resolution filters candidate list according to the actual parameter and value argument specified at the site of the call, diagnosing an error if none of the candidates are viable or if multiple are viable and ambiguous. We also consider implicit conversions in overload look:

    fn foo(x: Int): pass
    fn foo(x: F64): pass
    foo(Int(1)) # resolves to the first overload
    foo(1.0)    # resolves to the second overload
    foo(1)      # error: both candidates viable with 1 implicit conversion!
  • The short circuiting binary and and or expressions are now supported.

  • Unary operator processing is a lot more robust, now handling the not expression and ~x on Bool.

  • 📢 The compiler now generates debug information for use with GDB/LLDB that describes variables and functions.

  • The first version of the Mojo vscode extension has been released! It supports syntax highlighting for Mojo files.

  • The first version of the Bool type has landed in the new Mojo standard library!

  • 📢 Implicit conversions are now supported in return statements.

Week of 2022-12-05

  • “Discard” patterns are now supported, e.g. _ = foo()

  • We now support implicit conversions in function call arguments, e.g. converting an index value to Int automatically. This eliminates a bunch of casts, e.g. the need to say F32(1.0) everywhere.

    This is limited for a few reasons that will be improved later:

    1. We don’t support overloading, so lots of types aren’t convertible from all the things they should be, e.g. you can’t pass “1” to something that expects F32, because F32 can’t be created from index.
    2. This doesn’t “check to see if we can invoke new” it force applies it on a mismatch, which leads to poor QoI.
    3. This doesn’t fix things that need radd.

November 2022

Week of 2022-11-28

  • 📢 We support the True and False keywords as expressions.

  • 📢 A new alias declaration is supported which allows defining local parameter values. This will eventually subsume type aliases and other things as it gets built out.

  • 📢 We now have end-to-end execution of Mojo files using the kgen tool! Functions exported with @export can be executed.

  • 📢 We have try-except-else and raise statements and implicit error propagation! The error semantics are that def can raise by default, but fn must explicitly declare raising with a @raises decorator. Stub out basic Error type.

  • The & sigil for by-ref arguments is now specified after the identifier. Postfix works better for ref and move operators on the expression side because it chains an mentally associates correctly: thing.method().result^. We don’t do that yet, but align param decl syntax to it so that things won’t be odd looking when we do. In practice this looks like:

    def mutate_argument(a&: index):
        a = 25

Week of 2022-11-21

  • 📢 The magic index type is gone. Long live __mlir_type.index.

  • Implement parameter substitution into parametric __mlir_type decls. This allows us to define parametric opaque MLIR types with exposed parameters using a new “placeholder” attribute. This allows us to expose the power of the KGEN type parametric system directly into Mojo.

  • 📢 Fully-parametric custom types can now be defined and work in Mojo, bringing together a lot of the recent work. We can write the SIMD type directly as a wrapper around the KGEN type, for example:

    struct SIMD[dt: __mlir_type.`!kgen.dtype`, nelts: __mlir_type.index]:
        var value:
          __mlir_type.`!pop.simd<#lit<placeholder index>,
                                 #lit<placeholder !kgen.dtype>>`[nelts, dt]
        fn __add__(self, rhs: SIMD[dt, nelts]) -> SIMD[dt, nelts]:
            return __mlir_op.`pop.add`(self.value, rhs.value)

Week of 2022-11-14

  • 📢 Implement a magic __mlir_type declaration that can be used to access any MLIR type. E.g. __mlir_type.f64.

  • 📢 Add an fn declaration. These are like def declarations, but are more strict in a few ways: they require type annotations on arguments, don’t allow implicit variable declarations in their body, and make their arguments rvalues instead of lvalues.

  • Implemented Swift-style backtick identifiers, which are useful for code migration where names may collide with new keywords.

  • 📢 A new __include directive has been added that performs source-level textual includes. This is temporary until we have an import model.

  • Implement IR generation for arithmetic operators like + and * in terms of the __add__ and __mul__ methods.

  • 📢 Added support for break and continue statements, as well as early returns inside loops and conditionals!

  • 📢 Implemented augmented assignment operators, like += and @=.

  • 📢 Mojo now has access to generating any MLIR operations (without regions) with a new __mlir_op magic declaration. We can start to build out the language’s builtin types with this:

    struct Int:
        var value: __mlir_type.index
        fn __add__(self, rhs: Int) -> Int:
            return __mlir_op.`index.add`(self.value, rhs.value)

    Attributes can be attached to the declaration with subscript [] syntax, and an explicit result type can be specified with a special _type attribute if it cannot be inferred. Attributes can be accessed via the __mlir_attr magic decl:

        _type: __mlir_type.i1,
        pred: __mlir_attr.`#index<cmp_predicate slt>`
    ](lhs, rhs)
  • Improved diagnostics emissions with ranges! Now errors highlight the whole section of code and not just the first character.

Week of 2022-11-07

  • Implemented the @interface and @implements decorators, which provide access to KGEN generator interfaces. A function marked as an @interface has no body, but it can be implemented by multiple other functions.

    def add(lhs: index, rhs: index):
    def normal_add(lhs: index, rhs: index) -> index:
        return lhs + rhs
    def slow_add(lhs: index, rhs: index) -> index:
        return normal_add(lhs, rhs)
  • 📢 Support for static struct methods and initializer syntax has been added. Initializing a struct with Foo() calls an implicitly static __new__ method. This method should be used instead of __init__ inside structs.

    struct Foo:
        var value: index
        def __new__() -> Foo:
            var result: Foo
            result.value = Foo.return_a_number() # static method!
            return result
        def return_a_number() -> index:
            return 42
  • 📢 Full by-ref argument support. It’s now possible to define in-place operators like __iadd__ and functions like swap(x, y) correctly.

  • 📢 Implemented support for field extract from rvalues, like x.value where x is not an lvalue (var declaration or by-ref function argument).

October 2022

Week of 2022-10-31

  • Revised return handling so that a return statement with no expression is syntax sugar for return None. This enables early exits in functions that implicitly return None to be cleaner:

    def just_return():
  • Added support for parsing more expressions: if-else, bitwise operators, shift operators, comparisons, floor division, remainder, and matmul.

  • 📢 The type of the self argument can now be omitted on member methods.

Week of 2022-10-24

  • Added parser support for right-associativity and unary ops, like the power operator a ** b ** c and negation operator -a.

  • Add support for &expr in Mojo, which allows denoting a by-ref argument in functions. This is required because the self type of a struct method is implicitly a pointer.

  • Implemented support for parametric function declarations, such as:

    struct SIMD[dt: DType, width: index]:
        fn struct_method(self: &SIMD[dt, width]):
    def fancy_add[dt: DType, width: index](
        lhs: SIMD[dt, width], rhs: SIMD[dt, width]) -> index:
      return width

Week of 2022-10-17

  • Added explicit variable declarations with var, for declaring variables both inside functions and structs, with support for type references. Added index as a temporary built-in type.

    def foo(lhs: index, rhs: index) -> index:
        var result: index = lhs + rhs
        return result
  • Implemented support for parsing struct declarations and references to type declarations in functions! In def, the type can be omitted to signal an object type.

    struct Foo:
        var member: index
    def bar(x: Foo, obj) -> index:
        return x.member
  • Implemented parser support for if statements and while loops!

    def if_stmt(c: index, a: index, b: index) -> index:
        var result: index = 0
        if c:
            result = a
            result = b
        return result
    def while_stmt(init: index):
        while init > 1:
            init = init - 1
  • Significantly improved error emission and handling, allowing the parser to emit multiple errors while parsing a file.

Week of 2022-10-10

  • Added support for parsing integer, float, and string literals.

  • Implemented parser support for function input parameters and results. You can now write parametric functions like,

    def foo[param: Int](arg: Int) -> Int:
        result = param + arg
        return result

Week of 2022-10-03

  • Added some basic parser scaffolding and initial parser productions, including trivial expressions and assignment parser productions.

  • Implemented basic scope handling and function IR generation, with support for forward declarations. Simple functions like,

    def foo(x: Int):

    Now parse! But all argument types are hard-coded to the MLIR index type.

  • Added IR emission for simple arithmetic expressions on builtin types, like x + y.

September 2022

Week of 2022-09-26

  • Mojo’s first patch to add a lexer was Sep 27, 2022.

  • Settled on [] for Mojo generics instead of <>. Square brackets are consistent with Python generics and don’t have the less than ambiguity other languages have.