-
Notifications
You must be signed in to change notification settings - Fork 1
Added advice for students #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
00c6c86
c8eae80
8729666
56dcb37
e78bc8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,84 @@ Backend | |
| You don’t need to implement an interpreter for Gazprea. You only need to | ||
| implement a *MLIR* code generator that outputs *LLVM IR*. | ||
|
|
||
| .. This uses a direct link because it links to a different sphinx project (info) | ||
|
|
||
| See also `MLIR Tips and Hints <../../info/mlir_tips.html>`_ for detailed MLIR and dialect tips, debugging strategies, and dialect selection guidance. | ||
|
|
||
| .. _ssec:representing_values: | ||
|
|
||
| Representing Values | ||
| ------------------- | ||
|
|
||
| When you emit MLIR you must decide *how a value lives*: as a slot in memory that | ||
| you load from and store to, or as an SSA value that is defined once. **Use the | ||
| memory model.** It keeps code generation local and mechanical. The choice is the | ||
| same for scalars and for arrays, matrices, vectors, and aggregates, so this section | ||
| treats them together. This section also describes the value (SSA) model, because | ||
| understanding it explains what the optimizer does for you, but you are not expected | ||
| to implement it. | ||
|
|
||
| .. note:: | ||
|
|
||
| **In practice.** The memory model (``alloca`` / ``load`` / ``store`` + | ||
| ``mem2reg``) is standard for imperative, systems-style languages with an | ||
| explicit, language-defined memory layout, like C. Value-semantic ``tensor`` | ||
| representations are favored at the high level by array- and math-focused | ||
| compilers like Fortran and Tensorflow. | ||
|
|
||
| The memory model | ||
| ~~~~~~~~~~~~~~~~ | ||
| **Use this.** | ||
|
|
||
| *Scalars.* Give every value a slot: reads are loads, writes are stores. The mutable | ||
| state lives in memory and the optimizer's ``mem2reg`` promotion turns the slots back | ||
| into SSA registers for you. A scalar declaration is an ``alloca``, a read a | ||
| ``load``, an assignment a ``store``: | ||
|
|
||
| :: | ||
|
|
||
| // var integer n = 42; then n = n + 1; | ||
| %size = llvm.mlir.constant(1 : i64) : i64 | ||
| %n = llvm.alloca %size x i32 : (i64) -> !llvm.ptr | ||
| %c42 = llvm.mlir.constant(42 : i32) : i32 | ||
| llvm.store %c42, %n : i32, !llvm.ptr | ||
| %0 = llvm.load %n : !llvm.ptr -> i32 | ||
| %c1 = llvm.mlir.constant(1 : i32) : i32 | ||
| %1 = llvm.add %0, %c1 : i32 | ||
| llvm.store %1, %n : i32, !llvm.ptr | ||
|
|
||
| It pairs naturally with unstructured ``cf``: because the state is in memory, | ||
| ``break`` / ``continue`` / ``return`` are ordinary branches with nothing threaded | ||
| through them and no analysis of which variables cross a construct. | ||
|
|
||
| *Arrays, matrices, vectors, and aggregates.* Use a ``{ptr, len, cap}`` struct (or | ||
| ``{ptr, len}`` where capacity is not needed) for **every** sequence: arrays, | ||
| vectors, strings, matrices. It is one representation for growable and fixed | ||
| sequences alike, and — because it *is* an LLVM type — it nests freely inside an | ||
| ``!llvm.struct``. A tuple or struct is then just an ``!llvm.struct`` of its fields, | ||
| with an array field an ordinary ``{ptr, len}`` member and no special case. Keeping | ||
| this single representation across the whole language is the recommended starting | ||
| point. | ||
|
|
||
| .. Note:: | ||
| For performance marks, consider how your vector grows. | ||
|
|
||
| The value (SSA) model | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
| Understand the formulation, avoid implementing it. | ||
|
|
||
| *Scalars.* Map each variable name to its *current* SSA value: a read is a lookup, an | ||
| assignment produces a new value and rebinds the name. There are no slots and no | ||
|
Comment on lines
+75
to
+76
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be useful to link to the bufferization documentation, since this comes with a little bit more nuance when you look at the bufferization passes: https://mlir.llvm.org/docs/Bufferization/#destination-passing-style We may also want to discuss the composite types here too, and how MLIR can handle the destructuring for the students automatically. Probably pointing to toy ch7 |
||
| loads or stores; bufferization introduces memory later, downstream of your emitter. | ||
| The one hard case is a *merge*: after an ``if``, a variable set on only one branch | ||
| still needs a single value afterward. | ||
|
|
||
| *Arrays and aggregates.* The array analog is ``tensor``: a write yields a new value | ||
| you thread through, exactly as in the scalar model and with the same costs. It also | ||
| cannot nest in an ``!llvm.struct``, so array-typed struct and tuple fields must be | ||
| split out of their aggregate (SROA/aggregate flattening). The payoff is greater array and | ||
| matrix optimization. | ||
|
|
||
| .. _ssec:backend_memory: | ||
|
|
||
| Memory Management | ||
|
|
@@ -17,19 +95,19 @@ but this could be problematic if the arrays are very large. | |
| It is likely safer to use ``malloc`` and ``free`` for these purposes. | ||
| This may be done in either your runtime or directly within MLIR. | ||
|
|
||
| Below is an example of how to use ``malloc`` and ``free`` within MLIR using the LLVM dialect: | ||
| Below is an example of how to use ``malloc`` and ``free`` within MLIR: | ||
|
|
||
| :: | ||
|
|
||
| module { | ||
| llvm.func @malloc(i32) -> !llvm.ptr<i8> | ||
| llvm.func @free(!llvm.ptr<i8>) | ||
| llvm.func @main() -> i32 { | ||
| %0 = llvm.mlir.constant(128 : i32) : i32 | ||
| %1 = llvm.call @malloc(%0) : (i32) -> !llvm.ptr<i8> | ||
| llvm.call @free(%1) : (!llvm.ptr<i8>) -> () | ||
| %c0_i32 = llvm.mlir.constant(0 : i32) : i32 | ||
| llvm.return %c0_i32 : i32 | ||
| func.func private @malloc(i64) -> !llvm.ptr | ||
| func.func private @free(!llvm.ptr) | ||
| func.func @main() -> i32 { | ||
| %0 = arith.constant 128 : i64 | ||
| %1 = func.call @malloc(%0) : (i64) -> !llvm.ptr | ||
| func.call @free(%1) : (!llvm.ptr) -> () | ||
| %c0_i32 = arith.constant 0 : i32 | ||
| func.return %c0_i32 : i32 | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we should outright suggest this. There is a good argument for using value-typed systems that FLang uses in FIR, namely they associate an fir expression that can grow and shrink (above memref) and then bufferize down to fir.box and fir.ref types with allocations. There is also precedent for this in the sparse_tensor dialect, which has a 'push_back' op that creates a new value type whenever adding an element to an array.
I agree that if the students were to implement this all themselves it just results in a ton of extra work, but they can steal from other dialects to make it somewhat easier. Especially with the bots, adopting the FIR/CLangIR methods would be effective in my mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also point to the pytorch dialect that has the NonValueTensorType which might be a useful model. It exists above bufferization but is also mutable, I think that's the model we should follow in the solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for this year, we should hold off on these types of improvements so the feedback from the change in evaluation is less noisy.