The language and compiler now supports functions, calls, parameters, returns, basic conditional blocks, scalar and array declarations, binary and unary operations, arithmetic and boolean operators, and a print statement.
See the Changelog for full details of all the changes since V4. The IF/ELSE work was described recently, but the ARRAY element work is new.
Array element lvalues and rvalues were both implemented. This required grammar, builder, and lowering changes.
The grammar now has optional array element indexes for many elements. Examples:
returnStatement : RETURN_TOKEN (literal | scalarOrArrayElement)? ; print : PRINT_TOKEN (scalarOrArrayElement | STRING_PATTERN) ; assignment : scalarOrArrayElement EQUALS_TOKEN rhs ; rhs : literal | unaryOperator? scalarOrArrayElement | binaryElement binaryOperator binaryElement | call ; binaryElement : numericLiteral | unaryOperator? scalarOrArrayElement ; booleanElement : booleanLiteral | scalarOrArrayElement ; scalarOrArrayElement : IDENTIFIER (indexExpression)? ; indexExpression : ARRAY_START_TOKEN (IDENTIFIER | INTEGER_PATTERN) ARRAY_END_TOKEN ;
Most of these scalarOrArrayElement used to be just LITERAL. My MLIR AssignOp and LoadOp’s are now generalized to include optional indexes:
def Toy_AssignOp : Op<Toy_Dialect, "assign"> {
let summary = "Assign a value to a variable (scalar or array element).";
let description = [{
Assigns `value` to the variable referenced by `var_name`.
If `index` is present, the assignment targets the array element at that index.
The target variable must have been declared with a matching `toy.declare`.
}];
let arguments = (ins
SymbolRefAttr:$var_name, // @t
Optional:$index, // optional SSA value of index type (dynamic or none)
AnyType:$value // the value being assigned
);
let results = (outs);
let assemblyFormat =
"$var_name (`[` $index^ `]`)? `=` $value `:` type($value) attr-dict";
}
def Toy_LoadOp : Op<Toy_Dialect, "load"> {
let summary = "Load a variable (scalar or array element) by symbol reference.";
let arguments = (ins
SymbolRefAttr:$var_name, // @t
Optional:$index // optional SSA value of index type (dynamic or none)
);
let results = (outs AnyType:$result);
let assemblyFormat =
"$var_name (`[` $index^ `]`)? `:` type($result) attr-dict";
}
Here is a simple example program that has a couple array elements, assignments, accesses, print and exit statements:
Here is the MLIR listing for this program, illustrating a couple of the optional index inputs:
module {
func.func @main() -> i32 {
"toy.scope"() ({
"toy.declare"() <{size = 7 : i64, type = i32}> {sym_name = "t"} : () -> ()
"toy.declare"() <{type = i32}> {sym_name = "x"} : () -> ()
%c3_i64 = arith.constant 3 : i64
%c42_i64 = arith.constant 42 : i64
%0 = arith.index_cast %c3_i64 : i64 to index
toy.assign @t[%0] = %c42_i64 : i64
%c3_i64_0 = arith.constant 3 : i64
%1 = arith.index_cast %c3_i64_0 : i64 to index
>> %2 = toy.load @t[%1] : i32
toy.assign @x = %2 : i32
%3 = toy.load @x : i32
toy.print %3 : i32
%c0_i32 = arith.constant 0 : i32
"toy.return"(%c0_i32) : (i32) -> ()
}) : () -> ()
"toy.yield"() : () -> ()
}
}
PRINT and EXIT also now support array elements, but that isn’t in this bit of sample code.
Here is an example lowering to LLVM LL:
define i32 @main() !dbg !4 {
%1 = alloca i32, i64 7, align 4, !dbg !8
#dbg_declare(ptr %1, !9, !DIExpression(), !8)
%2 = alloca i32, i64 1, align 4, !dbg !14
#dbg_declare(ptr %2, !15, !DIExpression(), !14)
%3 = getelementptr i32, ptr %1, i64 3, !dbg !16
store i32 42, ptr %3, align 4, !dbg !16
>> %4 = getelementptr i32, ptr %1, i64 3, !dbg !17
>> %5 = load i32, ptr %4, align 4, !dbg !17
store i32 %5, ptr %2, align 4, !dbg !17
%6 = load i32, ptr %2, align 4, !dbg !18
%7 = sext i32 %6 to i64, !dbg !18
call void @__toy_print_i64(i64 %7), !dbg !18
ret i32 0, !dbg !18
}
(with the GEP and associated load for the array access highlighted.)
Even without optimization enabled, the assembly listing is pretty good:
0000000000000000 :
0: sub $0x28,%rsp
4: movl $0x2a,0x18(%rsp)
c: movl $0x2a,0x8(%rsp)
14: mov $0x2a,%edi
19: call 1e
1a: R_X86_64_PLT32 __toy_print_i64-0x4
1e: xor %eax,%eax
20: add $0x28,%rsp
24: ret
With optimization, everything is in registers, looking even nicer:
