Category Archives: Julia

Working with a grouped data frame, part 1

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2024/03/01/gdf.html

Introduction

One of the features of DataFrames.jl that I often find useful is that when you group
a data frame by some of its columns the resulting GroupedDataFrame is an object
that gains new and useful functionalities.

Some time ago I have discussed how GroupedDataFrame can be filtered. You can find
this post here. In this post and the following one that I plan to write next
week I thought that it would be useful to review other key functionalities of
a GroupedDataFrame.

The post was written under Julia 1.10.1 and DataFrames.jl 1.6.1.

Creating a grouped data frame

You can create a GroupedDataFrame using the groupby function.

Here are some examples:

julia> using DataFrames

julia> df = DataFrame(int=[1, 3, 2, 1, 3, 2],
                      str=["a", "a", "c", "c", "b", "b"])
6×2 DataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
   3 │     2  c
   4 │     1  c
   5 │     3  b
   6 │     2  b

julia> show(groupby(df, :int), allgroups=true)
GroupedDataFrame with 3 groups based on key: int
Group 1 (2 rows): int = 1
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
Group 2 (2 rows): int = 2
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b
Group 3 (2 rows): int = 3
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
julia> show(groupby(df, :int; sort=true), allgroups=true)
GroupedDataFrame with 3 groups based on key: int
Group 1 (2 rows): int = 1
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
Group 2 (2 rows): int = 2
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b
Group 3 (2 rows): int = 3
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
julia> show(groupby(df, :int; sort=false), allgroups=true)
GroupedDataFrame with 3 groups based on key: int
Group 1 (2 rows): int = 1
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
Group 2 (2 rows): int = 3
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
Group 3 (2 rows): int = 2
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b
julia> show(groupby(df, :str), allgroups=true)
GroupedDataFrame with 3 groups based on key: str
Group 1 (2 rows): str = "a"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
Group 2 (2 rows): str = "c"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     1  c
Group 3 (2 rows): str = "b"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  b
   2 │     2  b
julia> show(groupby(df, :str; sort=true), allgroups=true)
GroupedDataFrame with 3 groups based on key: str
Group 1 (2 rows): str = "a"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
Group 2 (2 rows): str = "b"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  b
   2 │     2  b
Group 3 (2 rows): str = "c"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     1  c
julia> show(groupby(df, :str; sort=false), allgroups=true)
GroupedDataFrame with 3 groups based on key: str
Group 1 (2 rows): str = "a"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
Group 2 (2 rows): str = "c"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     1  c
Group 3 (2 rows): str = "b"
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  b
   2 │     2  b

What this example shows is that the key thing you need to remember
to decide about a grouped data frame is the order of groups.

There are two options here:

  • groups sorted by the grouping column value, when you pass sort=true;
  • groups sorted by the order of appearance of values in the source, when you pass sort=true.

You might ask what happens if you do not pass the sort keyword argument?
In this case either of the options is used depending on which one is faster.
Therefore, omitting sort, can be thought of as an information that the user does not
care about the order of groups but wants the grouping operation to be as fast as possible.

When does the order of groups not matter?

In some cases the order of groups is irrelevant (so you can safely skip passing it).
The most important scenario of this kind is when you use the select or transform function
with a GroupedDataFrame. The reason is that these functions anyway always keep the order of
rows from the source data frame (no matter how the groups are rearranged in a GroupedDataFrame).
However, it is not the case with combine, as it respects the order of groups in a GroupedDataFrame.

Let us see an example highlighting the difference between these cases:

julia> select(groupby(df, :int, sort=true), :str)
6×2 DataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
   3 │     2  c
   4 │     1  c
   5 │     3  b
   6 │     2  b

julia> combine(groupby(df, :int, sort=true), :str)
6×2 DataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
   3 │     2  c
   4 │     2  b
   5 │     3  a
   6 │     3  b

julia> select(groupby(df, :int, sort=false), :str)
6×2 DataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     3  a
   3 │     2  c
   4 │     1  c
   5 │     3  b
   6 │     2  b

