While reading Crystal-lang internal code, I came across to some snippet like:
@[Primitive(:some_name)]
def some_method
end
May anyone explain me what the Primitive
attribute does and how it works (eg. tells the compiler to use a LLVM function)?
This is functionality that cannot be expressed in pure Crystal. Any time a call to such a method appears, it needs to be replaced with low-level instructions (LLVM IR) directly by the compiler, rather than being backed by an actual function.
This is needed because some operations are already at the lowest level. For example, how do you implement the addition of two numbers? Uhh, a+b
? Wait, but that's what we're trying to implement. So, other than resorting to ridiculous things like if a==1 && b==1 ...
this needs to be deferred to the CPU (through LLVM IR).
The file primitives.cr contains only empty shells of such methods, and also the documentation for them, so they look like a normal part of the standard library. They are marked by an annotation for the compiler to know when to replace them. And the real action happens in compiler/crystal/codegen/primitives.cr: there's a case
branch for each annotation name, which leads to code that generates appropriate code.
For completeness, here's an excerpt from primitives.cr:
Methods defined here are primitives because they either:
- can't be expressed in Crystal (need to be expressed in LLVM). For example unary and binary math operators fall into this category.
- should always be inlined with an LLVM instruction for performance reasons, even in non-release builds. An example of this is
Char#ord
, which could be implemented in Crystal by assigningself
to a variable and casting a pointer to it toInt32
, and then reading back the value.
Side note:
This is not the only way that the compiler has for implementing methods that can't be normal stdlib methods. This also happens for pseudo-methods like obj.is_a?
, on a syntactic level. This is an even "stronger" ability, and the pretense of it being a method is dropped (you won't find is_a?
in Object's documentation). This kind of construct is tied deeply into the compiler and can affect type information.