Why did the `InlineArray` type sugar proposal come to review?

This comment probably belongs in this thread instead:

I find it stange that the proposal does not reference prior art in LLVM IR, which seems to me like the reason this is proposed in this current form in the first place, non?

4 Likes

I am going to venture to say: I wish they appreciated the beauty of x, which is used in LLVM IR.

If x is good enough for LLVM, why should x not be used in Swift's type sugar for InlineArray?

[<number of elements> x <element type>]

[10 x i32]

[5 x float]

[2 x [3 x i8]]

I tinkered with LLVM IR a decade ago when writing a compiler, so I had to double check. :slight_smile:

I asked Gemini

Hello, Gemini. How does LLVM represent arrays in its IR? Do you think the syntax used is easy to understand?

Hello! That's a great question. LLVM's Intermediate Representation (IR) represents arrays in a way that's quite explicit about their structure and memory layout. Let's break it down:

How LLVM IR Represents Arrays

In LLVM IR, arrays are represented as derived types. This means an array type is constructed based on another type (the element type) and a size (the number of elements). The general syntax looks like this:

Code snippet[ <# elements> x <element type> ]

Let's dissect this:

  • [ and ]: These square brackets enclose the array type definition.
  • <# elements>: This is an integer constant specifying the number of elements in the array. The size is fixed at compile time.
  • x: This symbol acts as a separator between the number of elements and the type of each element.
  • <element type>: This is the type of each individual element within the array (e.g., i32 for a 32-bit integer, float, another struct type, or even another array type for multi-dimensional arrays).

Examples:

  • An array of 10 integers (32-bit) would be represented as: [10 x i32]
  • An array of 5 floating-point numbers would be: [5 x float]
  • A 2x3 two-dimensional array of 8-bit integers (represented as an array of 2 arrays, where each inner array has 3 i8elements) would be: [2 x [3 x i8]]

Key Aspects of LLVM Array Representation:

  1. Static Size: The size of the array is part of its type and must be known at compile time. LLVM IR doesn't directly represent dynamically sized arrays in the same way. Dynamic allocation and manipulation are handled using pointers and memory management instructions (alloca, malloc, free).
  2. Contiguous Memory: LLVM assumes that the elements of an array are stored in contiguous memory locations. This is crucial for generating efficient code.
  3. No Bounds Checking (Implicit): LLVM IR itself doesn't inherently include bounds checking for array accesses. These checks, if needed, are typically implemented at a higher level (e.g., in the source language compiler).
  4. Accessing Elements: To access individual elements of an array in LLVM IR, you typically use the getelementptr(GEP) instruction. GEP calculates the address of a specific element within the array based on the base address of the array and the indices you provide.

Do you think the syntax used is easy to understand?

I think the syntax for representing array types in LLVM IR is relatively easy to understand, especially considering its purpose as an intermediate representation for compilers. Here's why:

  • Explicit and Direct: The syntax directly reflects the fundamental properties of a static array: a fixed number of elements of a specific type. The [size x type] format is quite intuitive.
  • Consistent Structure: The pattern is consistent, making it easier to parse and reason about array types, even for multi-dimensional arrays. The nested structure clearly shows the hierarchy of dimensions.
  • Low-Level Focus: LLVM IR operates at a lower level, closer to how memory is organized. The explicit size and element type align with this focus.

However, it's important to remember that LLVM IR is not meant to be directly written or read by most programmers. It's an internal representation for compiler optimization and code generation. For someone unfamiliar with compiler internals or low-level concepts, the syntax might initially seem a bit terse or abstract compared to high-level language array declarations.

In summary: While the LLVM IR syntax for arrays might not be as immediately user-friendly as array declarations in languages like Python or JavaScript, it is quite effective and understandable within the context of compiler design due to its explicitness and direct mapping to memory organization.

What are your thoughts on it? Do you find it intuitive, or are there aspects that seem less clear?

I asked Perplexity

Hello, Perplexity! How does LLVM represent arrays in its IR? Do you think the syntax used is easy to understand?

LLVM represents arrays in its Intermediate Representation (IR) using a clear and consistent syntax:

[N×T]

where N is the number of elements and T is the type of each element. For example, an array of 341 64-bit integers is written as [341×i64], and a two-dimensional array of 3 rows and 4 columns of 64-bit integers is [3×[4×i64]].

Key Points on LLVM IR Array Syntax:

  • The array type is always specified with the number of elements and the element type: [N×T].
  • Arrays can be nested for multidimensional arrays, such as [3×[4×i64]].
  • Arrays are of fixed size, determined at compile time.
  • Accessing array elements typically involves the getelementptr (GEP) instruction, which calculates the address of a specific element within the array.

Example:

text

@my_array = global [10 x i32] zeroinitializer

This declares a global array of 10 integers, all initialized to zero.

Ease of Understanding:
The syntax is generally considered straightforward and easy to understand for anyone familiar with C-like languages or type systems, as it closely resembles the mathematical and C-style notation for arrays. The use of explicit size and type makes the structure clear. However, working with arrays in LLVM IR can become more complex when dealing with pointers, multidimensional arrays, or dynamic memory management, as these require additional instructions and type casts.

Summary Table:

LLVM IR Syntax Meaning Example
[N x T] Array of N elements of type T [10 x i32]
[M x [N x T]] 2D array (MxN) of type T [3 x [4 x i64]]

In conclusion, LLVM's array syntax in IR is concise and mirrors familiar array notations, making it accessible for most programmers, though manipulating arrays at the IR level can require a deeper understanding of pointer arithmetic and memory layout.

3 Likes

I think the "prior art" argument is not at all an argument in favor of a decision:

  • The context in which the prior art decision was made was almost certainly different, with different assumptions, different problems to solve, and different priorities.
  • The prior art decision was made according to a different set of rules. There's no telling what standards of review the original authors were following when making the decision.
  • The prior art decision was made a while ago and all lessons learned since then related to that decision will be lost.

For all of these reasons, when I see any "prior art" argument, what I understand is this: "I didn't make a decision here, so I'm going to take an existing decision and hope that it won't backfire".

Simply taking an existing decision that may be irrelevant or inappropriate for the use case at hand is not at all an acceptable means of coming up with solutions to practical problems.

EDIT:

Moreover, how famous the prior art is has no baring on its validity. There are examples of very famous mistakes and their fame doesn't make them any less of a mistake.

3 Likes

I just want to say "prior art" arguments can provide valuable context information.

I think in this particular case, the reasons why some reviewers find x "not serious" are somewhat because this syntax is alien to them. It can really help to make people feel more comfortable by conveying this message to the audience in the process, and I personally softened my attitude towards x thanks to this information.

4 Likes

For me, It doesn't look alien at all, it looks utterly silly to me solely because it looks like it isn't compatible at all with the design philosophy of Swift syntax.

It may look absolutely fine for LLVM IR, which is extremely terse, primarily designed for debugging, and thus is optimized for conveying most information in least amount of space.

Swift syntax, on the other hand, has completely different priorities: It is designed to be written by hand at a large scale and maintained for a long time, thus clarity is far more important than terseness.

EDIT:

I understand that terseness that inherently doesn't come at the cost of clarity ends up improving clarity by making the syntax easier to read. My argument is that this isn't the case in this instance.

1 Like

Yes. And LLVM IR has completely different design philosophy to Swift. x just doesn’t fit into Swift’s. Additionally, considering how it would look and be used in other contexts is important. [10 x 10] appears like multiplication not like repetition (as far as I know this doesn’t exist in LLVM IR, could be wrong)

EDIT: I just realized this topic didn’t belong here. And should be in the other thread.

3 Likes

This is not really what textual LLVM IR is at all. Yes, it contains a lot of information, but it is also remarkably verbose.

Isn't it true that the verbosity of LLVM IR serves the purpose of conveying more low-level implementation details that are useful for understanding exactly what the instructions are doing, but is unnecessary and counter-productive for high-level code?

This is what I mean by "terse": it doesn't mean "not enough characters", it means "too much encoded meaning per character".

EDIT:

Maybe "terse" is not the best word to describe what I mean. If you know of a better word for it, please let me know!

We're on a bit of a tangent here, so this is all I'll write, but I would say LLVM's textual IR tries to beš precise, without much regard for terseness. E.g., something like:

%res = call {i32, i1} @llvm.ssub.with.overflow.i32(i32 %a, i32 %b)

in Swift (which itself is not terse) would just be:

let res = a.subtractingReportingOverflow(b)

Low-level Swift is--from one viewpoint--just a nicer and stable syntax to write LLVM IR, which provides some justification for x. But there are also perfectly reasonable arguments against it.


š At least nowadays. Some of the older IR decisions definitely sacrificed precision a bit.

This thread is about the decision to bring the proposal to review and shouldn't become a second place to substantively discuss the proposal. Would you all like me to just move the recent line of discussion to the review thread?

7 Likes

Imo the information about LLVM IR details is off-topic in both threads — therefor, I'd leave it here.

I thought of posting these messages in the other thread, but I chose to post them here because the point I was trying to make pertained more to review procedures themselves, rather than this specific instance. I merely referenced this specific instance due to recency and relevance.

But I completely defer to your judgment as to which thread this discussion belongs to.

1 Like

My guess is that LLVM IR is made for tools and compiler engineers, not every-day development. I also don't like x for another reason: it adds yet more exceptions to an already complex language.

Now operators will never be letters, except when they are.

Here it's natural but inflammatory when participants view discussion as a contest with advocates, or consider how to convince the decider in their favor. That approach can make silence or non-responsiveness appear to be intransigence, or can end up targeting LSG members and influencers.

But because it's natural, I would hope the review managers can do more to re-orient reviewers; currently, the notion in the review introduction of "constructive criticism" is not bearing the weight it's trying to hold.

It's already been said evolution discussions are mainly to proof the proposal, hardening it against omission, mistake and misdirection. Author silence on an issue should mean confidence in the proposal on point; it's good for authors to let the proposal speak for itself.

Still, as with testing, the question is how you know when you're done. (Exhaustion is not the best measure.)

Consider the analogy of law: there, only when the issue is ripe should courts decide things. Ripeness in that context means there is a concrete controversy, the facts and law at issue have been sufficiently developed, and delaying a decision would be a waste.

Sometimes timing matters most: when later relief would be pointless, courts address even under-developed controversies.

Concreteness means decisions should be as narrow as necessary for the present need, avoid deciding future aspects or abstract aspect. Extensions should be included only if there's real consensus that it's better to include the larger issue because there's really no likelihood of another decision, and others would benefit from relying on the broader scope.

When is a proposal sufficiently developed? Continuing the analogy, any language design or implementation principles should be raised, and the facts would be particular use cases. Where the issue is usability, the cases would include impressions/significance of those cases by representative sample of the audience. As in testing (for software or medical devices), impact matters: even rare incidence of catastrophic consequences can block a feature, and stakeholders can be key to future success.

Finally (and again like testing) you can be complete but wasteful - duplicating effort, not isolating distinct issues or connecting independent ones, focusing on the doable or seeable rather than the important, etc. So ripeness as a principle needs relevance and economy to be sweet and avoid rot -- which again mean people might not respond when issues have been sufficiently raised, or might need a few interactions to surface the crux of an issue.

Discussions and decisions will be unwieldy in many cases -- when they are matters of taste, or require trade-off's or assembling multiple perspectives, etc. Even a ripe and clean decision can involve incommensurable values and future predictions. But still, those decisions need making.

For reviewers to avoid needless umbrage and contribute (as needed), they will have to actively drop natural but misplaced assumptions about advocacy and contest, orient themselves around finding gaps in the proposal or the understanding of its significance, avoid repetition and misdirection, and adopt a belief in the good faith of other participants, the author, and the deciders.

The Swift Evolution Process [1] provides concrete questions for proposal reviewers included in the start of every review thread. But to really orient reviewers to the difference in focus and progress of proposal discussions, it might not be enough to say, "The goal of the review process is to improve the proposal under review through constructive criticism." Advocacy is natural but misplaced here, so up-front elaboration of what constitutes a complete or productive review might help people find the best way to contribute. Also review managers might consider mid-stream ripeness assessments to identify hot spots or gaps, timing and significance, in order to focus further discussion and model reviewer considerations when participating.

[1] https://github.com/swiftlang/swift-evolution/blob/main/process.md

2 Likes

I think this sums up how this proposal feels to me, and it's not the first time I've felt like this. There's a pitch thread with good feedback, not all of which can/should addressed right away. However then the proposal goes to review without sufficient mention of that feedback, and it feels like the feedback has been completely ignored. The feedback usually gets addressed in the review, but it seems like it could proactively be put somewhere in the proposal to avoid the conflict that inevitably comes up as a result.

8 Likes