julia> combine(groupby(df, :int, sort=false), :str)
6×2 DataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
   3 │     3  a
   4 │     3  b
   5 │     2  c
   6 │     2  b

As you can see select kept the rows in the order in which they are present in df no matter if we
passed sort=true or sort=false. On the other hand combine returns rows grouped by the groups and
the order of groups corresponds to their order in GroupedDataFrame, so passing sort=true or
sort=false in general changes.

Special operation specification syntax for working with grouped data frames

When discussing select or combine in conjunction with GroupedDataFrame it is important to mention
that there are four special cases of operation specification syntax designed specifically for working with
them. They are:

  • nrow to compute the number of rows in each group.
  • proprow to compute the proportion of rows in each group.
  • eachindex to return a vector holding the number of each row within each group.
  • groupindices to return the group number.

Each of them optionally allows you to specify the name of the target column by => syntax.
Here are some examples:

julia> combine(groupby(df, :int, sort=false), nrow)
3×2 DataFrame
 Row │ int    nrow
     │ Int64  Int64
─────┼──────────────
   1 │     1      2
   2 │     3      2
   3 │     2      2

julia> combine(groupby(df, :int, sort=false), proprow => "row %")
3×2 DataFrame
 Row │ int    row %
     │ Int64  Float64
─────┼─────────────────
   1 │     1  0.333333
   2 │     3  0.333333
   3 │     2  0.333333

julia> combine(groupby(df, :int, sort=false), eachindex)
6×2 DataFrame
 Row │ int    eachindex
     │ Int64  Int64
─────┼──────────────────
   1 │     1          1
   2 │     1          2
   3 │     3          1
   4 │     3          2
   5 │     2          1
   6 │     2          2

julia> combine(groupby(df, :int, sort=false), groupindices => "group #")
3×2 DataFrame
 Row │ int    group #
     │ Int64  Int64
─────┼────────────────
   1 │     1        1
   2 │     3        2
   3 │     2        3

Iterating a grouped data frame

Apart from using functions such as select or combine on a GroupedDataFrame it is useful to know
that it supports iteration. Therefore you can use a GroupedDataFrame in a loop or in a comprehension.
When iterated GroupedDataFrame returns data frames corresponding to the groups. Let us see:

julia> for v in groupby(df, :int, sort=false)
           println(v)
       end
2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b

julia> [v for v in groupby(df, :int, sort=false)]
3-element Vector{SubDataFrame{DataFrame, DataFrames.Index, Vector{Int64}}}:
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b

julia> collect(groupby(df, :int, sort=false))
3-element Vector{Any}:
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b

The last example has shown you that you can pass a GroupedDataFrame to a function expecting an iterable, in this case the collect function. The one exception to this rule is that you cannot use GroupedDataFrame with the map function directly:

julia> map(identity, groupby(df, :int, sort=false))
ERROR: ArgumentError: using map over `GroupedDataFrame`s is reserved

The reason is that it was not clear if such operation should produce a vector or a data frame, and it is easy enough to achieve both results with other means. If you want e vector use e.g. a comprehension. If you want a data frame use e.g. combine or select.

Advanced iteration

Sometimes, when iterating a GroupedDataFrame we might be interested not only in a data frame per group, but also in a value of grouping variable. This is easily achieved with the keys and pairs functions (depending on whether you only want grouping values or both grouping values and data frames):

julia> map(identity, keys(groupby(df, :int, sort=false)))
3-element Vector{DataFrames.GroupKey{GroupedDataFrame{DataFrame}}}:
 GroupKey: (int = 1,)
 GroupKey: (int = 3,)
 GroupKey: (int = 2,)

julia> map(identity, pairs(groupby(df, :int, sort=false)))
3-element Vector{Pair{DataFrames.GroupKey{GroupedDataFrame{DataFrame}}, SubDataFrame{DataFrame, DataFrames.Index, Vector{Int64}}}}:
 GroupKey: (int = 1,) => 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     1  a
   2 │     1  c
 GroupKey: (int = 3,) => 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     3  a
   2 │     3  b
 GroupKey: (int = 2,) => 2×2 SubDataFrame
 Row │ int    str
     │ Int64  String
