-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Abstract
Nim ABI should be documented as undefined by default, allowing the compiler to make free optimization choices, with the possibility to define compatibility options.
Motivation
No response
Description
Currently, the ABI of Nim is not well-defined, except that it's loosely based on whatever the backend decides to emit.
There exist some backend-specific pragmas to control some aspects of the ABI - for example {.packed.}
, {.align.}
, {.bycopy.}
etc, but these are spotty and live in a vacuum of otherwise undefined behavior - for example, how parameters are passed depends on undocumented and arbitrary features like the size of the object.
With this proposal, the idea would be two-fold:
- enshrine the undefined:ness in the specification, explicitly pointing out for example that the parameter passing distinction between pointer and value may change and that the order of fields in an
object
may change / be reorganised by the compiler as it sees fit - document the ABI more in detail when the code is annotated with
exportc
- this means defining behaviors and disallowing the use of features with undefined behaviors in such functions- example: if a function is tagged
exportc
, thevar
and "ordinary" parameter passing should be well-defined and documented - alternatively, it should be disallowed and only be allowed with further more specific annotations (iebycopy
). exportc
for object would force the compiler to generate fields inC
order and rules- etc
- example: if a function is tagged
Allowing the compiler ABI-freedom allows the implementation of significant optimizations - one such optimization is field reordering for alignment purposes: this allows the compiler to order fields according to an optimal arrangement for the target platform, taking into account alignment requirements etc.
Code Examples
type
SomeObject = object
f0: char
f1: int
f2: char
f3: int
proc f(v: SomeObject) =
# This prints 32 on a 64-bit platform today - the optimal size on x86_64 is
# 24 however achieved by ordering the fields in decreasing size order.
# Applying the optimizations made possible by ABI freedom also means that
# it `v` could be passed to `f` by value instead of by pointer, assuming the
# cutoff is `3*sizeof(int)`, thus making `f` more amenable to further optimizations
echo sizeof(SomeObject)
type
SomeExported {.exportc.} =
# this type would use ABI rules matching `C` as closely as possible
...
Most benefits of ABI freedom can be realized using the C backend - nlvm
can take further advantage by guiding the optimizer using llvm-specific metadata that is used to achieve good performance in other languages such as rust/swift/etc which already have features like this.
Backwards Compatibility
Backwards compatibility can be achieved by adding various degrees of strictness to the language in phases, starting with warnings (akin to deprecations) and finally introducing compile-time errors for things that previously were undefined and may become invalid under such optimizations.
Some backwards incompatibility is expected when encountering code that relies on the current implicit undefined behavior - for example, code might assume that just because previous versions used a pointer to pass >24-byte objects to a function, this will remain so. Such code is arguably already broken, but the edge can be taken off the upgrade by simply highlighting such code as invalid, either via warning or error - it's viable because it's constrained to exportc
/ importc
functions which see limited use.