It is possible to create function-local named types:
func foo() any {
type named int
return named(0)
}
This patch makes sure they don't alias with named types declared at the
package scope.
Bug originally found by Damian Gryski while working on reflect support.
This is a big commit that changes the way runtime type information is stored in
the binary. Instead of compressing it and storing it in a number of sidetables,
it is stored similar to how the Go compiler toolchain stores it (but still more
compactly).
This has a number of advantages:
* It is much easier to add new features to reflect support. They can simply
be added to these structs without requiring massive changes (especially in
the reflect lowering pass).
* It removes the reflect lowering pass, which was a large amount of hard to
understand and debug code.
* The reflect lowering pass also required merging all LLVM IR into one
module, which is terrible for performance especially when compiling large
amounts of code. See issue 2870 for details.
* It is (probably!) easier to reason about for the compiler.
The downside is that it increases code size a bit, especially when reflect is
involved. I hope to fix some of that in later patches.
A number of llvm.Const* functions (in particular extractvalue and
insertvalue) were removed in LLVM 15, so we have to use a builder
instead. This builder will create the same constant values, it simply
uses a different API.
Go 1.19 started reformatting code in a way that makes it more obvious
how it will be rendered on pkg.go.dev. It gets it almost right, but not
entirely. Therefore, I had to modify some of the comments so that they
are formatted correctly.
For example, this commit moves the 'throw' branch of an assertion (nil
check, slice index check, etc) to the end of the function while
inserting the "continue" branch right after the insert location. This
makes the resulting IR easier to follow.
For some reason, this also reduces code size a bit on average. The
TinyGo smoke tests saw a reduction of 0.22%, mainly from WebAssembly.
The drivers repo saw little average change in code size (-0.01%).
This commit also adds a few compiler tests for the defer keyword.
There used to be a difference between `byte` and `uint8` in interface
methods. These are aliases, so they should be treated the same.
This patch introduces a custom serialization format for types,
circumventing the `Type.String()` method that is slightly wrong for our
purposes.
This also fixes an issue with the `any` keyword in Go 1.18, which
suffers from the same problem (but this time actually leads to a crash).
This removes the parentHandle argument from the internal calling convention.
It was formerly used to implment coroutines.
Now that coroutines have been removed, it is no longer necessary.
Some map keys are hard to compare, such as floats. They are stored as if
the map keys are of interface type instead of the key type itself. This
makes working with them in the runtime package easier: they are compared
as regular interfaces.
Iterating over maps didn't care about this special case though. It just
returns the key, value pair as it is stored in the map. This is buggy,
and this commit fixes this bug.
This commit has a few related changes:
* It sets the optsize attribute immediately in the compiler instead of
adding it to each function afterwards in a loop. This seems to me
like the more appropriate way to do it.
* It centralizes setting the optsize attribute in the transform
package, to make later changes easier.
* It sets the optsize in a few more places: to runtime.initAll and to
WebAssembly i64 wrappers.
This commit does not affect the binary size of any of the smoke tests,
so should be risk-free.
This commit simplifies the IR a little bit: instead of calling
pseudo-functions runtime.interfaceImplements and
runtime.interfaceMethod, real declared functions are being called that
are then defined in the interface lowering pass. This should simplify
the interaction between various transformation passes. It also reduces
the number of lines of code, which is generally a good thing.
This attribute is also set by Clang when it compiles C source files
(unless -fexceptions is set). The advantage is that no unwind tables are
emitted on Linux (and perhaps other systems). It also avoids
__aeabi_unwind_cpp_pr0 on ARM when using the musl libc.
This commit includes two changes:
* It makes unexported interface methods package-private, so that it's
not possible to type-assert on an unexported method in a different
package.
* It makes the globals used to identify interface methods defined
globals, so that they can (eventually) be left in the program for an
eventual non-LTO build mode.
Sometimes, LLVM may rename named structs when merging modules.
Therefore, we can't rely on typecodeID structs to retain their struct
names.
This commit changes the interface lowering pass to not rely on these
names. The interp package does however still rely on this name, but I
hope to fix that in the future.
This commit adds a new transform that converts reflect Implements()
calls to runtime.interfaceImplements. At the moment, the Implements()
method is not yet implemented (how ironic) but if the value passed to
Implements is known at compile time the method call can be optimized to
runtime.interfaceImplements to make it a regular interface assert.
This commit is the last change necessary to add basic support for the
encoding/json package. The json package is certainly not yet fully
supported, but some trivial objects can be converted to JSON.
This patch fixes a use of the global context. I've seen a few instances
of crashes in the llvm.ConstInt function when called from
makeStructTypeFields, which I believe are caused by this bug.
Previously there was code to avoid impossible type asserts but it wasn't
great and in fact was too aggressive when combined with reflection.
This commit improves this by checking all types that exist in the
program that may appear in an interface (even struct fields and the
like) but without creating runtime.typecodeID objects with the type
assert. This has two advantages:
* As mentioned, it optimizes impossible type asserts away.
* It allows methods on types that were only asserted on (in
runtime.typeAssert) but never used in an interface to be optimized
away using GlobalDCE. This may have a cascading effect so that other
parts of the code can be further optimized.
This sometimes massively improves code size and mostly negates the code
size regression of the previous commit.
This distinction was useful before when reflect wasn't properly
supported. Back then it made sense to only include method sets that were
actually used in an interface. But now that it is possible to get to
other values (for example, by extracting fields from structs) and it is
possible to turn them back into interfaces, it is necessary to preserve
all method sets that can possibly be used in the program in a type
assert, interface assert or interface method call.
In the future, this logic will need to be revisited again when
reflect.New or reflect.Zero gets implemented.
Code size increases a bit in some cases, but usually in a very limited
way (except for one outlier in the drivers smoke tests). The next commit
will improve the situation significantly.
This commit switches from the previous behavior of compiling the whole
program at once, to compiling every package in parallel and linking the
LLVM bitcode files together for further whole-program optimization.
This is a small performance win, but it has several advantages in the
future:
- There are many more things that can be done per package in parallel,
avoiding the bottleneck at the end of the compiler phase. This
should speed up the compiler futher.
- This change is a necessary step towards a non-LTO build mode for
fast incremental builds that only rebuild the changed package, when
compiler speed is more important than binary size.
- This change refactors the compiler in such a way that it will be
easier to inspect the IR for one package only. Inspecting this IR
will be very helpful for compiler developers.
Moving settings to a separate config struct has two benefits:
- It decouples the compiler a bit from other packages, most
importantly the compileopts package. Decoupling is generally a good
thing.
- Perhaps more importantly, it precisely specifies which settings are
used while compiling and affect the resulting LLVM module. This will
be necessary for caching the LLVM module.
While it would have been possible to cache without this refactor, it
would have been very easy to miss a setting and thus let the
compiler work with invalid/stale data.
This package was long making the design of the compiler more complicated
than it needs to be. Previously this package implemented several
optimization passes, but those passes have since moved to work directly
with LLVM IR instead of Go SSA. The only remaining pass is the SimpleDCE
pass.
This commit removes the *ir.Function type that permeated the whole
compiler and instead switches to use *ssa.Function directly. The
SimpleDCE pass is kept but is far less tightly coupled to the rest of
the compiler so that it can easily be removed once the switch to
building and caching packages individually happens.
Because the parentHandle parameter wasn't always set to the right value,
the coroutine lowering pass would sometimes panic with "trying to make
exported function async" even though there was no exported function
involved. Therefore, it should unconditionally be set to avoid this.
The parent function doesn't always have the parentHandle function
parameter set because it can only be set after defining a function, not
when it is only declared.
This makes viewing the IR easier because parameters have readable names.
This also makes it easier to write compiler tests (still a work in
progress), that work in LLVM 9 and LLVM 10, as LLVM 10 started printing
value names for unnamed parameters.
Previously, the typecode was passed via a direct reference, which results in invalid IR when the defer is not reached in all return paths.
It also results in incorrect behavior if the defer is in a loop, causing all defers to use the typecode of the last iteration.
This is used for example by the errors package, which contains:
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
The interface here is not a named type.
This gives a hint to the compiler that such parameters are either NULL
or point to a valid object that can be dereferenced. This is not
directly very useful, but is very useful when combined with
https://reviews.llvm.org/D60047 to remove the runtime.isnil hack without
regressing escape analysis.
Now that most of the utility compiler methods are ported over to the
builder or compilerContext, it is possible to avoid having to do the
wrapper creation in two steps. A new builder is created just to create
the wrapper.
This is a small reduction in line count (and a significant reduction in
complexity!), even though more documentation was added.