─────┼───────────────
   1 │     2  c
   2 │     2  b

I used the map function to show you that it is only reserved to use it with plain GroupedDataFrame.

Working with group keys

As you can see in this example each group in a GroupedDataFrame is associated with a GroupKey. To get all
keys use the keys function:

julia> keys(groupby(df, :int, sort=false))
3-element DataFrames.GroupKeys{GroupedDataFrame{DataFrame}}:
 GroupKey: (int = 1,)
 GroupKey: (int = 3,)
 GroupKey: (int = 2,)

Let us, as an example extract the last key so see how one can work with it:

julia> key = last(keys(groupby(df, :int, sort=false)))
GroupKey: (int = 2,)

You can get a value of the key by property access or indexing:

julia> key.int
2

julia> key[1]
2

julia> key["int"]
2

julia> key[:int]
2

It is also easy co convert GroupKey to a dictionary, vector, Tuple or NamedTuple if you would need it:

julia> Dict(key)
Dict{Symbol, Int64} with 1 entry:
  :int => 2

julia> collect(key)
1-element Vector{Int64}:
 2

julia> Tuple(key)
(2,)

julia> NamedTuple(key)
(int = 2,)

Note that, in general, you can group a data frame by multiple columns so you could query value of any grouping column
in the examples above. If you needed to get a list of grouping columns use the groupcols function:

julia> groupcols(groupby(df, :int, sort=false))
1-element Vector{Symbol}:
 :int

Conclusions

In this post we have learned how one can create a grouped data frame and how to choose the order of groups in it.
As a follow-up we have shown how GroupedDataFrame interacts with functions like select or combine.
Next we discussed iterator interface support by GroupedDataFrame and how to get and use information about
values of grouping columns for each group. I hope you found these examples useful.

In the post next week we will discuss how GroupedDataFrame supports the indexing interface.

GSoC in LLVM 2024

By: Miguel Raz Guzmán Macedo

Re-posted from: https://miguelraz.github.io/blog/gsoc2024/index.html

I'm trying to get a GSoC 2024 in LLVM

and I will be documenting my work with this ongoing blogpost in reverse chronological order.


If you want to see more posts like this, consider chucking a buck or two on my GitHub sponsors, or, you know, hire me as a grad student.


29/02/2024

"hazlo cobarde"

Add the 3 way comparison instruction <=> to LLVM.

I like this GSoC in particular because

  • I will learn a wide swath of LLVM

  • I'll be working with a lot of optimization passes

  • I'll get to bring cool perf to C++/Rust and Julia

  • I was dared by the other, more talented Miguel to actually help improve LLVM

Next task

Add a new intrinsicLangref, then Intrinsics.td, then maybe the pass verifier.

I've already put up a sample PR and got redirected on what looks like the proper working path for this endeavour.

GSoC in LLVM 2024

By: Miguel Raz Guzmán Macedo

Re-posted from: https://miguelraz.github.io/blog/gsoc2024/index.html

I'm trying to get a GSoC 2024 in LLVM

and I will be documenting my work with this ongoing blogpost in reverse chronological order.


If you want to see more posts like this, consider chucking a buck or two on my GitHub sponsors, or, you know, hire me as a grad student.


23/02/2024

Only added stubs for where ISD::[US]CMP will go in the LegalizerDAG.cpp. Some code is better than no code!

22/02/2024

Back from exams. I let my mentors know I wouldn't be around for a few and would come back later.

Omg Nori taught me how to use vim marks (even within VSCode). ma will set a mark a and 'a lets you jump back to it. If you set an uppercase one, it's GLOBAL and you can jump between files!

19/02/2024

Got feedback for the proposal. Seems people like not reading ChatGPT scripts, whodathunkit. Aggregated suggestions and sent it off. Should update if I get more updates.

Also, rebased the next step of the PR, which is merging into the SelectionDAG

