66 Commits

Author SHA1 Message Date
Jake Bailey 7c3dc05117 all: modernize (#5498)
* modernize string cut usage

* modernize string cut prefix usage

* modernize slices helper usage

* modernize min and max usage

* modernize loop variable copies

* modernize integer range loops

* modernize map copy loops

* modernize go types iterator usage

* modernize empty interface usage

* modernize atomic type usage

* modernize string builders

* modernize string split iteration

* modernize remaining loop variable copies

* modernize usb cdc min usage

* modernize src integer range loops

* modernize example empty interface usage

* modernize src min and max usage

* modernize src integer range loops

* modernize src empty interface usage

* modernize src atomic type usage

* modernize reflect type lookups

* modernize review nits
2026-07-06 21:58:48 +02:00
Ayke van Laethem 134de98ba5 compiler, runtime: optimize zero-sized allocations
Instead of referring to an unused global, use a constant value. This is
safe even when using `-gc=none` (since no actual memory gets allocated)
which wasn't the case before. It should also reduce binary size by a few
bytes for most programs.
2026-06-25 21:09:43 +02:00
Ayke van Laethem 85d223c5e2 compiler: add //go:noheap pragma 2026-05-25 16:58:01 +02:00
Jake Bailey 18033ebc36 compiler, runtime, reflect: generate type-specific hash/equal (#5359)
* compiler, runtime, reflect: generate type-specific hash/equal for composite map keys

For map keys that are not trivially binary-comparable, the compiler now
generates type-specific hash and equal functions as LLVM IR instead of
going through the interface+reflection path. This covers comparable
types: strings, floats, complex numbers, interfaces, channels, and
composites containing any mix of these.

Previously, maps with composite keys containing strings or floats
converted the key to interface{}, hashed via reflection, and compared
through interface equality. Now the compiler walks struct fields and
array elements directly, dispatching to the right runtime helper for
each field type and storing keys at their actual type.

Struct keys are always handled field-by-field so padding bytes do not
affect equality or hashing. Blank fields are ignored, matching Go
equality. Generated hash/equal function names use canonical underlying
type structure so structurally identical key types can share generated
functions. Padding zeroing before map operations is no longer needed
because structs no longer use the binary key path.

Also fix reflect map iteration for interface-keyed maps: MapIter.Key
returns an interface Value for map[interface{}] keys instead of
unpacking to the concrete key kind.

* compiler: generate loops for array map key hash/equal

Previously, array key hash and equal functions were unrolled at compile
time, generating one block of IR per element. For large arrays like
[1000]int inside a struct with non-binary fields, this caused code
explosion.

Now, binary-element arrays dispatch directly to hash32/memequal for the
whole array. Non-binary-element arrays generate an LLVM IR loop. The
equal loop short-circuits on the first mismatch.

Small arrays are still unrolled instead of looping, keeping the simple
cases compact.

* reflect: fix at-runtime map issues from review, and more found locally

Maps created through reflect.MakeMap need hash/equal behavior that
matches compiler-created maps. Add hashmapMakeReflect for composite key
types, using runtime closures that reconstruct interface{} values from
raw key bytes and delegate to the interface hash and equality paths.

Interface-keyed maps are already stored as interface values, so use the
existing interface hash/equal helpers directly for those. This keeps
reflect insert, lookup, delete, and compiled lookup paths consistent.

Also fix addressable small values used as interface map keys or
interface map values. loadSmallValue puts small indirect values back in
the pointer-sized interface data field the same way valueInterfaceUnsafe
does.

* compiler, interp, reflect: fix pointer map literals; remove interface fallback

Package-level map literals with pointer keys (both *T and
unsafe.Pointer) crash the compiler: the interp pass panics when trying
to hash pointer data as raw bytes, because pointer values in the interp
memory model are symbolic identities that do not fit in a byte.

Fix this by setting a recoverable error flag instead of panicking. The
interp detects the error after each instruction and defers the map
insert to runtime init code, where real addresses are available for
hashing. This matches how the interp already handles other operations
it cannot evaluate at compile time.

With this fix, unsafe.Pointer can also be classified as a binary map
key, which was the last type requiring the interface-based fallback.
Since all comparable types now use either the binary or the
compiler-generated hash/equal path, remove the interface fallback from
the compiler and reflect packages.

* compiler, transform: always pass hash/equal function pointers to hashmapMakeGeneric

The compiler now always resolves the hash and equal functions at compile
time and passes them directly to hashmapMakeGeneric, instead of passing
an algorithm enum to hashmapMake and resolving at runtime. For string
keys, the runtime hashmapStringPtrHash/hashmapStringEqual functions are
referenced directly. For binary keys, hash32/memequal are referenced.

The old hashmapMake with alg enum is retained for reflect, which still
needs runtime resolution when creating maps dynamically.

The OptimizeMaps transform pass is updated to handle both hashmapMake
and hashmapMakeGeneric, and to recognize hashmapGenericSet in addition
to hashmapBinarySet and hashmapStringSet. The now-unused
hashmapCanGenerateHashEqual helper is removed.

* runtime: store large map keys and values indirectly

When a map key or value exceeds 128 bytes, the bucket now stores a
pointer to separately allocated memory instead of the data inline. This
matches Go's MapMaxKeyBytes/MapMaxElemBytes threshold and prevents
bucket sizes from exploding for large key/value types.

For example, map[[256]byte]int previously used 2128 bytes per bucket
(16 header + 256*8 keys + 8*8 values); now it uses 144 bytes per bucket
(16 header + 8*8 pointers + 8*8 values).

The indirection is fully encapsulated in the runtime via helper
functions. Store the computed key and value slot sizes on the hashmap so
all runtime and reflect paths use the same bucket layout, including
non-indirect keys and values.

Add big-key golden coverage and benchmarks. Make the benchmark vary
enough key bytes to exercise hashing.
2026-05-18 13:31:27 +02:00
Jake Bailey 89d9e33bca interp: bail out of loops that iterate too many times (#5395)
* interp: bail out of loops that iterate too many times

The existing loop guard (errLoopUnrolled) only fires when a loop body
emits runtime instructions. Loops that are fully evaluable at compile
time, such as inserting thousands of entries into a map, were not
caught and could hang the compiler.

Add a per-basic-block iteration counter that triggers a recoverable
error (errLoopTooLong) when any block is entered more than 1000 times
in a single function call. This defers the init function to runtime,
which is the same behavior as other interp bailouts.

Profiling showed that 83% of CPU time was spent in GC, caused by
allocation pressure from the interp memory cloning on each map
mutation. The iteration limit avoids this entirely by bailing out
before the quadratic cost becomes significant.

Performance on the reproducer from #2090 (map init with strconv.Itoa):

    entries  before       after
    5,000    7.4s         2.1s
    10,000   17.5s        2.1s
    20,000   48.0s        2.8s
    65,536   >180s (OOM)  3.2s

* Add -interp-loop-limit
2026-05-17 20:42:27 +02:00
Jake Bailey 5ba8766cbc compiler: implement method-set based AssignableTo and Implements (#5304)
* reflect: implement method-set based AssignableTo and Implements

Based on the design from #4376 by aykevl.
Fixes #4277, fixes #3580.

Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>

* builder: update expected binary sizes for reflect changes

* Make interface checks similar to invoke, allowing typeImplementsMethodSet and method info to be dropped when reflect is not present

* Add more tests that BigGo reflect tests

* Even more pruning

* Add go/token and net/url to passing tests

* Prune even further, I am less happy with this, though

* Update size test now that we are smaller

* Skip some tests

* elide method lists

* format, oops

* fix tests

* Add a panic, pull out constant to keep in sync

* Add debug info

* Remove code that was leftover from a previous refactor

---------

Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
2026-04-17 21:57:03 +02:00
Nia Waldvogel a40586d092 compiler: implement copy directly
The compiler now implements the copy builtin directly instead of calling sliceCopy.
The length is calculated with the llvm.umax.* intrinsics, and the move is performed by llvm.memmove.*.
Both of these operations are easily understood by LLVM's optimization passes.
The type's alignment is also provided to llvm.memmove.*, which is useful when rewriting the move.

Interp no longer needs to reimplement sliceCopy.
Some edge case handling was implemented by sliceCopy but not llvm.memmove.*/llvm.memcpy.*.
I copied this over, so copies of external slices should work now.
Volatile moves/copies are now run at runtime by interp.

There is a 4-byte size increase due to some confusing length logic in sendUSBPacket.
I will look at sendUSBPacket in a future PR.
2026-01-05 15:10:31 -05:00
Ayke van Laethem c820d83ae2 interp: better errors when debugging interp
When debugging is enabled for interp, print better errors in a specific
case.

Before:

    !! revert because of error: interp: unsupported instruction (to be emitted at runtime)

After:

    !! revert because of error: /usr/local/go1.24.0/src/regexp/syntax/parse.go:927:27: interp: unsupported instruction (to be emitted at runtime)

So this adds error location information, which can be quite useful.
2025-10-02 05:42:08 +02:00
Ayke van Laethem cb5f7ad3fd interp: fix copy() from/to external buffers
This includes copying from a //go:embed slice for example. The slice
with data is not available at interp time.

See: https://github.com/tinygo-org/tinygo/issues/4895
2025-05-22 14:46:41 +02:00
Ayke van Laethem 811b13a9d3 interp: correctly mark functions as modifying memory
Previously, if a function couldn't be interpreted in the interp pass, it
would just be run at runtime and the parameters would be marked as
potentially being modified. However, this is incorrect: the function
itself can also modify memory. So the function itself also needs to be
marked (recursively).

This fixes a number of regressions while upgrading to Go 1.24.

This results in a significant size regression that is mostly worked
around in the next commit.
2025-02-21 11:36:17 +01:00
Ayke van Laethem ecc6d16f22 interp: align created globals
Use the alignment from the align attribute of the runtime.alloc call.
This is going to be a more accurate alignment, and is typically smaller
than the default.
2024-11-21 10:19:19 +01:00
Ayke van Laethem 73f519b589 interp: support big-endian targets
The interp package was assuming that all targets were little-endian. But
that's not true: we now have a big-endian target (GOARCH=mips).

This fixes the interp package to use the appropriate byte order for a
given target.
2024-09-05 10:06:30 +02:00
Ayke van Laethem a5f78a3900 main: add interp tests
This one needed more advanced checking of the error messages, because we
don't want to hardcode things like `!dbg !10` in the output.
2024-07-20 14:30:34 +02:00
Marco Manino 71878c2c0c Adding a comment 2024-05-13 16:49:18 +02:00
Marco Manino cef2a82c97 Checking for methodset existance 2024-05-13 16:49:18 +02:00
Ayke van Laethem 1f6d34d995 interp: make getelementptr offsets signed
getelementptr offsets are signed, not unsigned. Yet they were used as
unsigned integers in interp.
Somehow this worked most of the time, until finally there was some code
that did a getelementptr with a negative index.
2024-02-27 15:16:38 +01:00
Ayke van Laethem 9951eb9990 interp: return a proper error message when indexing out of range
This helps debug issues inside interp.
2024-02-27 15:16:38 +01:00
Ayke van Laethem f529b6071d interp: do not register runtime timers during interp
The runtime hasn't been initialized yet so this leads to problems.

This fixes https://github.com/tinygo-org/tinygo/issues/4123.
2024-02-19 22:17:19 +01:00
Ayke van Laethem 1da1abe314 all: remove LLVM 14 support
This is a big change: apart from removing LLVM 14 it also removes typed
pointer support (which was only fully supported in LLVM up to version
14). This removes about 200 lines of code, but more importantly removes
a ton of special cases for LLVM 14.
2023-10-01 18:32:15 +02:00
Ayke van Laethem 081d2e58c6 interp: print LLVM instruction in traceback
The old traceback would look like this:

    # internal/godebug
    /usr/local/go/src/internal/godebug/godebug.go:101:11: interp: test
    call <2> 0 <3> 0

    traceback:
    /usr/local/go/src/internal/godebug/godebug.go:101:11:
    call <2> 0 <3> 0
    /usr/local/go/src/internal/godebug:
    call <1> 0

With this patch, it looks like this:

    # io/fs
    /usr/local/go/src/io/fs/fs.go:144:45: interp: test
      %0 = load %runtime._interface, ptr @"internal/oserror.ErrInvalid", align 8, !dbg !316

    traceback:
    /usr/local/go/src/io/fs/fs.go:144:45:
      %0 = load %runtime._interface, ptr @"internal/oserror.ErrInvalid", align 8, !dbg !316
    /usr/local/go/src/io/fs/fs.go:137:28:
      %0 = call %runtime._interface @"io/fs.errInvalid"(ptr undef), !dbg !317

For developers (like me) who are familiar with LLVM, this is probably
easier to read. For users, I'm not sure: the instructions have quite a
lot of distracting fluff in them. But at least it contains the function
names that are called (which are not currently present in the old
traceback).
...that said, having the LLVM instructions in a bug report is probably
going to be easier for people who are familar with LLVM.
2023-09-22 15:28:52 +02:00
Kenneth Bell 58fafaeb5c build: #3893 do not return LLVM structs from Build 2023-09-21 01:18:05 +02:00
Ayke van Laethem f11731ff35 interp: don't copy unknown values in runtime.sliceCopy
This was bug https://github.com/tinygo-org/tinygo/issues/3890.
See the diff for details. Essentially, it means that `copy` in the
interpreter was copying data that wasn't known yet, or copying data into
a slice that could be read externally after the interp pass.
2023-09-10 11:30:09 +02:00
Ayke van Laethem 481f60c5ea interp: add support for reading a pointer tag
This is necessary to get https://github.com/tinygo-org/tinygo/pull/3691
working.
2023-05-20 23:57:20 +02:00
Ayke van Laethem cf39db36fe interp: fix subtle bug in pointer xor
If a pointer value was xor'ed with a value other than 0, it would not
have been run at runtime but instead would fall through to the generic
integer operations. This would likely result in a "cannot convert
pointer to integer" panic.

This commit fixes this subtle case.
2023-05-20 23:57:20 +02:00
Ayke van Laethem 54c07b7de8 interp: move some often-repeated code into a function 2023-05-20 23:57:20 +02:00
Ayke van Laethem 4e8453167f all: refactor reflect package
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.
2023-02-17 22:54:34 +01:00
Ayke van Laethem 2b7f562202 ci: add support for LLVM 15
This commit switches to LLVM 15 everywhere by default, while still
keeping LLVM 14 support.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 08a51535d4 interp: add support for constant icmp instructions
These instructions sometimes pop up in LLVM 15, but they never occured
in LLVM 14. Implementing them is relatively straightforward: simply
generalize the code that already exists for llvm.ICmp interpreting.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 62df1d7490 all: remove pointer ElementType calls
This is needed for opaque pointers, which are enabled by default in
LLVM 15.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 229746b71e interp: change object.llvmType to the initializer type
Previously it was a pointer type, which won't work with opaque pointers.
Instead, use the global initializer type instead.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 09ec846c9f all: replace llvm.Const* calls with builder.Create* calls
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.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 7b6a9fab42 all: add type parameter to CreateLoad
This is needed for LLVM 15.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 6bc6de8f82 all: add type parameter to CreateCall
This uses LLVMBuildCall2 in the background, which is the replacement for
the deprecated LLVMBuildCall function.
2022-10-19 22:23:19 +02:00
Ayke van Laethem 9e4e182615 interp: fix reading from external global
This fixes https://github.com/tinygo-org/tinygo/issues/3020.
2022-09-15 19:07:04 +02:00
Damian Gryski 0b77e92c50 make interp timeout configurable from command line 2022-08-20 07:40:39 +02:00
Nia Waldvogel f2e576decf interp: do not unroll loops
This change triggers a revert whenever a basic block runs instructions at runtime twice.
As a result, a loop body with runtime-only instructions will no longer be unrolled.
This should help some extreme cases where loops can be expanded into hundreds or thousands of instructions.
2022-06-02 18:13:56 +02:00
Ayke van Laethem 80d94115dc interp: improve error handling of markExternal* functions 2022-05-25 12:51:31 +02:00
Ayke van Laethem b8e433821a interp: fix some buggy localValue handling
Bug:

 1. fn.locals[v.value] returns 0 (the default value) if v.value is not
    part of the fn.locals map.
 2. locals[fn.locals[v.value]] then returns the first local value, which
    is usually non-nil
 3. This incorrect value is then used as the operand value.

The manifestation of this convoluted bug was
https://github.com/tinygo-org/tinygo/issues/2842. It didn't occur more
often probably because it only seems to happen in practice with inline
assembly.

Fixes https://github.com/tinygo-org/tinygo/issues/2842
2022-05-22 15:08:17 +02:00
Ayke van Laethem dd1a836903 interp: do not try to interpret past task.Pause()
For context, see:
https://github.com/tinygo-org/tinygo/pull/2863#issuecomment-1133875237

Basically, testdata/goroutine.go was doing `time.Sleep()` inside an init
function which broke because doing that from a non-goroutine is not
allowed. However, we never hit this bug because of
https://github.com/tinygo-org/tinygo/issues/2842 (I didn't investigate
exactly why).
2022-05-22 15:08:17 +02:00
Damian Gryski 06b19cde2d interp: prevent an off-by-one during interp debug 2022-03-17 20:06:59 +01:00
Ayke van Laethem cd867b72b9 interp: don't log from the interp package
We shouldn't be logging anything when not explicitly requested via a
flag or some other way.
2022-01-24 15:01:35 +01:00
Nia Waldvogel 2f57e4ff6d interp: handle type assertions on nil interfaces
Previously, a type assertion on a nil interface would result in an out-of-bounds operand access, as we assumed it was a ptrtoint.
This would usually result in an undefined value which happens not to have the same name as the asserted type (and therefore the assertion fails as expected).
However, with an LLVM build with asserts, LLVM throws an assertion error:
```
interp.test: /home/niaow/go/src/github.com/tinygo-org/tinygo/llvm-project/llvm/include/llvm/IR/User.h:170: llvm::Value* llvm::User::getOperand(unsigned int) const: Assertion `i < NumUserOperands && "getOperand() out of range!"' failed.
```

This change handles a type code of 0 specially.
2022-01-21 14:49:36 +01:00
Dan Kegel b85cb55aab interpreter.go: double timeout to let a real world project build. Waiting is a bit less frustrating with breadcrumbs. 2022-01-20 10:07:48 +01:00
Nia Waldvogel c6ae1c58fc compiler: remove parentHandle from calling convention
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.
2022-01-19 14:42:02 -05:00
Nia Waldvogel aa053b5fb0 interp: always run atomic and volatile loads/stores at runtime 2022-01-15 14:40:14 +01:00
Dan Kegel 69bf6e3c89 interp: show breadcrumbs to help developer find slow init functions; raise timeout. 2022-01-11 22:09:15 +01:00
Nia Waldvogel 9db8826b3b interp: run goroutine starts and checks at runtime
This change prevents interp from trying to execute goroutine starts or checks.
This fixes a bug where a goroutine started by an init function would run before the init function.
2021-12-24 09:10:21 +01:00
Ayke van Laethem 6c02b4956c interp: fix reverting of extractvalue/insertvalue with multiple indices 2021-11-11 10:36:22 +01:00
Ayke van Laethem 1869efe954 interp: use object layout information for LLVM types
This commit will use the memory layout information for heap allocations
added in the previous commit to determine LLVM types, instead of
guessing their types based on the content. This fixes a bug in which
recursive data structures (such as doubly linked lists) would result in
a compiler stack overflow due to infinite recursion.

Not all heap allocations have a memory layout yet, but this can be
incrementally fixed in the future. So far, this commit should fix
(almost?) all cases of this stack overflow issue.
2021-11-02 22:16:15 +01:00
Ayke van Laethem a4afc3b4b0 compiler: simplify interface lowering
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.
2021-10-31 14:17:25 +01:00