Skip to main content

January 2023

Week of 2023-01-30​

  • A basic Mojo language server has been added to the VS Code extension, 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):
        print(item)
  • 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 homogeneous 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):
        print(a[0])
        print(b[1])
  • πŸ“š 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():
            pass
        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'
      callee(1.0+F32(2.0))
      ^
    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'
      callee(1.0+F32(2.0))
      ^      ~~~~~~~~~~~~
    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.

Was this page helpful?