Alright, pushed some code that probably fails on everything but can't get more brainpower on learning the casting RTTI APIs today.

18/02/2024

IT GOT MERGED!

I wrote up the draft proposal, and sent it for feedback from the mentors.

Tomorrow I'll try to get started on the next steps of the proposal and send a dummy PR to get to the good stuff.

17/02/2024

Actually just rested yesterday. 'Twas good. Today I'll do some light reading.

  • isa<>, cast<>, dyn_cast<> templates

    • isa<> returns true/false if it's an instance of the specified class

    • cast<> is a "checked cast"

    • dyn_cast<> usually used in if (auto *AI = dyn_cast<AllocationInst>(Val)) {

  • StringRef and TwineValue class and StringMap must be generic over null chars, can't take a const char*

  • Error handling: abort early, use assert(cond && "you done goofed");

enum { Foo, Bar, Baz } X = foo();switch (X) {
  case Foo: /* Handle Foo */; break;
  case Bar: /* Handle Bar */; break;
  default:
    llvm_unreachable("X should be Foo or Bar here");
}
LLVM_DEBUG(dbgs() << "I am here!\n");

and then

$ opt < a.bc > /dev/null -mypass
<no output>
$ opt < a.bc > /dev/null -mypass -debug
I am here!
  • Use the Statistic class!

  • stopped at picking the right data structure.

16/02/2024

Start: read LLVM Programmer's Manual, then start with some local changes to the SelectionDAG.

15/02/2024

Derp, I kept getting "undefined intrinsics" errors when running FileCheck like

> ../../../build/bin/opt -S -passes=verify 2>&1 intrinsic-cmp.ll | FileCheck intrinsic-cmp.ll

… because I had to rebuild LLVM and have the new opt pick it up. Once that happened, even the new llvm-lit passed 😀

I also had to remove the CHECK-LABEL, still not sure why. Lemme read that for a bit.

Also, I should update mold – it failed with an unknown --long-plt option.

Here's my LLVM build instruction:

> cmake -S . -B build -G Ninja -DLLVM_TARGETS_TO_BUILD=X86 -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug -DLLVM_OPTIMIZED_TABLEGEN=ON -DLLVM_USE_NEWPM=ON -DLLVM_USE_SPLIT_DWARF=ON -DLLVM_PARALLEL_LINKER_JOBS=10 -DLLVM_PARALLEL_COMPILE_JOBS=10
> mold -run ninja -C .

Windows builders take freakin' forever to run.

  • Coding Standards

    • prefer llvm::DenseMap over std::unordered_map and similar. No std IO plz.

    • comments – write what does is trying to do and why, not how

    • spaces not tabs

    • prefer C++ style casts like auto DestVecLen = cast<VectorType>(DestTy)->getElementCount(); for getting the Vector length

    • beware auto copies:

// Typically there's no reason to copy.
for (const auto &Val : Container) observe(Val);
for (auto &Val : Container) Val.change();// Remove the reference if you really want a new copy.
for (auto Val : Container) { Val.change(); saveSomewhere(Val); }// Copy pointers, but make it clear that they're pointers.
for (const auto *Ptr : Container) observe(*Ptr);
for (auto *Ptr : Container) Ptr->change();

* use early returns, specially in loops:

// not this
for (Instruction &I : BB) {
  if (auto *BO = dyn_cast<BinaryOperator>(&I)) {
    Value *LHS = BO->getOperand(0);
    Value *RHS = BO->getOperand(1);
    if (LHS != RHS) {
      ...
    }
  }
}
// prefer this
for (Instruction &I : BB) {
  auto *BO = dyn_cast<BinaryOperator>(&I);
  if (!BO) continue;  Value *LHS = BO->getOperand(0);
  Value *RHS = BO->getOperand(1);
  if (LHS == RHS) continue;  ...
}

* other good tips for no *else* after a *return*
* turn predicate loops into functions
* assert liberally! you may find other people's bugs! assert(Ty->isPointerType() && "Can't allocate a non-pointer type!");
* consider llvm::unreachable("invalid radix for integer literal")
* never using namespace std
* don't evaluate end() every time through a loop!

// not this
BasicBlock *BB = ...
for (auto I = BB->begin(); I != BB->end(); ++I)
  ... use I ...// prefer this
BasicBlock *BB = ...
for (auto I = BB->begin(), E = BB->end(); I != E; ++I)
  ... use I ...

* lol #include <iostream> is forbidden, as well as std::endl
* prefer preincrement (++x)
* anonymous namespaces allow more aggressive optimizations! make them small and only use them for class declarations
* omit braces on short if's -_-
* read "Effective C++" by Scott Meyers or "Large Scale C++ Software Design" by John Lakos. later.

BIG ALERT! We're passing all tests and Nikita says "LGTM!" Huzzahhh!

I finished reading the Coding Standards and will now read the LLVM Programmer's Manual.

Afternoon: only 1 reviewer missing approval!

14/02/2024

It's close to finishingggggggggg

Nikita mentioned the CodingStandards, I'll give that a read in a bit.

In FileCheck unit tests, it's just 2 spaces, not tabs or 4 spaces.

Dhruv recommends using git clang-format or Format Modified Lines in VSCode.

I only have to make FileCheck pass now and Nikita says it should be good!

13/02/2024

OK, I just saw that both Nikita Popov and Dhruv Chawla are listed as mentors for the project. Should try and reach out before the application period begins.

Nikita commented that there's two good places to look into (which I should do meanwhile):

Notes on the SelectionDAG

  • GlobalISel and SelectionDAG both translated LLVM code to target specific machine instructions.

  • In theory Intrinsics.td should handle it all but some parts need custo C++ code.

  • SDAGs represent Machine Value Types and DataFlow types. Dataflow types provide ordering.

  • Legal vs illegal DAG: only use supported ops on supported types

  • SDAG phases: build, optimize, legalizeTypes, optimize, legalize, select instructions, scheduling * common to use -debug-only=isel/-dump and -filter-print-funcs=<function-name>

    • several flags can print SDAG after each pass

  • OK, effective sign extension elimination seems to be highly non-trivial optimizations

  • Oohhhh TableGen representing stuff as a DAG lets you build the SelectionDAG on top of it!

After a few hours of noodling with PR reviews, it's clear I gotta jump on a next one/help PR review some other people. Maybe it's not great to go full mercenary on every good-first-issue and try and help others along the way.

ParticleSwarmOptimization was kind enough in the LLVM/beginners Discord chat to point out that I should have a full build of LLVM and then run llvm-lit from there on a unit test. Sure enough, that let me do gh pr checkout 84903 and then LIT llvm/test/CodeGen/AArch64/hadd-combine.ll to check that a unit test properly ran. Now I can help verify my own and other people's PRs! yay!

That seems like enough for today, I'll hunt for some good issues tomorrow.

12/02/2024

Did a bunch of very quick fixes and got some neat feedback.

11/02/2024

Ohhhhhh neat! Got some really fast feedback this time around on stray newlines and a couple fixes on the PR. Things are moving faster and faster! I think I'm only missing adding some FileCheck tests for the invariants under the Verifier.cpp stuff that isn't handled by Intrinsics.td but that sounds about it!

FileCheck reading I'll do for a bit.

Note: If you are going to check multiple things in a single file, use a CHECK-LABEL to avoid spurious matches.

Sometimes, a CHECK-NEXT is useful as the last unit test.

Learned the LLVMIR verbose call syntax goes (gotta include types in call site).

That should do it for today, see y'all tomorrow.

10/02/2024

Plan for today: Read the IR Verifier, add code to it if needed, then work on adding to SDAG node.

IR Verifier code: It's under llvm/IR/Verifier.h and llvm/IR/Verifier.cpp.

Ok, read the code for a bit. What I'm interested in in Verifier.cpp is the huge switch statement towards the end of the file where the verification of intrinsics happens.

Oh neat, learned a lot of places that probably need modification via searching case Intrinsic:: in the llvm folder.

Neat! VectorUtils.cpp looks like where we add SIMD 😀

There is also a SelectionDAGBuilder.cpp that seems like I should update stuff there.

Ah, seems the SDAG needs the ID node to be added to the ISDOpcodes first, like ISD::SCMP and such.

Intrinsic Function: start with llvm. prefix. Must always be external functions. If any are added, must be documented in LangRef. Have naming convention on type name return.

In TargetLowering.h:

/// This class defines information used to lower LLVM code to legal SelectionDAG
/// operators that the target instruction selector can accept natively.
///
/// This class also defines callbacks that targets must implement to lower
/// target-specific constructs to SelectionDAG operators.
class TargetLowering : public TargetLoweringBase {

Updated the PR to not include a dirty file from the ValueTracking.cpp unit tests.

Sweet, rebased the spaceship-intrinsic branch changes into a new one.

Now I can go for

lib/CodeGen/SelectionDAG/SelectionDAG.cpp:Add code to print the node to getOperationName. If your new node can be
evaluated at compile time when given constant arguments (such as an add of a constant with another constant), find the getNode method that takes the appropriate number of arguments, and add a case for your node to the switch statement that performs constant folding for nodes that take the same number of arguments as your new node.

Which gives on Line 6629 of SelectionDAG.cpp

SDValue SelectionDAG::getNode(unsigned Opcode, const SDLoc &DL, EVT VT,
                              SDValue N1, SDValue N2, const SDNodeFlags Flags) {

where I can add an case ISD::UCMP/case ISD::SCMP.

Oh wow, having VSCode let me just hover over a type and give me info is amazing.

Alright, did some good work today I think.


Got some feedback on typo fixes from other people and some concrete directions from Nikita:

def int_scmp : DefaultAttrsIntrinsic<
    [llvm_anyint_ty], [llvm_anyint_ty, LLVMMatchType<1>],
    [IntrNoMem, IntrSpeculatable, IntrWillReturn]>;

for the Intrinsics.td so that we can have a "result type overload over a fixed type" and then

and then add a check in Verifier.cpp that a) the return type and the first argument type have the same number of elements (if they are vectors) and b) the return scalar type has width at least 2.

So I've pushed a commit already to have the Intrinsics.td that way and I will now add a case to the Verifier.cpp in the void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { to add

(Many hours later) Put in a good first effort into the Verifier.cpp cases for Intrinsic::scmp/ucmp. It's probably a bit boneheaded but I've been getting really good and fast feedback on the PRs, so I feel encouraged to keep the hot streak going.

09/02/2024

Lost the renaming battle to standard practices. Can't complain. Updated the PR to reflect that.

Now, reading the DCE pass code.

Ok, Andrzej Warzyński did an incredibly useful tutorial for llvm-tutor.

I'll try writing the Transformation pass, since it's closes to what I need.

Notes: * LLVM_DEBUG is super useful.

#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"#define DEBUG_TYPE "mba-add"
STATISIC(SubstCount, "The # of substituted instructions"

and then you can do:

LLVM_DEBUG(dbgs() << *BinOp << " -> " << *NewInst << "\n";
    // or , with Statistic and `-stat` on the `opt` CLI and debug build
    ++SubstCount;
  • Analysis inherits from AnalysisInfoMixin

  • Very common pattern:

for (auto &Func : M) {
    for (auto &BB : Func) {
        for (auto &Ins : BB) {
            ...
  • You call PreservedAnalysis.abandon() when you wanna bail on a logic.

  • FileCheck is a pattern matching tool that comes with LLVM. Can check emitted assembly output for test correctness.

  • Rely on CMake's find_package and add sanity-checks to your scripts!

  • LLDB is your friend

lldb -- $LLVM_DIR/bin/opt -load-pass-plugin lib/libMyPass.so -passes=my-pass -S dummy.ll
(lldb) b MyPass::run
(lldb) r

Finally: started a branch called scmp-and-ucmp and I'll start trying out changes there whilst people make up their minds.

8/02/2024

Jyn strikes again and sometimes you can just do make after cmake because some stuff is optional.

Otherwise, install:

sudo apt install libzstd-dev libcurl4-openssl-dev libedit-dev

to get rolling.

Once you do

# Run the pass
$LLVM_DIR/bin/opt -load-pass-plugin ./libHelloWorld.{so|dylib} -passes=hello-world -disable-output input_for_hello.ll
# Expected output
(llvm-tutor) Hello from: foo
(llvm-tutor)   number of arguments: 1
(llvm-tutor) Hello from: bar
(llvm-tutor)   number of arguments: 2
(llvm-tutor) Hello from: fez
(llvm-tutor)   number of arguments: 3
(llvm-tutor) Hello from: main
(llvm-tutor)   number of arguments: 2

using -disable-output means no bitcode gets produced.

Passes come in 3 flavors, mostly: Analysis, Transformations and CFG manipulations.

Note that clang adds the optnone function attribute if 1) no opt level is specified or -O0 is specified.

Ah, forgot to run the cmake .. -> mold -run make -j and had some passes missing. Derp.

Minutes lost to cmake bullshit: 60.

Oh sweet! I just learned that I can write an injection pass that will give me a new binary and it will print out cool analysis info.

Also, if I get an instrumented binary I can just use lli to interpret the .ll file directly.

You can also build a static binary that will run that analysis for you!

Ran a bunch of passes with opt and friends. Transformation passes will normally inherit from PassInfoMixin.

Analysis Passes will inherit from AnalysisInfoMixin.

Ok, cool I an outdated example in llvm-tutor in the examples and sent a PR for it.

Tomorrow I shall dive into those optimization passes at the end and hopefully run some good lit tests.

7/02/2024

Alright, did some good catchup on the semester and pushed the PR forward a bit.

I also had an awesome "uniwtting tourist" genius moment by pointing out the return type could be different than what Nikita had thought of.

Today I setup my environment with

wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"
sudo apt-get update
sudo apt-get install -y llvm-17 llvm-17-dev llvm-17-tools clang-17

so now I can run opt shenanigans without awkward path invocations.

2/02/2024

I'm waiting for others to chime in on my latest fix so I will be reading on how to add unit tests.

Cool, that took like 25 minutes and I pushed a commit into the unit test generation framework.

Took me a bit but tried adding using llvm-lit and couldn't get the Examples folder to run, so I posted a question in the #beginners channel about it.

1/02/2024

Sweet! I was able to address Nikita's refactoring comments without too much hassle.

I guess the next task is to add it to the Verifier.

-> I read the llvm/lib/IR/Verifier.cpp header and grep'd for smin. Seems I can add a

Intrinsics::vector_reduce_sthreecmp:
Intrinsics::vector_reduce_uthreecmp:

on line 5378 and get away with this. I don't see other places where it's defined.

I think I've hit my potential here. I will go do some tutorials.

29/02/2024

"hazlo cobarde"

Add the 3 way comparison instruction <=> to LLVM.

I like this GSoC in particular because

  • I will learn a wide swath of LLVM

  • I'll be working with a lot of optimization passes

  • I'll get to bring cool perf to C++/Rust and Julia

  • I was dared by the other, more talented Miguel to actually help improve LLVM

Task 1: Add to LangRef

Add a new intrinsicLangref, then Intrinsics.td, then maybe the pass verifier.

I've already put up a sample PR and got redirected on what looks like the proper working path for this endeavour.

Oh neat, I've finished. Only took about 1 hour with careful copy pasting. I'm probably blundering the return type being iM bits, but someone will correct me.

Now, I need to add an entry of the intrinsic into TableGen.

Task 2: Add to Intrinsics.td

Whelp, I guess I gotta learn tablegen and then this Intrinsics.td file.

Ok, I found a non-intimidating TableGen overview.

Gotta find the optimization wizardry in there after reading for a few minutes.

Alright, took more than a bit of an hour but I got pushed something for this task.

Pinged Nikita to get some adults to look at my horrible TableGen incantation.

See ya tomorrow.