pub enum ExprKind {
Show 30 variants
Error,
Const(ConstId),
Str(StrId),
Decl(DeclId),
Member {
base: ExprId,
field: u32,
},
Subscript {
base: ExprId,
index: ExprId,
},
Call {
callee: ExprId,
args: ExprList,
},
Unary {
op: UnaryOp,
operand: ExprId,
},
Binary {
op: BinaryOp,
lhs: ExprId,
rhs: ExprId,
},
Assign {
op: Option<BinaryOp>,
computation: TypeId,
lhs: ExprId,
rhs: ExprId,
},
Cond {
cond: ExprId,
then: ExprId,
otherwise: ExprId,
},
Comma {
lhs: ExprId,
rhs: ExprId,
},
Cast(ExprId),
Convert {
kind: Conversion,
operand: ExprId,
},
CompoundLiteral(DeclId),
StmtExpr(StmtId),
LabelAddr(LabelId),
VaArg {
list: ExprId,
},
VaStart {
list: ExprId,
},
VaEnd {
list: ExprId,
},
VaCopy {
dst: ExprId,
src: ExprId,
},
Classify {
op: Classify,
lhs: ExprId,
rhs: Option<ExprId>,
},
FpClassify {
value: ExprId,
answers: ExprList,
},
Sign {
op: Sign,
lhs: ExprId,
rhs: Option<ExprId>,
},
Abs {
operand: ExprId,
},
ByteSwap {
operand: ExprId,
},
BitCount {
operand: ExprId,
count: BitCount,
},
Overflow {
op: OverflowOp,
at: TypeId,
args: ExprList,
},
Atomic {
op: AtomicOp,
order: Ordering,
args: ExprList,
},
Unreachable,
}Expand description
What an expression is.
Variants§
Error
A node that was already the subject of a diagnostic.
Poisoned, in the sense of spec/06-lexer-and-parser.md section 6.8: nothing is reported
about one of these, which is what stops one bad declaration becoming forty bad uses.
Const(ConstId)
A constant, in the value table. Every constant that could be folded already has been.
Str(StrId)
A string literal, which is an array of characters with static storage duration.
Decl(DeclId)
A use of a declared object or function.
Member
base.field or, after the pointer has been dereferenced, base->field.
Fields
Subscript
base[index], with the pointer operand first however it was written.
Kept as a subscript rather than rewritten into *(base + index) because the rewriting
has exactly one home, which is the walk to the IR, and because a diagnostic about a
subscript should talk about a subscript.
Fields
Call
callee(args), with the arguments already converted to the parameter types.
Fields
Unary
A prefix or postfix operator on one operand.
Binary
A binary operator on two operands of the same type, except for the shifts and the pointer arithmetic, where the two sides legitimately differ.
Assign
lhs = rhs, or a compound assignment with the operator kept as written.
Fields
computation: TypeIdThe type the operation is performed in, which is the node’s own type for a plain assignment and for most compound ones.
It is here because a op= b is not a = a op b with the conversions left out, and
the difference is not academic: in int i = 5; i /= 0.5; the division happens in
double and the answer is ten, and a compiler that converts the right side to int
first divides by zero. The left side is an lvalue and cannot carry a conversion node
of its own, so the type it is read into is written here instead, which is what clang
calls the computation type and for the same reason.
Cond
cond ? then : otherwise, with both arms already converted to the common type.
Fields
Comma
lhs, rhs, whose value is the right side and whose left side is evaluated and dropped.
Cast(ExprId)
A cast the program wrote. The type is the node’s type.
Convert
A conversion the language performed. The type is the node’s type.
Fields
kind: ConversionWhich conversion, so that a reader and the verifier can both tell what happened rather than comparing the two types and guessing.
CompoundLiteral(DeclId)
(T){ ... }, which is an unnamed object with an initializer and not a conversion.
StmtExpr(StmtId)
({ ... }), GNU’s statement expression, whose value is its last expression statement.
LabelAddr(LabelId)
&&label, GNU’s label address.
VaArg
va_arg(list, T), which reads the next argument and moves the list on.
The type it fetches is the node’s own type, so there is nothing else to hold. It is a node rather than a call because what it becomes is the target’s own sequence of loads and not a function anything links against.
VaStart
va_start(list, last), which sets a list to the first argument past the named ones.
What the source wrote as the second argument is not here. It names where the named
arguments stopped, which the enclosing function’s own type already says, and it is not
evaluated: gcc rewrites va_start(ap, last) to a call with a zero in that place and C23
lets the program leave it out altogether.
VaEnd
va_end(list), which is the end of the reading and is nothing at all on most targets.
VaCopy
va_copy(dst, src), which makes a second list standing where the first one stands.
Fields
Classify
One of the floating point classification builtins, which asks about a value rather than computing one.
A node rather than a call because there is nothing to call: isnan and the rest are
macros in math.h that expand to exactly these, so the name has no function under it on
any platform. What each becomes is a comparison, and the four of the family that C
already has an operator for are ExprKind::Binary instead. See
check/builtin/classify.rs for which are here and why.
Fields
FpClassify
__builtin_fpclassify(nan, inf, normal, subnormal, zero, x), which answers with whichever
of the five the value is.
A node of its own rather than one of ExprKind::Classify because it has five operands
besides the value, and a node rather than the chain of conditionals it turns into because
the value is asked about four times and a program that writes __builtin_fpclassify(.., f()) calls f once.
Fields
Sign
__builtin_fabs or __builtin_copysign, which set the sign bit of a value from somewhere
and leave every other bit of it alone.
A node rather than a call because the call would be to the math library, which is not on
the link line of a program that never asked for it, and because neither one needs anything
the library has: both are a mask and an or over the bits. See check/builtin/sign.rs.
Fields
Abs
abs, labs and llabs, which are the magnitude of an integer.
A node rather than a call because the names are the C library’s and the compiler is allowed
to know what they do, which is what lets a program define one of them and still get the
magnitude. See check/builtin/abs.rs for when a call becomes one of these and when it
stays a call.
The operand has already been converted to the type of the answer, which is the type the declaration gave the parameter, so nothing downstream has to widen it.
ByteSwap
__builtin_bswap16, __builtin_bswap32 and __builtin_bswap64, which are the bytes of a
value in the other order.
A node rather than a call because no object file defines one of these, and because a byte
order swap is arithmetic: every machine can do it and most have an instruction for it. See
check/builtin/bswap.rs.
The operand has already been converted to the unsigned type the declaration gave the parameter, which is also the type of the answer, so the width the bytes are reversed in is the width of the node and nothing downstream has to work it out.
BitCount
The bit counting builtins, which are five questions about which bits of a value are set.
__builtin_clz, __builtin_ctz, __builtin_popcount, __builtin_parity and
__builtin_ffs, each in the plain, l and ll widths. Nodes rather than calls for the
reason ExprKind::ByteSwap is one: no object file defines any of them, and every machine
can answer them with instructions it already has. See check/builtin/count.rs.
The operand keeps the width its declaration gave it, because that width is the question. The
type of the whole node is int whatever that width is, which is the one place this differs
from the byte swaps, so the walk to the IR counts at the operand’s width and then narrows
the answer.
Fields
Overflow
The overflow checking builtins, which do the arithmetic exactly and say whether it fit.
__builtin_add_overflow, __builtin_sub_overflow and __builtin_mul_overflow. A node
rather than a call for the reason ExprKind::ByteSwap is one, and for a second reason
besides: the answer is two things, a value and a bit, and a call in C can only give back
one. See check/builtin/overflow.rs.
The operands keep the types they were written with, because the arithmetic is defined as
happening in infinite precision and then being put somewhere. What stands in for infinite
precision is at, a type wide enough to hold every value all three of the written types
can hold, and the walk to the IR converts both operands to it before doing anything.
The three operands are a run rather than three fields, because three of them and a type
would be the widest variant here and every expression in the program is the size of the
widest one. See Tast for the same trade made about a declaration.
Fields
op: OverflowOpWhich of the three operations this is.
Atomic
The atomic accesses and the barriers, which carry a memory ordering.
__atomic_load_n, __atomic_store_n, __atomic_thread_fence, __atomic_signal_fence and
__sync_synchronize. A node rather than a call because none of them is a function anywhere:
what they are is an access with an ordering on it, and an ordering is a thing the IR says
about an access rather than an argument something is passed. See check/builtin/atomic.rs.
The order is a value here rather than an operand, because it was a constant in the source and the ordering of an access has to be known when the access is built. A call that wrote a value the compiler cannot fold gets the strongest ordering, which is what gcc does and is the only safe reading of a question that has to be answered before the program runs.
Fields
Unreachable
__builtin_unreachable(), which is the program promising control does not get here.
It has no operands and no value, and it is a node rather than a call for the reason
ExprKind::VaArg is one: there is no function of the name for a call to reach. What it
carries is the promise itself, which the optimizer is where it will pay, and until then
what it costs to honour is nothing at all. See check/builtin/unreachable.rs.