C2's high level IR is normally called Ideal and it's really kind of a mid level IR. There aren't that many high level nodes in it and most of the nodes correspond to single machine instructions on most machines. The highest level are nodes like Allocate/AllocateArray and Lock/Unlock which embody fast and slow path idioms. They get expanded at the very end of compilation into a larger set of nodes that represent the operations at a lower level.
All instructions in ideal are subclasses of Node. Nodes have a collection of Node inputs and as much as possible everything is done by using node inputs instead of having special fields on the subclasses of Node. Nodes maintain def-use edges so that you can always visit all the users of any node relatively cheaply. This is one of the reasons for the uniform treatment of inputs, since it makes maintaining that information straightforward.
There's a lot of sharing of nodes in the ideal graph because of the uniform treatment of nodes and the use global value numbering. For instance there's only one copy of every constant in the graph which is good for code but it makes understanding the graph somewhat difficult because globally the graph can get pretty tangled.
It has the normal set of binary operators you'd expect and these are specialized into int, long, float, double and pointer using a capital letter at the end to identify they type, so AddI is integer add and CmpP is the pointer compare.
Loads distinguish between the smaller signed and unsigned integral types like char and byte, so you have instructions like LoadC to load a character and LoadS to load a short. Stores are only differentiated by the size of the store and not the sign. Memory operations all have a special input called the Memory input which identifies the slice of memory that the load or store is operating on. All memory operations are connected to each other in a thing called the memory graph. There's an incoming memory state at the entry point of the compile and loads and stores are connected up to that. The memory state is divided into a bunch of slices which correspond to aliases classes. Operations within the same slice are ordered relative to each other but slices aren't ordered relative to other slices except when the slices merge into some form of memory barrier, like a call. The slices have to merge up at any operation where the compiler can't see the effect of a node, like a call to some other Java code, or where explicitly ordering is needed, like volatile memory operations. These operations produce a new memory state that all following loads and stores consume. The memory graph captures all the memory dependence in the ideal and insures that load and stores are issued in the correct order.
There are no explicit basic blocks in ideal at all. It's representation is what's known as a program dependence graph in the compiler literature. A node can only be evaluated once all of it's inputs have been evaluated so any constraints on order of execution have to be completely captured by the inputs to the node. There are special nodes that are said to produce control and every node has a special input, input 0, which can be set to point at one of these control nodes. Nodes that produce control are obvious ones like If and Call but there are also Region nodes which represent the merge of multiple control flow paths plus Halt and Return that terminate control flow. PhiNodes have a Region as their control input and inputs for each control flow path merging at the region. Many nodes don't require a control node at all because they can safely be executed once their data inputs have been evaluated. All the binary operators except division don't require a control input. Division is special in Java since it might produce a divide by zero exception.
By setting input 0 to point at a particular control flow node you're constraining that node to be evaluated only after that control flow node is evaluated. Note that control dependence expresses only the earliest point that a node can be evaluated. It doesn't say anything about the latest point in the program that node can be evaluated. Data and memory dependence tend to constrain the latest evaluation point. The node can actually be evaluated anywhere between the control point and the latest point allowed by the nodes uses.
There are also special nodes called projections (ProjNode) which are used to deal with nodes which produce multiple values. If nodes represent their control flow split by having two control projections, IfTrue and IfFalse, with nodes being dependent on the appropriate control projection instead of being dependent on the If.
Call nodes are among the most complex nodes in the graph. In addition to having a control input they are 4 other special inputs, along with their arguments and any values which are needed for deoptimization. The only other interesting special input is the incoming memory state. This is similar to the memory input to a load or store but it generally gathers all the live memory slices into one MergeMem object to represent the fact all memory operation preceeding the call should complete before the call is executed. Calls produce multiple values using ProjNodes. In particular they produce a control projection for nodes which are control dependent on the call, a memory projection to represent the state of memory after the call is complete and a projection for the return value of the call if there is one.
For calls which might be safepoints, there are also extra inputs which capture all the values which are live as far as deopt is concerned. A separate data structure called the JVMState maps from Java's notion of frames, locals and expression stack to inputs to the node.
There is also a special node call top which is use to fill inputs that we don't care about or which are dead.