4
$\begingroup$

There are situations during compilation when some identifier represents a particular value ("contained" in a variable, etc), but sometimes it references the variable's memory location directly.

How does the compiler know which is intended, to generate the correct code?

$\endgroup$
2
  • 2
    $\begingroup$ It sounds like you're asking about rvalues vs lvalues, to use the C++ terminology. Is that correct? $\endgroup$
    – Bbrk24
    Commented May 31, 2023 at 21:11
  • $\begingroup$ In essence yes. But there are situations where you would still treat an lvalue like an rvalue, depending on context, but I have been struggling to implement this all correctly in my own compiler project $\endgroup$ Commented Jun 1, 2023 at 4:35

2 Answers 2

5
$\begingroup$

The easy way of answering this is to just define, in each grammatical context, whether the name of a reference means the reference itself or the referenced value. So if x is a reference, and a reference in a simple name expression means a reference, then x + 7 gives a type error; inconvenient, but easy for the compiler. Then it's the user's responsibility to write *x + 7 when they want the referenced value. However, even strongly-typed languages like Rust usually aren't this pedantic about references.

The more ergonomic way of doing it is by implicit coercions, using information about static types. Let's use a more familiar example from Java:

int x = 5;
System.out.println(x + 2.5);

Here the variable x is an integer, but its value must be a double for the expression x + 2.5 to make sense. The compiler knows (based on the static types of the expressions) that the expression x is expected to produce a double but it is actually of type int, and so it inserts a coercion from int to double.

Likewise, consider the following Rust code:

let x_val = 5;
let x_ref = &x_val;
print!("{}", x_ref + 7);

In this code, x_ref has type &i32, a reference to an integer. But in the expression x_ref + 7, an i32 is expected. The Rust compiler knows both of these facts, because of the static type of x_ref and the static type expected by the + operator (given that its other operand is an integer), so it inserts a coercion from &i32 to i32 by dereferencing.

$\endgroup$
4
$\begingroup$

What I think you're referring to, but correct me if you meant something else, is this problem:

When looking at a node in my AST that refers to a variable (or worse, something else that can have its address taken), how do I know whether to emit IR/code to take its address, or to load its value?

And well, you don't. It depends on the context in which it is used. So you only know that one level up, and maybe not even then (consider a chain of record.field.field.field nodes). So here are three things you could do about it, while walking over your AST:

  • One option is to pass down a flag that says whether you want the value or the address.
  • Another option is to pass up the fact that you implicitly took the address of a variable, and then let it be up to the AST node that uses that thing. The address-of operator would turn that implicitly-taken address into a normal value that just happens to be an address, if it's the target of an assignment then you can store to the address, if it's used in a value context then you dereference the implicitly-taken address there. This option would tend to spam your IR with things that need to be optimized out while it may be non-trivial to do so.
  • Another option is having two different "visitors" (one for value contexts and another for address contexts) that use each other as required.

There is a similar distinction between evaluating a comparison for the purpose of branching on it, or for reifying it into an actual boolean that can be stored somewhere.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .