Mojo🔥 changelog
This is a running list of significant changes for the Mojo language and tools. It doesn’t include all internal implementation changes.
Update Mojo
If you don’t have Mojo yet, see the get started guide.
To see your current Mojo version, run this:
mojo --version
To update Mojo to the latest release, run this:
modular update mojo
However, if your current version is 0.3.0 or lower, you’ll need these additional commands:
sudo apt-get update
sudo apt-get install modular
modular clean
modular install mojo
v0.6.0 (2023-12-04)
🔥 Legendary
Traits have arrived!
You can now define a trait, which consists of a required set of method prototypes. A struct can conform to the trait by implementing these methods. This lets you write generic functions that work on any structs that conform to a given trait.
The following section gives a brief overview of traits—see the Mojo Manual and this traits blog post for more details!
Traits are declared with the
trait
keyword. The bodies of traits should contain method signatures declared with...
as their bodies. Default method implementations are not supported yet.trait SomeTrait: fn required_method(self, x: Int): ...
The trait can be implemented on a struct by inheriting from it.
struct SomeStruct(SomeTrait): fn required_method(self, x: Int): print("hello traits", x)
You can then write a generic functions that accepts any type that conforms to the trait. You do this by creating a parameterized function with a trait-typed parameter:
fn fun_with_traits[T: SomeTrait](x: T): 42) x.required_method(
Which can be invoked with instances of types that conform to the trait:
var thing = SomeStruct() # Infer the parameter `T`! fun_with_traits(thing)
Traits can also inherit from other traits, which simply requires that implementors of the child trait also conform to all parent traits.
trait Parent: fn parent_func(self): ... trait Child(Parent): fn child_func(self): ...
Then, both child and parent trait methods can be invoked on instances of the trait
Child
. As well, an instance of the child trait can be converted to an instance of the parent trait.fn the_parents[T: Parent](x: T): x.parent_func() fn the_children[T: Child](x: T): x.child_func() x.parent_func()# Upcast `x` from instance of `Child` to `Parent`. the_parents(x)
For more information, see the Traits page in the Mojo Manual.
A fundamental
Destructable
trait has been added to the language. This is a core trait that every trait automatically conforms to. This enables destruction of generic types and generic collections.Note: We’re aware that this trait might be better spelled
Destructible
. We’re planning on removing it in the future and moving its functionality toAnyType
so that any type that doesn’t provide its own destructor will have a default, no-op destructor.We’ve added some traits to the standard library, you can implement these on your own types:
We added built-in
len()
,str()
, andint()
functions, which work with types that implement theSized
,Stringable
, andIntable
traits, respectively.DynamicVector
is now a proper generic collection that can use any type that implements theMovable
andCopyable
traits. This means you can now write, for example,DynamicVector[String]
. Also,DynamicVector
now invokes its element destructors upon destruction, so_del_old
has been deleted.print
now works on any types that implementStringable
by invoking their__str__
method:@value struct BoxedInt(Stringable): var value: Int fn __str__(self) -> String: return self.value print(BoxedInt(11), "hello traits!", BoxedInt(42))
⭐️ New
The Mojo Manual is an all-new, complete Mojo user guide. It doesn’t include everything about Mojo yet, but it includes a lot, and more than the original programming manual (now deprecated).
Plus, the entire Mojo Manual and other Mojo docs are now open-sourced on GitHub, and we’d love to accept contributions to help us improve them!
Mojo now supports partial automatic parameterization: when a function is declared with an argument of a partially bound type, the unbound parameters of that type are implicitly added to the function’s input parameters. For example:
@value struct Fudge[a: Int, b: Int, c: Int = 7]: ... # These function declarations are roughly equivalent: fn eat(f: Fudge[5]): ... # implicitly parameterized fn eat[_b: Int](f: Fudge[5, _b]): ... # explicitly parameterized
In the first signature for
eat()
, theb
parameter isn’t bound, so it’s implicitly added as an input parameter on the function.In the second signature for
eat()
, the author has explicitly defined an input parameter (_b
), which is bound to the second parameter on the argument type (which happens to beb
).Both functions can be called like this:
5, 8]()) eat(Fudge[
Mojo infers the value of the
b
parameter from the argument (in this case, 8).With the second signature, you can also pass the
_b
parameter value explicitly:3](Fudge[5, 3]()) eat[
Moreover, Mojo now allows you to explicitly mark parameters as unbound using the
_
as syntax meaning “placeholder for an unbound parameter.” For example:# These function declarations are roughly equivalent: fn eat(f: Fudge[5, _, c=_]): ... # implicitly parameterized fn eat(f: Fudge[c=_, a=5, b=_]): ... # implicitly parameterized fn eat[_b: Int, _c: Int](f: Fudge[5, _b, _c]): ... # explicitly parameterized
The first two signatures explicitly unbind the
b
andc
parameters.In the last signature, the
_b
and_c
parameters are explicitly declared by the author, and bound to theb
andc
parameters in the argument type.Any of these signatures can be called like this:
5, 8]()) eat(Fudge[5, 8, 9]()) eat(Fudge[
Note that the default parameter values of struct parameters are bound, unless explicitly unbound by the user.
For more information, see the Mojo Manual.
Parametric types can now be partially bound in certain contexts. For example, a new
Scalar
type alias has been added defined as:alias Scalar = SIMD[size=1]
Which creates a parametric type alias
Scalar
with a single parameter of typeDType
. Types can also be partially or fully bound in other contexts. For instance,alias
declarations of type values inside functions now work properly:fn type_aliases(): alias T = SIMD print(T[DType.float32, 1]()) alias Partial = T[type=DType.int32] print(Partial[2]())
The
__mlir_op
feature now supports operations that return multiple results. To use them, you write the_type
field as aTuple
of types. For example:# The `ret` variable has type `Tuple[Int, Int]`. let ret = __mlir_op.`multi_result_op`[ _type = (Int, Int) ]()
Mojo now has the ability to read raw bytes from a file using the
read_bytes()
method. For example:with open("file.binary", "r") as f: = f.read_bytes() data
A size argument was added to the
read()
andread_bytes()
methods on the builtinfile.FileHandle
. The size argument defaults to -1 and maintains the previous “read to EOF” behavior when size is negative.with open("file.binary", "r") as f: = f.read_bytes(1024) data1 = f.read_bytes(256) data2
Path
now hasread_bytes()
andread_text()
methods to read file contents from a path:let text_path = Path("file.txt") let text = text_path.read_text() let binary_path = Path("file.binary") let data = binary_path.read_bytes()
Tensor
has newsave()
andload()
methods to save and load to file. These methods preserve shape and datatype information. For example:let tensor = Tensor[DType.float32]() tensor.save(path) let tensor_from_file = Tensor[DType.float32].load(path)
Subscripting added to
DTypePointer
andPointer
:let p = DTypePointer[DType.float16].alloc(4) for i in range(4): = i p[i] print(p[i])
file.FileHandle
now has aseek()
method.String
now has anrfind()
method analogous to Python’sstr.rfind()
.String
now has ansplit()
method analogous to Python’sstr.split()
.Path
now has asuffix()
method analogous to Python’spathlib.Path.suffix
.The Mojo REPL now supports indented expressions, making it a bit easier to execute expressions copied from an indented block (such as a doc string).
The Mojo Language Server now implements the Document Symbols request. IDEs use this to provide support for Outline View and Go to Symbol. This addresses Issue #960.
The Mojo Language Server now shows documentation when code completing modules or packages in
import
statements.The Mojo Language Server now supports processing code examples, defined as markdown Mojo code blocks, inside of doc strings. This enables IDE features while writing examples in API documentation.
The Mojo Language Server now provides semantic token information, providing better highlighting for symbols whose semantics are not statically analyzable.
The Mojo Language Server now classifies doc strings as folding ranges, making them easier to collapse, reducing vertical space while editing.
Command line options for the
mojo
driver that take arguments can now be written in either of two ways: both--foo FOO
and--foo=FOO
. Previously, only the former was valid.
🦋 Changed
Variadic list types
VariadicList
andVariadicListMem
are now iterable. Variadic arguments are automatically projected into one of these types inside the function body, so var args can be iterated:fn print_ints(*nums: Int): for num in nums: print(num) print(len(nums))
The assert functions in the
testing
package now raise anError
when the assertion fails instead of returning aBool
for whether the assertion succeeded or not.Parameters of
AnyType
type are no longer (implicitly) assumed to be register-passable. A newAnyRegType
type is used to represent generic types that are register passable.Changing the units in a
benchmark
report is now an argument instead of a parameter:let report = benchmark.run[timer]() print(Unit.ms) report.
Default values on
inout
arguments are no longer permitted, i.e. the following will now raise an error:fn inout_default(inout x: Int = 2): ...
The
to_string()
function has been removed fromPythonObject
in favor of the new__str__()
function. This composes better with traits so it can be used with the genericstr()
function.
🛠️ Fixed
#734 - Consumption of struct works only for types with a
__del__
method.#910 - Parser crash when using memory-only generic type as return of function that
raise
s.#1060 - Mojo happily parses code that has messed up indentation
#1159 - The language server doesn’t warn about bad return type.
#1166 - warning: unreachable code after return statement with context manager
#1098 - The language server doesn’t highlight properties of PythonObjects correctly.
#1153 - The language server crashes when parsing an invalid multi-nested module import.
#1236 - The language server doesn’t show autocomplete in if statements.
#1246 - Warning diagnostics are transient in the presence of caching.
Known Issue
- There is an issue affecting Jupyter notebooks that use autotuning and traits. This issue only manifests on macOS, and the same code runs without issue outside of the notebooks. This issue affects the Matrix multiplication in Mojo notebook.
v0.5.0 (2023-11-2)
⭐️ New
The
SIMD
type now defaults to the architectural SIMD width of the type. This means you can writeSIMD[DType.float32]
which is equivalent toSIMD[DType.float32, simdwidthof[DType.float32]()]
.The
SIMD
type now contains ajoin()
function that allows you to concatenate twoSIMD
values together and produce a newSIMD
value.Mojo now supports compile-time keyword parameters, in addition to existing support for keyword arguments. For example:
fn foo[a: Int, b: Int = 42](): print(a, "+", b) =5]() # prints '5 + 42' foo[a=7, b=13]() # prints '7 + 13' foo[a=20, a=6]() # prints '6 + 20' foo[b
Keyword parameters are also supported in structs:
struct KwParamStruct[a: Int, msg: String = "🔥mojo🔥"]: fn __init__(inout self): print(msg, a) fn use_kw_params(): =42]() # prints '🔥mojo🔥 42' KwParamStruct[a5, msg="hello"]() # prints 'hello 5' KwParamStruct[="hello", a=42]() # prints 'hello 42' KwParamStruct[msg
For more detail, see the programming manual.
For the time being, the following notable limitations apply:
Keyword-only parameters are not supported yet:
fn baz[*args: Int, b: Int](): pass # fails fn baz[a: Int, *, b: Int](): pass # fails
(The analogous keyword-only arguments in Python are described in PEP 3102.)
Variadic keyword parameters are not supported yet:
fn baz[a: Int, **kwargs: Int](): pass # fails
Mojo now supports “automatic” parameterization of functions. What this means is that if a function argument type is parametric but has no bound parameters, they are automatically added as input parameters on the function. This works with existing features to allow you to write parametric functions with less boilerplate.
@value struct Thing[x: Int, y: Int]: pass fn foo(v: Thing): print(v.x) print(v.y) fn main(): let v = Thing[2, 3]() foo(v)
However, partial autoparameterization is not supported yet:
fn foo(v: Thing[y=7]): # Partially bound type not allowed yet. ...
Keyword argument passing is supported when invoking
__getitem__
using the bracket syntax:@value struct MyStruct: fn __getitem__(self, x: Int, y: Int, z: Int) -> Int: return x * y + z =7, x=3, y=5] # returns 22 MyStruct()[z
However, keyword argument passing to
__setitem__
using the bracket syntax is not supported yet:@value struct OtherStruct: fn __setitem__(self, x: Int, y: Int): pass =1] = 4 # fails OtherStruct()[x
Function argument input parameters can now be referenced within the signature of the function:
fn foo(x: SIMD, y: SIMD[x.type, x.size]): pass
The
benchmark
module has been simplified and improved so you can now run:import benchmark from time import sleep fn sleeper(): .01) sleep( fn main(): let report = benchmark.run[sleeper]() print(report.mean())
It no longer requires a capturing
fn
so can benchmark functions outside the same scope.You can print a report with:
print() report.
--------------------- Benchmark Report (s) --------------------- Mean: 0.012314264957264957 Total: 1.440769 Iters: 117 Warmup Mean: 0.0119335 Warmup Total: 0.023866999999999999 Warmup Iters: 2 Fastest Mean: 0.012227958333333334 Slowest Mean: 0.012442699999999999
Units for all functions default to seconds, but can be changed with:
from benchmark import Unit print[Unit.ms]() report.
Mojo now supports struct parameter deduction (a.k.a. class template argument deduction, or CTAD) for partially bound types. Struct parameter deduction is also possible from static methods. For example:
@value struct Thing[v: Int]: pass struct CtadStructWithDefault[a: Int, b: Int, c: Int = 8]: fn __init__(inout self, x: Thing[a]): print("hello", a, b, c) @staticmethod fn foo(x: Thing[a]): print("🔥", a, b, c) fn main(): = CtadStructWithDefault[b=7](Thing[6]()) # prints 'hello 6 7 8' _ =7].foo(Thing[6]()) # prints '🔥 6 7 8' CtadStructWithDefault[b
Tensor
has newfromfile()
andtofile()
methods to save and load as bytes from a file.The built-in
print()
function now works on theTensor
type.TensorShape
andTensorSpec
now have constructors that takeDynamicVector[Int]
andStaticIntTuple
to initialize shapes.The
String
type now has thecount()
andfind()
methods to enable counting the number of occurrences or finding the offset index of a substring in a string.The
String
type now has areplace()
method which allows you to replace a substring with another string.
🦋 Changed
VariadicList
andVariadicListMem
moved under builtins, and no longer need to be imported.Variadic arguments are now automatically projected into a
VariadicList
orVariadicListMem
inside the function body. This allows for more flexibility in using var args. For example:fn print_ints(*nums: Int): let len = len(nums) for i in range(len): print(nums[i]) print(len)
The parameters for
InlinedFixedVector
have been switched. The parameters are now[type, size]
instead of[size, type]
. TheInlinedFixedVector
now has a default size which means that one can just useInlinedFixedVector
asInlinedFixedVector[Float32]
and the default size is used.write_file()
method inBuffer
andNDBuffer
is renamed totofile()
to match the Python naming.Mojo will now utilize all available cores across all NUMA sockets on the host machine by default. The prior default behavior was to use all the cores on the first socket.
❌ Removed
- The
math.numerics
module is now private, because its types (FPUtils
andFlushDenormals
) should not be used externally.
🛠️ Fixed
- #532 - Compiler optimizing while True loop away
- #760 - Compilation error: ‘hlcf.for.yield’ op specifies 0 branch inputs but target expected 1 along control-flow edge from here
- #849 - The
Tensor
type is now initialized with zeros at construction time. - #912 - Invalid load for
__get_address_as_lvalue
. - #916 - Parser crash when specifying default values for
inout
arguments. - #943 - Mojo hangs if you use continue in the nested loop
- #957 - Parser crash when a function call with variadic arguments of a memory-only type is evaluated at compile time.
- #990 - Fixes rounding issue with floor division with negative numerator.
- #1018 - In some cases the sort function was returning invalid results. This release fixes some of these corner cases.
- #1010 - Initializing tensor in alias declaration results in crash.
- #1110 - The
time.now()
function now returns nanoseconds across all operating systems. - #1115 - cannot load non-register passable type into SSA register.
v0.4.0 for Mac (2023-10-19)
🔥 Legendary
Mojo for Mac!
The Mojo SDK now works on macOS (Apple silicon). This is the same version previously released for Linux. Get the latest version of the SDK for your Mac system:
v0.4.0 (2023-10-05)
⭐️ New
Mojo now supports default parameter values. For example:
fn foo[a: Int = 3, msg: StringLiteral = "woof"](): print(msg, a) fn main(): # prints 'woof 3' foo() 5]() # prints 'woof 5' foo[7, "meow"]() # prints 'meow 7' foo[
Inferred parameter values take precedence over defaults:
@value struct Bar[v: Int]: pass fn foo[a: Int = 42, msg: StringLiteral = "quack"](bar: Bar[a]): print(msg, a) fn main(): 9]()) # prints 'quack 9' foo(Bar[
Structs also support default parameters:
@value struct DefaultParams[msg: StringLiteral = "woof"]: alias message = msg fn main(): print(DefaultParams[]().message) # prints 'woof' print(DefaultParams["meow"]().message) # prints 'meow'
The new
file
module adds basic file I/O support. You can now write:var f = open("my_file.txt", "r") print(f.read()) f.close()
or
with open("my_file.txt", "r") as f: print(f.read())
Mojo now allows context managers to support an
__enter__
method without implementing support for an__exit__
method, enabling idioms like this:# This context manager consumes itself and returns it as the value. fn __enter__(owned self) -> Self: return self^
Here Mojo cannot invoke a noop
__exit__
method because the context manager is consumed by the__enter__
method. This can be used for types (like file descriptors) that are traditionally used withwith
statements, even though Mojo’s guaranteed early destruction doesn’t require that.A very basic version of
pathlib
has been implemented in Mojo. The module will be improved to achieve functional parity with Python in the next few releases.The
memory.unsafe
module now contains abitcast
function. This is a low-level operation that enables bitcasting between pointers and scalars.The input parameters of a parametric type can now be directly accessed as attribute references on the type or variables of the type. For example:
@value struct Thing[param: Int]: pass fn main(): print(Thing[2].param) # prints '2' let x = Thing[9]() print(x.param) # prints '9'
Input parameters on values can even be accessed in parameter contexts. For example:
fn foo[value: Int](): print(value) let y = Thing[12]() alias constant = y.param + 4 # prints '16' foo[constant]()
The Mojo REPL now supports code completion. Press Tab while typing to query potential completion results.
Error messages from Python are now exposed in Mojo. For example the following should print
No module named 'my_uninstalled_module'
:fn main(): try: let my_module = Python.import_module("my_uninstalled_module") except e: print(e)
Error messages can now store dynamic messages. For example, the following should print “Failed on: Hello”
fn foo(x:String) raises: raise Error("Failed on: " + x) fn main(): try: "Hello") foo(except e: print(e)
🦋 Changed
We have improved and simplified the
parallelize
function. The function now elides some overhead by caching the Mojo parallel runtime.The Mojo REPL and Jupyter environments no longer implicitly expose
Python
,PythonObject
, orPointer
. These symbols must now be imported explicitly, for example:from python import Python from python.object import PythonObject from memory.unsafe import Pointer
The syntax for specifying attributes with the
__mlir_op
prefix have changed to mimic Python’s keyword argument passing syntax. That is,=
should be used instead of:
, e.g.:# Old syntax, now fails. __mlir_op.`index.bool.constant`[value : __mlir_attr.`false`]() # New syntax. __mlir_op.`index.bool.constant`[value=__mlir_attr.`false`]()
You can now print the
Error
object directly. Themessage()
method has been removed.
🛠️ Fixed
- #794 - Parser crash when using the
in
operator. - #936 - The
Int
constructor now accepts otherInt
instances. - #921 - Better error message when running
mojo
on a module with nomain
function. - #556 - UInt64s are now printed correctly.
- #804 - Emit error instead of crashing when passing variadic arguments of unsupported types.
- #833 - Parser crash when assigning module value.
- #752 - Parser crash when calling async def.
- #711 - The overload resolution logic now correctly prioritizes instance methods over static methods (if candidates are an equally good match otherwise), and no longer crashed if a static method has a
Self
type as its first argument. - #859 - Fix confusing error and documentation of the
rebind
builtin. - #753 - Direct use of LLVM dialect produces strange errors in the compiler.
- #926 - Fixes an issue that occurred when a function with a return type of
StringRef
raised an error. When the function raised an error, it incorrectly returned the string value of that error. - #536 - Report More information on python exception.
v0.3.1 (2023-09-28)
Our first-ever patch release of the Mojo SDK is here! Release v0.3.1 includes primarily installation-related fixes. If you’ve had trouble installing the previous versions of the SDK, this release may be for you.
🛠️ Fixed
- #538 - Installation hangs during the testing phase. This issue occurs on machines with a low number of CPU cores, such as free AWS EC2 instances and GitHub Codespaces.
- #590 - Installation fails with a “failed to run python” message.
- #672 - Language server hangs on code completion. Related to #538, this occurs on machines with a low number of CPU cores.
- #913 - In the REPL and Jupyter notebooks, inline comments were being parsed incorrectly.
v0.3.0 (2023-09-21)
There’s more Mojo to love in this, the second release of the Mojo SDK! This release includes new features, an API change, and bug fixes.
There’s also an updated version of the Mojo extension for VS Code.
⭐️ New
Mojo now has partial support for passing keyword arguments to functions and methods. For example the following should work:
fn foo(a: Int, b: Int = 3) -> Int: return a * b fn main(): print(foo(6, b=7)) # prints '42' print(foo(a=6, b=7)) # prints '42' print(foo(b=7, a=6)) # prints '42'
Parameters can also be inferred from keyword arguments, for example:
fn bar[A: AnyType, B: AnyType](a: A, b: B): print("Hello 🔥") fn bar[B: AnyType](a: StringLiteral, b: B): print(a) fn main(): 1, 2) # prints `Hello 🔥` bar(=2, a="Yay!") # prints `Yay!` bar(b
For the time being, the following notable limitations apply:
Keyword-only arguments are not supported:
fn baz(*args: Int, b: Int): pass # fails fn baz(a: Int, *, b: Int): pass # fails
(Keyword-only arguments are described in PEP 3102.)
Variadic keyword arguments are not supported:
fn baz(a: Int, **kwargs: Int): pass # fails
Mojo now supports the
@nonmaterializable
decorator. The purpose is to mark data types that should only exist in the parameter domain. To use it, a struct is decorated with@nonmaterializable(TargetType)
. Any time the nonmaterializable type is converted from the parameter domain, it is automatically converted toTargetType
. A nonmaterializable struct should have all of its methods annotated as@always_inline
, and must be computable in the parameter domain. In the following example, theNmStruct
type can be added in the parameter domain, but are converted toHasBool
when materialized.@value @register_passable("trivial") struct HasBool: var x: Bool fn __init__(x: Bool) -> Self: return Self {x: x} @always_inline("nodebug") fn __init__(nms: NmStruct) -> Self: return Self {x: True if (nms.x == 77) else False} @value @nonmaterializable(HasBool) @register_passable("trivial") struct NmStruct: var x: Int @always_inline("nodebug") fn __add__(self: Self, rhs: Self) -> Self: return NmStruct(self.x + rhs.x) alias stillNmStruct = NmStruct(1) + NmStruct(2) # When materializing to a run-time variable, it is automatically converted, # even without a type annotation. let convertedToHasBool = stillNmStruct
Mojo integer literals now produce the
IntLiteral
infinite precision integer type when used in the parameter domain.IntLiteral
is materialized to theInt
type for runtime computation, but intermediate computations at compile time, using supported operators, can now exceed the bit width of theInt
type.The Mojo Language Server now supports top-level code completions, enabling completion when typing a reference to a variable, type, etc. This resolves #679.
The Mojo REPL now colorizes the resultant variables to help distinguish input expressions from the output variables.
🦋 Changed
Mojo allows types to implement two forms of move constructors, one that is invoked when the lifetime of one value ends, and one that is invoked if the compiler cannot prove that. These were previously both named
__moveinit__
, with the following two signatures:fn __moveinit__(inout self, owned existing: Self): ... fn __moveinit__(inout self, inout existing: Self): ...
We’ve changed the second form to get its own name to make it more clear that these are two separate operations: the second has been renamed to
__takeinit__
:fn __moveinit__(inout self, owned existing: Self): ... fn __takeinit__(inout self, inout existing: Self): ...
The name is intended to connote that the operation takes the conceptual value from the source (without destroying it) unlike the first one which “moves” a value from one location to another.
For more information, see the Mojo Manual section on move constructors.
The Error type in Mojo has changed. Instead of extracting the error message using
error.value
you will now extract the error message usingerror.message()
.
🛠️ Fixed
- #503 - Improve error message for failure lowering
kgen.param.constant
. - #554 - Alias of static tuple fails to expand.
- #500 - Call expansion failed due to verifier error.
- #422 - Incorrect comment detection in multiline strings.
- #729 - Improve messaging on how to exit the REPL.
- #756 - Fix initialization errors of the VS Code extension.
- #575 - Build LLDB/REPL with libedit for a nicer editing experience in the terminal.
v0.2.1 (2023-09-07)
The first versioned release of Mojo! 🔥
All earlier releases were considered version 0.1.
🔥 Legendary
First release of the Mojo SDK!
You can now develop with Mojo locally. The Mojo SDK is currently available for Ubuntu Linux systems, and support for Windows and macOS is coming soon. You can still develop from a Windows or Mac computer using a container or remote Linux system.
The Mojo SDK includes the Mojo standard library and the Mojo command-line interface (CLI), which allows you to run, compile, and package Mojo code. It also provides a REPL programming environment.
First release of the Mojo extension for VS Code.
This provides essential Mojo language features in Visual Studio Code, such as code completion, code quick fixes, docs tooltips, and more. Even when developing on a remote system, using VS Code with this extension provides a native-like IDE experience.
⭐️ New
A new
clobber_memory
function has been added to thebenchmark
module. The clobber memory function tells the system to flush all memory operations at the specified program point. This allows you to benchmark operations without the compiler reordering memory operations.A new
keep
function has been added to thebenchmark
module. Thekeep
function tries to tell the compiler not to optimize the variable away if not used. This allows you to avoid compiler’s dead code elimination mechanism, with a low footprint side effect.New
shift_right
andshift_left
functions have been added to thesimd
module. They shift the elements in a SIMD vector right/left, filling elements with zeros as needed.A new
cumsum
function has been added to thereduction
module that computes the cumulative sum (also known as scan) of input elements.Mojo Jupyter kernel now supports code completion.
🦋 Changed
- Extends
rotate_bits_left
,rotate_left
,rotate_bits_right
, androtate_right
to operate on Int values. The ordering of parameters has also been changed to enable type inference. Now it’s possible to writerotate_right[shift_val](simd_val)
and have thedtype
andsimd_width
inferred from the argument. This addresses Issue #528.
🛠️ Fixed
Fixed a bug causing the parser to crash when the
with
statement was written without a colon. This addresses Issue #529.Incorrect imports no longer crash when there are other errors at the top level of a module. This fixes Issue #531.
August 2023
2023-08-24
- Fixed issue where the
with expr as x
statement withinfn
behaved as if it were in adef
, bindingx
with function scope instead of using lexical scope.
⭐️ New
Major refactoring of the standard library to enable packaging and better import ergonomics:
- The packages are built as binaries to improve startup speed.
- Package and module names are now lowercase to align with the Python style.
- Modules have been moved to better reflect the purpose of the underlying functions (e.g.
Pointer
is now within theunsafe
module in thememory
package). - The following modules are now included as built-ins:
SIMD
,DType
,IO
,Object
, andString
. This means it’s no longer necessary to explicitly import these modules. Instead, these modules will be implicitly imported for the user. Private methods within the module are still accessible using thebuiltin.module_name._private_method
import syntax. - New
math
package has been added to contain thebit
,math
,numerics
, andpolynomial
modules. The contents of themath.math
module are re-exported into themath
package.
Mojo now supports using memory-only types in parameter expressions and as function or type parameters:
@value struct IntPair: var first: Int var second: Int fn add_them[value: IntPair]() -> Int: return value.first + value.second fn main(): print(add_them[IntPair(1, 2)]()) # prints '3'
In addition, Mojo supports evaluating code that uses heap-allocated memory at compile-time and materializing compile-time values with heap-allocated memory into dynamic values:
fn fillVector(lowerBound: Int, upperBound: Int, step: Int) -> DynamicVector[Int]: var result = DynamicVector[Int]() for i in range(lowerBound, upperBound, step): result.push_back(i)return result fn main(): alias values = fillVector(5, 23, 7) for i in range(0, values.__len__()): print(values[i]) # prints '5', '12', and then '19'
🦋 Changed
def main():
, without the explicitNone
type, can now be used to define the entry point to a Mojo program.The
assert_param
function has been renamed toconstrained
and is now a built-in function.The
print
function now works onComplex
values.
🛠️ Fixed
- Fixed issues with print formatting for
DType.uint16
andDType.int16
. - Issue #499 - Two new
rotate_right
androtate_left
functions have been added to the SIMD module. - Issue #429 - You can now construct a
Bool
from aSIMD
type whose element-type isDType.bool
. - Issue #350 - Confusing Matrix implementation
- Issue #349 - Missing load_tr in struct Matrix
- Issue #501 - Missing syntax error messages in Python expressions.
2023-08-09
🦋 Changed
The
ref
andmutref
identifiers are now treated as keywords, which means they cannot be used as variable, attribute, or function names. These keywords are used by the “lifetimes” features, which is still in development. We can consider renaming these (as well as other related keywords) when the development work gels, support is enabled in public Mojo builds, and when we have experience using them.The argument handling in
def
functions has changed: previously, they had special behavior that involved mutable copies in the callee. Now, we have a simple rule, which is thatdef
argument default to theowned
convention (fn
arguments still default to theborrowed
convention).This change is mostly an internal cleanup and simplification of the compiler and argument model, but does enable one niche use-case: you can now pass non-copyable types to
def
arguments by transferring ownership of a value into thedef
call. Before, that would not be possible because the copy was made on the callee side, not the caller’s side. This also allows the explicit use of theborrowed
keyword with adef
that wants to opt-in to that behavior.
2023-08-03
⭐️ New
A new
Tensor
type has been introduced. This tensor type manages its own data (unlikeNDBuffer
andBuffer
which are just views). Therefore, the tensor type performs its own allocation and free. Here is a simple example of using the tensor type to represent an RGB image and convert it to grayscale:from tensor import Tensor, TensorShape from utils.index import Index from random import rand let height = 256 let width = 256 let channels = 3 # Create the tensor of dimensions height, width, channels and fill with # random value. let image = rand[DType.float32](height, width, channels) # Declare the grayscale image. var gray_scale_image = Tensor[DType.float32](height, width) # Perform the RGB to grayscale transform. for y in range(height): for x in range(width): let r = image[y,x,0] let g = image[y,x,1] let b = image[y,x,2] = 0.299 * r + 0.587 * g + 0.114 * b gray_scale_image[Index(y,x)]
🛠️ Fixed
- Issue #53 -
Int
now implements true division with the/
operator. Similar to Python, this returns a 64-bit floating point number. The corresponding in-place operator,/=
, has the same semantics as//=
.
July 2023
2023-07-26
⭐️ New
Types that define both
__getitem__
and__setitem__
(i.e. where sub-scripting instances creates computed LValues) can now be indexed in parameter expressions.Unroll decorator for loops with constant bounds and steps:
@unroll
: Fully unroll a loop.@unroll(n)
: Unroll a loop by factor of n, wheren
is a positive integer.Unroll decorator requires loop bounds and iteration step to be compiler time constant value, otherwise unrolling will fail with compilation error. This also doesn’t make loop induction variable a parameter.
# Fully unroll the loop. @unroll for i in range(5): print(i) # Unroll the loop by a factor of 4 (with remainder iterations of 2). @unroll(4) for i in range(10): print(i)
The Mojo REPL now prints the values of variables defined in the REPL. There is full support for scalars and structs. Non-scalar SIMD vectors are not supported at this time.
🛠️ Fixed
Issue #437 - Range can now be instantiated with a PythonObject.
Issue #288 - Python strings can now be safely copied.
2023-07-20
⭐️ New
Mojo now includes a
Limits
module, which contains functions to get the max and min values representable by a type, as requested in Issue #51. The following functions moved fromMath
toLimits
:inf()
,neginf()
,isinf()
,isfinite()
.Mojo decorators are now distinguished between “signature” and “body” decorators and are ordered. Signature decorators, like
@register_passable
and@parameter
, modify the type of declaration before the body is parsed. Body decorators, like@value
, modify the body of declaration after it is fully parsed. Due to ordering, a signature decorator cannot be applied after a body decorator. That means the following is now invalid:@register_passable # error: cannot apply signature decorator after a body one! @value struct Foo: pass
Global variables can now be exported in Mojo compiled archives, using the
@export
decorator. Exported global variables are public symbols in compiled archives and use the variable name as its linkage name, by default. A custom linkage name can be specified with@export("new_name")
. This does not affect variable names in Mojo code.Mojo now supports packages! A Mojo package is defined by placing an
__init__.mojo
or__init__.🔥
within a directory. Other files in the same directory form modules within the package (this works exactly like it does in Python). Example:main.🔥 my_package/ __init__.🔥 module.🔥 my_other_package/ __init__.🔥 stuff.🔥
# main.🔥 from my_package.module import some_function from my_package.my_other_package.stuff import SomeType fn main(): var x: SomeType = some_function()
Mojo now supports direct module and package imports! Modules and packages can be imported and bound to names. Module and package elements, like functions, types, global variables, and other modules, can be accessed using attribute references, like
my_module.foo
. Note that modules lack runtime representations, meaning module references cannot be instantiated.import builtin.io as io import SIMD print("hello world") io.var x: SIMD.Float32 = 1.2
🦋 Changed
Reverted the feature from 2023-02-13 that allowed unqualified struct members. Use the
Self
keyword to conveniently access struct members with bound parameters instead. This was required to fix Issue #260.Updated the RayTracing notebook: added step 5 to create specular lighting for more realistic images and step 6 to add a background image.
🛠️ Fixed
- Issue #260 - Definitions inside structs no longer shadow definitions outside of struct definitions.
2023-07-12
⭐️ New
Mojo now has support for global variables! This enables
var
andlet
declaration at the top-level scope in Mojo files. Global variable initializers are run when code modules are loaded by the platform according to the order of dependencies between global variables, and their destructors are called in the reverse order.The Mojo programming manual is now written as a Jupyter notebook, and available in its entirety in the Mojo Playground (
programming-manual.ipynb
). (Previously,HelloMojo.ipynb
included most of the same material, but it was not up-to-date.)As a result, we’ve also re-written
HelloMojo.ipynb
to be much shorter and provide a more gentle first-user experience.Coroutine
module documentation is now available. Coroutines form the basis of Mojo’s support for asynchronous execution. Calls toasync fn
s can be stored into aCoroutine
, from which they can be resumed, awaited upon, and have their results retrieved upon completion.
🦋 Changed
simd_bit_width
in theTargetInfo
module has been renamed tosimdbitwidth
to better align withsimdwidthof
,bitwidthof
, etc.
🛠️ Fixed
The walrus operator now works in if/while statements without parentheses, e.g.
if x := function():
.Issue #428 - The
FloatLiteral
andSIMD
types now support conversion toInt
via theto_int
or__int__
method calls. The behavior matches that of Python, which rounds towards zero.
2023-07-05
⭐️ New
- Tuple expressions now work without parentheses. For example,
a, b = b, a
works as you’d expect in Python. - Chained assignments (e.g.
a = b = 42
) and the walrus operator (e.g.some_function(b := 17)
) are now supported.
🦋 Changed
The
simd_width
anddtype_simd_width
functions in theTargetInfo
module have been renamed tosimdwidthof
.The
dtype_
prefix has been dropped fromalignof
,sizeof
, andbitwidthof
. You can now use these functions (e.g.alignof
) with any argument type, includingDType
.The
inf
,neginf
,nan
,isinf
,isfinite
, andisnan
functions were moved from theNumerics
module to theMath
module, to better align with Python’s library structure.
🛠️ Fixed
Issue #253 - Issue when accessing a struct member alias without providing parameters.
Issue #404 - The docs now use
snake_case
for variable names, which more closely conforms to Python’s style.Issue #379 - Tuple limitations have been addressed and multiple return values are now supported, even without parentheses.
Issue #347 - Tuples no longer require parentheses.
Issue #320 - Python objects are now traversable via
for
loops.
June 2023
2023-06-29
⭐️ New
- You can now share
.ipynb
notebook files in Mojo Playground. Just save a file in theshared
directory, and then right-click the file and select Copy Sharable link. To open a shared notebook, you must already have access to Mojo Playground; when you open a shared notebook, click Import at the top of the notebook to save your own copy. For more details about this feature, see the instructions inside thehelp
directory, in the Mojo Playground file browser.
🦋 Changed
- The
unroll2()
andunroll3()
functions in theFunctional
module have been renamed to overload theunroll()
function. These functions unroll 2D and 3D loops andunroll()
can determine the intent based on the number of input parameters.
🛠️ Fixed
Issue #229 - Issue when throwing an exception from
__init__
before all fields are initialized.Issue #74 - Struct definition with recursive reference crashes.
Issue #285 - The
TargetInfo
module now includesis_little_endian()
andis_big_endian()
to check if the target host uses either little or big endian.Issue #254 - Parameter name shadowing in nested scopes is now handled correctly.
2023-06-21
⭐️ New
Added support for overloading on parameter signature. For example, it is now possible to write the following:
fn foo[a: Int](x: Int): pass fn foo[a: Int, b: Int](x: Int): pass
For details on the overload resolution logic, see the Mojo Manual section on parameters.
A new
cost_of()
function has been added toAutotune
. This meta-function must be invoked at compile time, and it returns the number of MLIR operations in a function (at a certain stage in compilation), which can be used to build basic heuristics in higher-order generators.from autotune import cost_of fn generator[f: fn(Int) -> Int]() -> Int: @parameter if cost_of[fn(Int) -> Int, f]() < 10: return f() else: # Do something else for slower functions...
Added a new example notebook with a basic Ray Tracing algorithm.
🦋 Changed
- The
constrained_msg()
in theAssert
module has been renamed toconstrained()
.
🛠️ Fixed
Overloads marked with
@adaptive
now correctly handle signatures that differ only in declared parameter names, e.g. the following now works correctly:@adaptive fn foobar[w: Int, T: DType]() -> SIMD[T, w]: ... @adaptive fn foobar[w: Int, S: DType]() -> SIMD[S, w]: ...
Issue #219 - Issue when redefining a function and a struct defined in the same cell.
Issue #355 - The loop order in the Matmul notebook for Python and naive mojo have been reordered for consistency. The loop order now follows (M, K, N) ordering.
Issue #309 - Use snake case naming within the testing package and move the asserts out of the TestSuite struct.
2023-06-14
⭐️ New
Tuple type syntax is now supported, e.g. the following works:
fn return_tuple() -> (Int, Int): return (1, 2)
🦋 Changed
- The
TupleLiteral
type was renamed to justTuple
, e.g.Tuple[Int, Float]
.
🛠️ Fixed
- Issue #354 - Returning a tuple doesn’t work even with parens.
- Issue #365 - Copy-paste error in
FloatLiteral
docs. - Issue #357 - Crash when missing input parameter to variadic parameter struct member function.
2023-06-07
⭐️ New
- Tuple syntax now works on the left-hand side of assignments (in “lvalue” positions), enabling things like
(a, b) = (b, a)
. There are several caveats: the element types must exactly match (no implicit conversions), this only works with values ofTupleLiteral
type (notably, it will not work withPythonObject
yet) and parentheses are required for tuple syntax.
❌ Removed
- Mojo Playground no longer includes the following Python packages (due to size, compute costs, and environment complications):
torch
,tensorflow
,keras
,transformers
.
🦋 Changed
- The data types and scalar names now conform to the naming convention used by numpy. So we use
Int32
instead ofSI32
, similarly usingFloat32
instead ofF32
. Closes Issue #152.
🛠️ Fixed
- Issue #287 - computed lvalues don’t handle raising functions correctly
- Issue #318 - Large integers are not being printed correctly
- Issue #326 - Float modulo operator is not working as expected
- Issue #282 - Default arguments are not working as expected
- Issue #271 - Confusing error message when converting between function types with different result semantics
May 2023
2023-05-31
⭐️ 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 = ... ... takeValueAsOwned(someValue) ...
When
takeValueAsOwned()
takes its argument as anowned
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 thesomeValue
value, and pass that value instead ofsomeValue
, because there may be other uses ofsomeValue
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 liketakeValueAsOwned(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 don’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() use(someValue) ...let otherValue = someValue # Last use of someValue use(otherValue) ...var yetAnother = otherValue # Last use of otherValue mutate(yetAnother)
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
2023-05-24
⭐️ New
finally
clauses are now supported ontry
statements. In addition,try
statements no longer requireexcept
clauses, allowingtry-finally
blocks.finally
clauses contain code that is always executed from control-flow leaves any of the other clauses of atry
statement by any means.
🦋 Changed
with
statement emission changed to use the newfinally
logic so thatwith ContextMgr(): return
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
Time.now
function result is unclear - Issue #165 - Type in
Pointer.free
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
2023-05-16
⭐️ 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
2023-05-11
⭐️ New
NDBuffer
andBuffer
are now constructable viaPointer
andDTypePointer
.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 aninout
keyword to be more similar to theborrowed
andowned
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
2023-05-02
📢 Released
- Mojo publicly launched! This was epic, with lots of great coverage online including a wonderful post by Jeremy Howard. The team is busy this week.
⭐️ 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 withfoo
bound to theself
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
andlet
declarations inside functions can shadow declarations from higher “scopes”, where a scope is defined as any new indentation block. In addition, thefor
loop iteration variable is now scoped to the loop body, so it is finally possible to writefor 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:@value 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): self.name = name^ self.age = age fn __copyinit__(inout self, existing: Self): self.name = existing.name self.age = existing.age fn __moveinit__(inout self, owned existing: Self): self.name = existing.name^ 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, andvar
declarations that are never mutated get a warning suggesting they be converted to alet
declaration. Lazy initialization allows more flexible patterns of initialization than requiring the initializer be inline, e.g.:let x : Int if cond: = foo() x else: = bar() x use(x)
📢 Functions defined with
def
now returnobject
by default, instead ofNone
. This means you can return values (convertible toobject
) insidedef
functions without specifying a return type.📢 The
@raises
decorator has been removed. Raisingfn
should be declared by specifyingraises
after the function argument list. The rationale is thatraises
is part of the type system, instead of a function modifier.📢 The
BoolLiteral
type has been removed. Mojo now emitsTrue
andFalse
directly asBool
.📢 Syntax for function types has been added. You can now write function types with
fn(Int) -> String
orasync def(&String, *Int) -> None
. No more writing!kgen.signature
types by hand!📢 Float literals are not emitted as
FloatLiteral
instead of an MLIRf64
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 thewith
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!kgen.pointer
type) and returns anowned
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. consume(__get_address_as_owned_value(somePointer.value))
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. = SomeHeavy(4, 5) __get_address_as_lvalue(somePointer.value) # Ok to use an lvalue, convert to borrow etc. use(__get_address_as_lvalue(somePointer.value)) # "Placement new": Initialize a new value into uninitialied memory. = SomeHeavy(4, 5) __get_address_as_uninit_lvalue(somePointer.value) # Error, cannot read from uninitialized memory. use(__get_address_as_uninit_lvalue(somePointer.value))
Note that
__get_address_as_lvalue
assumes that there is already a value at the specified address, so the assignment above will run theSomeHeavy
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:
fn __moveinit__(inout self, owned existing: Self)
: Traditional Rust style moving constructors that shuffles data around while taking ownership of the source binding.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 takeself
by borrow, allow non-copyable callees to be called.Implicit conversions are now invoked in
raise
statements properly, allowing converting strings toError
type.Automatic destructors are turned on for
__del__
instead of__del___
.📚 Add the builtin FloatLiteral type.
📚 Add integral
floordiv
andmod
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 heterogeneous 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 witha[1:2:3]
now emits calls to__setitem__
and__getitem__
with a slice object.Call syntax has been wired up to
__call__
. You can nowf()
on custom types!Closures are now explicitly typed as capturing or non-capturing. If a function intends to accept a capturing closure, it must specify the
capturing
function effect.📚 Add a
Tile2D
function to enable generic2D
tiling optimizations.📚 Add the
slice
struct to enable getting/setting spans of elements viagetitem
/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
andNoneType
aliases were added and auto-imported in all files.📢 The Mojo VS Code extension 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 newBuiltin
module and is auto-imported in all code. The type of integer literals has been changed from the MLIRindex
type to theInt
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, likefoo(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 initializationx = 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:
. TheT{}
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 likex.foo()
wherefoo
is not a member ofx
.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[ __mlir_type[`!kgen.signature<`, Int, `>() -> !kgen.none`] func: ]():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 thePointer[T]
library type. ThePointer.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]. = SomeHeavy(4, 5) __get_address_as_lvalue(somePointer.value)
Note that
__get_address_as_lvalue
assumes that there is already a value at the specified address, so the assignment above will run theSomeHeavy
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 likeInt
.You can now write back to attributes of structs that are produced by a computed lvalue expression. For example
a[i].x = ..
works whena[i]
is produced with a__getitem__
/__setitem__
call. This is implemented by performing a read ofa[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 likeInt
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 1, 1.2, True, "hello") foo(
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&
:- A future borrow checker will make sure there are no mutable aliases with an immutable borrow.
@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 1, 1.5, "hello", True) foo[Int, F32, String, Bool](
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 theOS
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 newparam_return
statement. This allows returning result parameters from throwing functions:@raises fn foo[() -> out: Int](): 42] param_return[raise Error()
And returning different parameters along
@parameter if
branches:fn bar[in: Bool -> out: Int](): @parameter if in: 1] param_return[else: 2] param_return[
📢 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
andSIMD
, 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 usingInt
. 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:
- Inference now works with
let
decls in some scenarios that weren’t working before. - Parameter bindings can now infer types into parameter expressions. This helps resolve higher-order functions in parameter expressions.
- Inference now works with
📚 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 genericevaluate
function in the standard library.📢 A new built-in literal type
BoolLiteral
was added in_CompilerBuiltin
. It represents the literal boolean valuesTrue
andFalse
. 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]: @staticmethod pass bar(): self): foo(# implicitly binds to Foo[x].bar() bar() 2].bar() # explicitly bind to another parameter Foo[
📢 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 supersede 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.@adaptive fn sort(arr: ArraySlice[Int]): bubble_sort(arr) @adaptive fn sort(arr: ArraySlice[Int]): merge_sort(arr) fn concat_and_sort(lhs: ArraySlice[Int], rhs: ArraySlice[Int]): let arr = lhs + rhs # this forks compilation, creating two instances sort(arr) # 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-linemojo
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 ofy = 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 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 aCoroutine[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 aCoroutine[T]
. But today, it must be done manually. This type implements all the expected hooks, such as__await__
, andget()
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 indef
s). 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 likeInt.__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 withv[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 takeself
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 canawait
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 statementfor 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 theawait
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: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.Similarly, these “operators” should be treated by users as primitives: they don’t want to know about MLIR or internal implementation details of Int.
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.
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 thex
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 likeDType
. We’d like to eventually preserve the reference in the AST, but this unblocks library development.📚 Add a
now
function andBenchmark
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 bykgen-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 *2 -> a, b]() idx_result_params[xyz
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:
- Added Integer and SIMD structs to bootstrap the standard library.
- 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_simdwidthof[() -> nelts: Int](): return[2] fn vectorized_function(): -> nelts]() get_preferred_simdwidthof[() 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:- It’s simpler.
- 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.
- 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 1)) # resolves to the first overload foo(Int(1.0) # resolves to the second overload foo(1) # error: both candidates viable with 1 implicit conversion! foo(
The short circuiting binary
and
andor
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 Visual Studio Code 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 toInt
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:
- 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.
- This doesn’t “check to see if we can invoke
__new__
” it force applies it on a mismatch, which leads to poor QoI. - This doesn’t fix things that need radd.
November 2022
Week of 2022-11-28
📢 We support the
True
andFalse
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 thatdef
can raise by default, butfn
must explicitly declare raising with a@raises
decorator. Stub out basicError
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): = 25 a
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 likedef
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 animport
model.Implement IR generation for arithmetic operators like
+
and*
in terms of the__add__
and__mul__
methods.📢 Added support for
break
andcontinue
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:__mlir_op.`index.cmp`[ __mlir_type.i1, _type: __mlir_attr.`#index<cmp_predicate slt>` pred: ](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.@interface def add(lhs: index, rhs: index): @implements(add) def normal_add(lhs: index, rhs: index) -> index: return lhs + rhs @implements(add) def slow_add(lhs: index, rhs: index) -> index: 1000) wait(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 = Foo.return_a_number() # static method! result.value return result @staticmethod 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 likeswap(x, y)
correctly.📢 Implemented support for field extract from rvalues, like
x.value
wherex
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 forreturn None
. This enables early exits in functions that implicitly returnNone
to be cleaner:def just_return(): 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 theself
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]): pass def fancy_add[dt: DType, width: index]( -> index: lhs: SIMD[dt, width], rhs: SIMD[dt, width]) 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. Addedindex
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 andwhile
loops!def if_stmt(c: index, a: index, b: index) -> index: var result: index = 0 if c: = a result else: = b result return result def while_stmt(init: index): while init > 1: = init - 1 init
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: = param + arg result 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.