Skip to content

IO Example through LLVM stdlib

Emilien Bauer edited this page Apr 18, 2023 · 5 revisions

Here is a basic example of using LLVM and stdlin to get basic IO in MLIR IR.

This is a toy program, reading a line from stdin up to 1024 bytes, and just printing it out. It uses terrible practices, and is just meant as a minimal example on how to use stdlib's IO routines from an MLIR IR, nothing more. Please choose your routines and error checking accordingly to your use-case!

module{
	// Declare external puts/gets functions. 
	func.func private @puts(!llvm.ptr<i8>) -> i32
	func.func private @gets(!llvm.ptr<i8>) -> !llvm.ptr<i8>
	
	// MLIR's func dialect doesn't handle variadics at this point.
	// To use e.g. printf, one have to use the llvm dialect at the time of writing.
	// It can then be called with llvm.call, which has the same syntax as func.call!
	llvm.func @printf(!llvm.ptr<i8>) -> i32
	
	// Declare your main function
	func.func @main() -> (i32){
		// Allocate some buffer for your data
		%N = arith.constant 1024 : i64
		%buffer = llvm.alloca %N x i8 : (i64) -> !llvm.ptr<i8>
		
		// Just call gets on this buffer
		%ptr = func.call @gets(%buffer) : (!llvm.ptr<i8>) -> !llvm.ptr<i8>
		// Call puts on its returned pointer
		%puts = func.call @puts(%ptr) : (!llvm.ptr<i8>) -> i32
		
		// Return puts's returned value, just for fun
		return %puts : i32
	}
}

In generic syntax:

"builtin.module"() ({
  // Declare external puts/gets functions.
  "func.func"() ({
  }) {function_type = (!llvm.ptr<i8>) -> i32, sym_name = "puts", sym_visibility = "private"} : () -> ()
  "func.func"() ({
  }) {function_type = (!llvm.ptr<i8>) -> !llvm.ptr<i8>, sym_name = "gets", sym_visibility = "private"} : () -> ()

  // MLIR's func dialect doesn't handle variadics at this point.
  // To use e.g. printf, one have to use the llvm dialect at the time of writing.
  // It can then be called with llvm.call, which has the same syntax as func.call!
  "llvm.func"() ({
  }) {CConv = #llvm.cconv<ccc>, function_type = !llvm.func<i32 (ptr<i8>)>, linkage = #llvm.linkage<external>, sym_name = "printf"} : () -> ()

  // Declare your main function
  "func.func"() ({
    // Allocate some buffer for your data
    %0 = "arith.constant"() {value = 1024 : i64} : () -> i64
    %1 = "llvm.alloca"(%0) : (i64) -> !llvm.ptr<i8>
		
    // Just call gets on this buffer
    %2 = "func.call"(%1) {callee = @gets} : (!llvm.ptr<i8>) -> !llvm.ptr<i8>
    // Call puts on its returned pointer
    %3 = "func.call"(%2) {callee = @puts} : (!llvm.ptr<i8>) -> i32
		
    // Return puts's returned value, just for fun
    "func.return"(%3) : (i32) -> ()
  }) {function_type = () -> i32, sym_name = "main"} : () -> ()
}) : () -> ()

You should be able to compile it to a reader executable with mlir-opt io.mlir --pass-pipeline="builtin.module(convert-func-to-llvm)" | mlir-translate --mlir-to-llvmir | clang -x ir - -o reader

Here is en exemple execution:

myterminalprompt:~$ ./reader 
Hey reader, please print this, cheers.  
Hey reader, please print this, cheers.