SA-C Overview
The SA-C Language: restricting C
Single Assignment C (SA-C) [pronounced "sassy"] is a variant of the C programming language that can be directly and intuitively mapped onto circuits, including FPGAs. A new language is necessary because C assumes a one-to-one correspondence between variables and memory locations. As a result, programmers can take the address of any variable and explicitly dereference pointers. In SA-C, on the other hand, variables correspond to wires in a circuit, and the address-of operator (&) and dereferencing operator (*) have been removed. More importantly, SA-C is a single assignment language, implying that the value of a variable can only be set once. Again, this is because variables in SA-C correspond to wires, and electrical wires can be driven by only one source. By comparison, in C a variable is a memory location whose value can be set and reset throughout the course of an algorithm.
The C language also assumes that programs are implemented on a von Neuman style calling stack. As a result, functions can recursively call each other. This is not possible in a circuit, which is a fixed-size piece of hardware. SA-C therefore bans recursion.
The SA-C Language: Extending C
Having restricted C, we needed to add new features to compensate, and to exploit features of FPGAs not found in traditional processors. Most of the extensions were added with image and signal processing applications in mind, although many other applications may benefit from SA-C as well.
In image and signal processing, one of the most common use of pointers is to index into large arrays of data. SA-C obviates this use of pointers by adding true multidimensional arrays to the language, including dynamic arrays. For example, int32 arr[:,100] declares a two-dimensional array with one hundred columns; the number of rows is not specified. SA-C also introduces a parallel looping mechanisms that is tightly integrated with its multidimensional arrays. For example, for window w[3:3] in arr{...} will execute the loop body once for every possible 3x3 subwindow of the two-dimensional array arr .
SA-C also exploits the flexibility of FPGAs by introducing variable bit-precision data types. Whereas integers in C are limited to short , standard or long , SA-C programmers can specify the width of any variable in bits. In the example above, int32 declared a traditional 32-bit integer. However, we could also have written int12 or uint6 (the latter being an unsigned integer). SA-C also introduces fixed-point data types, allowing programmers to avoid building complex circuits for floating point arithmetic whenever possible.
Finally, SA-C compensates for the inconvenience of the single assignment restriction by allowing variable names to be reused, and by converting many C statements into expressions. New variables can be declared at any point in a SA-C program, and variables are initialized when they are declared (to enforce the single assignment restriction). To avoid a proliferation of variable names, however, SA-C programs can reuse previously used variable names. When a new variable is declared with the same name as a previous variable, the previous variable goes out of scope. As a result, statements such as int12 x = x + 1; are perfectly valid in SA-C. It simply declares a new variable x that replaces the old variable x (the new and old variables x may or may not have the same data type).
Pragmas & Generics
In addition to the SA-C language itself, two other facilities enhance the SA-C programming environment. One is a pragma facility that allows users to control compiler optimizations. For example, users can turn on or off optimizations such as loop unrolling, stripmining, and implementing functions as lookup tables. This gives programmers control over how their programs are mapped onto circuits. The second facility is a Generic facility in the preprocessor. This facility allows code to be replicated for different data types, almost like a very simple version of C++'s template facility. It is added because variable bit resolution implies that SA-C has many more data types than traditional languages. As a result, it is often helpful to be able to implement a single library routine for many bit resolutions.
For more information about Pragmas, see the SA-C compiler page
A SA-C Example
A canonical image processing operation is to compute the magnitude of the edges in an image using horizontal and vertical edge masks. In our example, we use Prewitt edge masks. The basic algorithm is simple: first, convolve the image with horizontal and vertical edge masks, producing images of edge responses in the X and Y directions. Then, for each pixel in the source image, compute the square root of the sum of the squares of the X & Y edge responses. The result is the edge magnitude.
In SA-C, the edge magnitude code looks like this:
The inner loop is computed for every 3x3 window in the source image. The dot product of the window with the two edge masks performs the convolutions. The remaining code computes the edge magnitude as the square root of the sum of the squares of the convolution responses.
Note that although this code is basic from an image processing perspective, it is non-trivial from a parallel systems perspective. The convolutions are classic data parallel operations, while the square root that follows is highly sequential. To optimize performance, the SA-C compiler must parallelize the convolutions while pipelining the square root.
More on Applications...
Lesson 6: Static Single Assignment
- discussion thread
- static single assignment
- SSA slides from Todd Mowry at CMU another presentation of the pseudocode for various algorithms herein
- Revisiting Out-of-SSA Translation for Correctness, Code Quality, and Efficiency by Boissinot on more sophisticated was to translate out of SSA form
- tasks due March 8
You have undoubtedly noticed by now that many of the annoying problems in implementing analyses & optimizations stem from variable name conflicts. Wouldn’t it be nice if every assignment in a program used a unique variable name? Of course, people don’t write programs that way, so we’re out of luck. Right?
Wrong! Many compilers convert programs into static single assignment (SSA) form, which does exactly what it says: it ensures that, globally, every variable has exactly one static assignment location. (Of course, that statement might be executed multiple times, which is why it’s not dynamic single assignment.) In Bril terms, we convert a program like this:
Into a program like this, by renaming all the variables:
Of course, things will get a little more complicated when there is control flow. And because real machines are not SSA, using separate variables (i.e., memory locations and registers) for everything is bound to be inefficient. The idea in SSA is to convert general programs into SSA form, do all our optimization there, and then convert back to a standard mutating form before we generate backend code.
Just renaming assignments willy-nilly will quickly run into problems. Consider this program:
If we start renaming all the occurrences of a , everything goes fine until we try to write that last print a . Which “version” of a should it use?
To match the expressiveness of unrestricted programs, SSA adds a new kind of instruction: a ϕ-node . ϕ-nodes are flow-sensitive copy instructions: they get a value from one of several variables, depending on which incoming CFG edge was most recently taken to get to them.
In Bril, a ϕ-node appears as a phi instruction:
The phi instruction chooses between any number of variables, and it picks between them based on labels. If the program most recently executed a basic block with the given label, then the phi instruction takes its value from the corresponding variable.
You can write the above program in SSA like this:
It can also be useful to see how ϕ-nodes crop up in loops.
(An aside: some recent SSA-form IRs, such as MLIR and Swift’s IR , use an alternative to ϕ-nodes called basic block arguments . Instead of making ϕ-nodes look like weird instructions, these IRs bake the need for ϕ-like conditional copies into the structure of the CFG. Basic blocks have named parameters, and whenever you jump to a block, you must provide arguments for those parameters. With ϕ-nodes, a basic block enumerates all the possible sources for a given variable, one for each in-edge in the CFG; with basic block arguments, the sources are distributed to the “other end” of the CFG edge. Basic block arguments are a nice alternative for “SSA-native” IRs because they avoid messy problems that arise when needing to treat ϕ-nodes differently from every other kind of instruction.)
Bril in SSA
Bril has an SSA extension . It adds support for a phi instruction. Beyond that, SSA form is just a restriction on the normal expressiveness of Bril—if you solemnly promise never to assign statically to the same variable twice, you are writing “SSA Bril.”
The reference interpreter has built-in support for phi , so you can execute your SSA-form Bril programs without fuss.
The SSA Philosophy
In addition to a language form, SSA is also a philosophy! It can fundamentally change the way you think about programs. In the SSA philosophy:
- definitions == variables
- instructions == values
- arguments == data flow graph edges
In LLVM, for example, instructions do not refer to argument variables by name—an argument is a pointer to defining instruction.
Converting to SSA
To convert to SSA, we want to insert ϕ-nodes whenever there are distinct paths containing distinct definitions of a variable. We don’t need ϕ-nodes in places that are dominated by a definition of the variable. So what’s a way to know when control reachable from a definition is not dominated by that definition? The dominance frontier!
We do it in two steps. First, insert ϕ-nodes:
Then, rename variables:
Converting from SSA
Eventually, we need to convert out of SSA form to generate efficient code for real machines that don’t have phi -nodes and do have finite space for variable storage.
The basic algorithm is pretty straightforward. If you have a ϕ-node:
Then there must be assignments to x and y (recursively) preceding this statement in the CFG. The paths from x to the phi -containing block and from y to the same block must “converge” at that block. So insert code into the phi -containing block’s immediate predecessors along each of those two paths: one that does v = id x and one that does v = id y . Then you can delete the phi instruction.
This basic approach can introduce some redundant copying. (Take a look at the code it generates after you implement it!) Non-SSA copy propagation optimization can work well as a post-processing step. For a more extensive take on how to translate out of SSA efficiently, see “Revisiting Out-of-SSA Translation for Correctness, Code Quality, and Efficiency” by Boissinot et al.
- One thing to watch out for: a tricky part of the translation from the pseudocode to the real world is dealing with variables that are undefined along some paths.
- Previous 6120 adventurers have found that it can be surprisingly difficult to get this right. Leave yourself plenty of time, and test thoroughly.
- You will want to make sure the output of your “to SSA” pass is actually in SSA form. There’s a really simple is_ssa.py script that can check that for you.
- You’ll also want to make sure that programs do the same thing when converted to SSA form and back again. Fortunately, brili supports the phi instruction, so you can interpret your SSA-form programs if you want to check the midpoint of that round trip.
- For bonus “points,” implement global value numbering for SSA-form Bril code.
- C Data Types
- C Operators
- C Input and Output
- C Control Flow
- C Functions
- C Preprocessors
- C File Handling
- C Cheatsheet
- C Interview Questions
Assignment Operators in C
Assignment operators are used for assigning value to a variable. The left side operand of the assignment operator is a variable and right side operand of the assignment operator is a value. The value on the right side must be of the same data-type of the variable on the left side otherwise the compiler will raise an error.
Different types of assignment operators are shown below:
1. “=”: This is the simplest assignment operator. This operator is used to assign the value on the right to the variable on the left. Example:
2. “+=” : This operator is combination of ‘+’ and ‘=’ operators. This operator first adds the current value of the variable on left to the value on the right and then assigns the result to the variable on the left. Example:
If initially value stored in a is 5. Then (a += 6) = 11.
3. “-=” This operator is combination of ‘-‘ and ‘=’ operators. This operator first subtracts the value on the right from the current value of the variable on left and then assigns the result to the variable on the left. Example:
If initially value stored in a is 8. Then (a -= 6) = 2.
4. “*=” This operator is combination of ‘*’ and ‘=’ operators. This operator first multiplies the current value of the variable on left to the value on the right and then assigns the result to the variable on the left. Example:
If initially value stored in a is 5. Then (a *= 6) = 30.
5. “/=” This operator is combination of ‘/’ and ‘=’ operators. This operator first divides the current value of the variable on left by the value on the right and then assigns the result to the variable on the left. Example:
If initially value stored in a is 6. Then (a /= 2) = 3.
Below example illustrates the various Assignment Operators:
Similar Reads
- C-Operators
- cpp-operator
Improve your Coding Skills with Practice
What kind of Experience do you want to share?
Assignment and shorthand assignment operator in C
Quick links.
- Shorthand assignment
Assignment operator is used to assign value to a variable (memory location). There is a single assignment operator = in C. It evaluates expression on right side of = symbol and assigns evaluated value to left side the variable.
For example consider the below assignment table.
The RHS of assignment operator must be a constant, expression or variable. Whereas LHS must be a variable (valid memory location).
Shorthand assignment operator
C supports a short variant of assignment operator called compound assignment or shorthand assignment. Shorthand assignment operator combines one of the arithmetic or bitwise operators with assignment operator.
For example, consider following C statements.
The above expression a = a + 2 is equivalent to a += 2 .
Similarly, there are many shorthand assignment operators. Below is a list of shorthand assignment operators in C.
IMAGES
VIDEO
COMMENTS
In compiler design, Static Single Assignment ( shortened SSA) is a means of structuring the IR (intermediate representation) such that every variable is allotted a value only once and every variable is defined before it’s use.
The assignment operators can be combined with some other operators in C to provide multiple operations using single operator. These operators are called compound operators. In C, there are 11 assignment operators :
186. Can you assign one instance of a struct to another, like so: struct Test t1; struct Test t2; t2 = t1; I have seen it work for simple structures, but does it work for complex structures? How does the compiler know how to copy data items depending on their type, i.e., differentiating between an int and string? c. struct. edited Mar 20 at 21:31.
In compiler design, static single assignment form (often abbreviated as SSA form or simply SSA) is a type of intermediate representation (IR) where each variable is assigned exactly once. SSA is used in most high-quality optimizing compilers for imperative languages, including LLVM, the GNU Compiler Collection, and many commercial compilers.
Single Assignment C (SA-C) [pronounced "sassy"] is a variant of the C programming language that can be directly and intuitively mapped onto circuits, including FPGAs. A new language is necessary because C assumes a one-to-one correspondence between variables and memory locations.
Single Assignment C (SA-C) (pronounced "sassy") is a member of the C programming language family designed to be directly and intuitively translatable into circuits, including FPGAs. [1] To ease translation, SA-C does not include pointers and arithmetics thereon.
static single assignment. SSA slides from Todd Mowry at CMU. another presentation of the pseudocode for various algorithms herein. Revisiting Out-of-SSA Translation for Correctness, Code Quality, and Efficiency. by Boissinot on more sophisticated was to translate out of SSA form. tasks due March 8. Gist.
Different types of assignment operators are shown below: 1. “=”: This is the simplest assignment operator. This operator is used to assign the value on the right to the variable on the left. Example: a = 10; b = 20; ch = 'y'; 2. “+=”: This operator is combination of ‘+’ and ‘=’ operators.
SAC (Single Assignment C) is a strict purely functional programming language whose design is focused on the needs of numerical applications. Emphasis is laid on efficient support for array processing via data parallelism. Efficiency concerns are essentially twofold.
Assignment operator is used to assign value to a variable (memory location). There is a single assignment operator = in C. It evaluates expression on right side of = symbol and assigns evaluated value to left side the variable.