Skip to main content

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, `>() -> !kgen.none`]
    ]():
       pass
    
    # Int parameter is named "foo".
    fn f0[foo: Int]():
       pass
    
    # Int parameter is named "bar".
    fn f1[bar: Int]():
       pass
    
    fn main():
       # Both can be used as `func`!
       generator[f0]()
       generator[f1]()

    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 !kgen.pointer types (respectively). This is most useful when using the Pointer[T] library type. The Pointer(to=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 "read" 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 "read" 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 heterogeneous 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:

    @raises
    fn foo[() -> out: Int]():
        param_return[42]
        raise Error()

    And returning different parameters along @parameter if branches:

    fn bar[in: Bool -> out: Int]():
        @parameter
        if in:
            param_return[1]
        else:
            param_return[2]
  • πŸ“’ 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
        else:
            return 1
    
    fn bar() -> Int:
        while True:
            pass
  • 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.

Was this page helpful?