Hacker News new | comments | show | ask | jobs | submit login
The C# compiler and ‘Lowering’ (mattwarren.org)
78 points by matthewwarren 9 months ago | hide | past | web | favorite | 13 comments



Coming from Lisp background one would take this as pretty obvious approach.

In fact I somewhat suspect that CL's tagbody is explicitly designed such that every non-trivial control structure in the language could be expanded into that.


"Lowering" (though I've never heard it called that) is also handy in the course of working with formal semantics especially in the case of proofs and other serious reasoning. You can "port" your reasoning from the "lower" construct to the special case of the higher construct.

In general this also translates to simply understanding the semantics of a language. If you can describe something in terms of another concept the listener already has an intuition for, often it makes the new thing easier to understand and learn.

Though it's easy to imagine cases where the "lower" thing is so abstract that it's hard to apprehend in the first place (e.g. "everything is just a closure!").


I thought the usual term is "desugaring"


I have been caring a lot about languages and compilers for almost two decades now, and I have heard the term "lowering" often... but it means something different (and broader) than "desugaring". Essentially: I disagree with the linguistic argument of this author :/. Here is a comment I left elsewhere on this post with a bunch of examples from a variety of compiler projects showing how I have always heard and understood the term "lowering": https://news.ycombinator.com/item?id=14429533 .


The C preprocessor allows you to do lowering to some degree. If I remember correctly Lisp is pretty much built on the idea of extending the language by itself.

It would be nice if other languages allowed implementing "syntactic sugar" in an easy way. A lot of stuff that's done with reflection in C# could be replaced.


> It would be nice if other languages allowed implementing "syntactic sugar" in an easy way. A lot of stuff that's done with reflection in C# could be replaced.

They're talking about allowing that in C#, but it's not there yet, see https://github.com/dotnet/csharplang/issues/107


Nice. It's a little disappointing to read that Visual Studio is holding this up.


The E and Monte programming languages do this too, lowering from Full-E or Full-Monte to Kernel-E or Kernel-Monte via a canonical "expansion" phase. In Monte, this indeed is the bulk of the work done by the compiler, and the optimizer only operates on Kernel-Monte.


Yesterday I was doing some debugging on the async/await downlevel support in Typescript and found myself exploring the ways Typescript lowers code in the source itself (as much to satisfy curiosity as anything else, similar to this article series' experiments/explorations of the Roslyn codebase). In its case the similarities between Typescript itself and its target language provide a particularly interesting question of what counts as lowering to Typescript versus lowering to JS/ES, particularly in places where one becomes the other as JS/ES standardizes different pieces into the wild.

For Typescript "lowering" seems a more accurate term than "compiling" in just about all cases.


The way I understand it, "lowering" is just another word for "compiling" to intermediate representations. It probably exists because traditionally there aren't many IR, or none at all.

There have been attempts to express compilation as a series of many more, maybe 50, intermediate steps, implemented in some LISP. I don't know if there are success stories. I think there is always a tension between modeling data structures close enough to your understanding to enable clean implementation and not modeling so many data structures that one loses track of them.


The difference between compiling and lowering is that the syntax of the output of lowering is a strict subset of the syntax of its' input, whereas the output of compiling produces a completely different format, e.g. an AST, an in-memory graph, assembly, machine code, etc.

This makes implementing the next layer (compiler perhaps) simpler because it has to understand fewer syntax elements. This also makes a difference is that you could manually write the output of the lowered code yourself in the original language. Another effect is that (ideally) applying the lowering function L() to any code a second time should produce no change i.e. L(code) == L(L(code)). This is not true of compiling.


I am pretty sure you have added a lot of personal context to this word that is unrelated to how it is actually used: I will argue that for decades, the term "lowering" has meant a transformation to a "lower-level" representation, and that this representation often is "a completely different format".

https://www.cs.utexas.edu/users/cart/Scale/lowering.html

> Lowering is the transformation of higher level representations to lower level representations. For example, subscript expressions are lowered to address arithmetic operations. A compiler traditionally lowers a high level language to machine code in one or more steps.

https://en.m.wikibooks.org/wiki/GNU_C_Compiler_Internals/GNU...

> As a result of lowering a function its control-flow graph is generated.

http://tap2k.org/projects/WIL/

> Generally each data value will start with an abstract representation, which will in turn go through a sequence of lowerings as representation choices are made. These lowerings will eventually transform both the representation and the operations upon it to fully concrete forms, down to the level of the actual bit layout of the value in memory, allowing for easy low-level code generation.

https://pdfs.semanticscholar.org/92ea/93e93a77b80465e7aeb53d...

> Instead of the compiler backend lowering object operations to machine operations using hard-wired runtime- specific logic, XIR allows the runtime system to implement this logic, simultaneously simplifying and separating the backend from runtime-system details.

https://www.researchgate.net/profile/Michael_Burke9/publicat...

> After high-level analyses and optimizations are performed, HIR is lowered to low-level IR (LIR). In contrast to HIR, the LIR expands instructions into operations that are specific to the Jalapeño JVM implementation, such as object layouts or parameter-passing mechanisms of the Jalapefio JVM. For example, operations in HIR to invoke methods of an object or of a class consist of a single instruction, matching the corresponding bytecode instructions invokevirtual/invokestatic. These single-instruction operations are lowered (i.e., converted) into multiple-instruction LIR operations that invoke the methods based on the virtual-function-table layout.


> The difference between compiling and lowering is that the syntax of the output of lowering is a strict subset of the syntax of its' input, whereas the output of compiling produces a completely different format, e.g. an AST, an in-memory graph, assembly, machine code, etc.

Brilliant, thanks for that explanation, I understand 'lowering' even more now!




Applications are open for YC Summer 2018

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: