Commit Graph

59 Commits

Author SHA1 Message Date
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
Randy Reddig 99a618385b runtime: use package reflectlite 2025-03-17 18:23:28 +01:00
Ayke van Laethem 6d4dfcf72f compiler, runtime: move constants into shared package
Use a single package for certain constants that must be the same between
the compiler and the runtime.

While just using the same values in both places works, this is much more
obvious and harder to mess up. It also avoids the need for comments
pointing to the other location the constant is defined. And having it in
code makes it possible for IDEs to analyze the source.

In the future, more such constants and maybe algorithms can be added.
2024-11-15 10:11:57 +01:00
Damian Gryski b3e1974c30 runtime: add maps.clone
Fixes #4382
2024-10-02 12:40:07 +02:00
Damian Gryski b3c040e9f7 runtime: make map iteration less defined
Fixes #3726
2024-10-02 12:40:07 +02:00
Ayke van Laethem eab1a5d7ec reflect, runtime: remove *UnsafePointer wrappers for functions
This was needed in the past because LLVM used typed pointers and there
was a mismatch between pointer types. But we've dropped support for
typed pointers a while ago so now we can remove these wrappers.

This is just a cleanup, it shouldn't have any practical effect.
2024-08-12 15:26:18 +02:00
Ayke van Laethem f1e25a18d2 compiler: implement clear builtin for maps 2023-08-04 11:59:11 +02:00
Damian Gryski 4e4e4eee04 runtime: use unsafe.Add() in hashmap code 2023-03-30 00:08:07 +02:00
Damian Gryski bf20c652e2 runtime: take max hash size into account when preallocating with size hint 2023-03-30 00:08:07 +02:00
Damian Gryski a3afd4e8ac runtime: factor duplicate hashmap snippts to functions 2023-03-30 00:08:07 +02:00
Damian Gryski cb9c6f0074 runtime: zero map key/value on deletion to so GC doesn't see them 2023-03-30 00:08:07 +02:00
Damian Gryski 5fee3428bc runtime: preallocate maps to size hint actually works 2023-03-30 00:08:07 +02:00
Damian Gryski 69e5c5088d reflect: add support for remaining map types 2023-03-10 16:28:22 -08:00
Ayke van Laethem 4ec1e58aa6 all: use unsafe.Add instead of unsafe.Pointer(uintptr(...) + ...)
We have an optimization for this specific pattern, but it's really just
a hack. With the addition of unsafe.Add in Go 1.17 we can directly
specify the intent instead and eventually remove this special case.

The code is also easier to read.
2023-03-03 16:55:13 +01:00
Damian Gryski c0a50e9b47 runtime: add unsafe.pointer reflect wrappers for hashmap calls 2023-02-28 13:10:40 -08:00
Damian Gryski 7b44fcd865 runtime: properly turn pointer into empty interface when hashing 2023-02-25 06:42:30 -08: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
Damian Gryski 0504e4a201 compiler,runtime: make keySize and valueSize uintptr 2023-01-18 09:48:00 +01:00
Damian Gryski 530ca0838d compiler,runtime: allow map values >256 bytes 2023-01-18 09:48:00 +01:00
Ayke van Laethem c7a23183e8 all: format code according to Go 1.19 rules
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.
2022-08-04 12:18:32 +02:00
Damian Gryski 5c488e3145 src/runtime: handle nil map write panics 2022-06-01 13:28:22 +02:00
Damian Gryski e45ff9c0e8 src/runtime: add per-map hash seeds 2022-06-01 13:28:22 +02:00
Damian Gryski b251ce7b33 src/runtime: return a nil pointer for compiler bugs in hashmap code
We'll still panic if there's a compiler bug, but at least we'll have smaller
code in all the cases where we don't.
2022-04-28 09:14:45 +02:00
Damian Gryski 1abe1203a3 src/runtime: make hashmap calculations more uintptr-size independent 2022-04-28 09:14:45 +02:00
Damian Gryski 8b360ec911 src/runtime: remove extra if check in hashmap set logic 2022-04-28 09:14:45 +02:00
Damian Gryski 93654544b3 src/runtime: prevent overflow when calculating hashmap growth limits 2022-04-28 09:14:45 +02:00
Damian Gryski 9a8328fcb7 src/runtime/hashmap: comments for iterator structure 2022-04-28 09:14:45 +02:00
Damian Gryski 6812a4dc04 src/runtime: first darft of map growth code
Fixes #1553
2022-04-28 09:14:45 +02:00
Ron Evans 1dcdd5f2d2 Revert "src/runtime: first darft of map growth code"
This reverts commit 7e05efa274.
2022-04-05 03:34:01 +02:00
Ron Evans dcd8ee63a8 Revert "src/runtime/hashmap: comments for iterator structure"
This reverts commit 8872229a0b.
2022-04-05 03:34:01 +02:00
Damian Gryski 8872229a0b src/runtime/hashmap: comments for iterator structure 2022-04-04 13:22:19 +02:00
Damian Gryski 7e05efa274 src/runtime: first darft of map growth code
Fixes #1553
2022-04-04 13:22:19 +02:00
Ayke van Laethem b9c0aa77bf runtime: implement memhash
This function is used by the hash/maphash package.

Unfortunately, I couldn't get the hash/maphash package to pass tests
because it's too slow. I think it hits the quadratic complexity of the
current map implementation.
2022-03-06 10:13:04 +01:00
Damian Gryski d6c892fe7b src/runtime: fix nil map dereference
Operations on nil maps are accepted and shouldn't
panic. The base hashmapGet/hashmapDelete handled
nil-maps correctly, but the hashmapBinary versions
could segfault accessing the nil map while trying
to hash the key.

Fixes #2341
2021-12-09 18:23:49 +01:00
Damian Gryski 1903cf23c9 src/runtime: improve float/complex hashing
This allows positive and negative zero to hash to the same value,
as required by Go.

This is not perfect, but the best I could do without
revamping all the hash funtions to take a seed.

Fixes #2356
2021-12-08 22:38:22 +01:00
Damian Gryski a360c82b40 src/runtime: strengthen hash function for structs and arrays
Using `|` to combine hash values will slowly turn every bit on.
Hashes combined with `^` with keep more entropy.
2021-12-08 10:33:35 +01:00
Ayke van Laethem f24a93c51d compiler, runtime: add layout parameter to runtime.alloc
This layout parameter is currently always nil and ignored, but will
eventually contain a pointer to a memory layout.

This commit also adds module verification to the transform tests, as I
found out that it didn't (and therefore didn't initially catch all
bugs).
2021-11-02 22:16:15 +01:00
Ayke van Laethem f800f7507c reflect: check for access in the Interface method call
This fixes a type system loophole. The following program would
incorrectly run in TinyGo, while it would trigger a panic in Go:

    package main

    import "reflect"

    func main() {
        v := reflect.ValueOf(struct {
            x int
        }{})
        x := v.Field(0).Interface()
        println("x:", x.(int))
    }

Playground link: https://play.golang.org/p/nvvA18XFqFC

The panic in Go is the following:

    panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

I've shortened it in TinyGo to save a little bit of space.
2021-03-24 12:19:16 +01:00
Ayke van Laethem c849bccb83 reflect: let reflect.Type be of interface type
This matches the main Go implementation and (among others) fixes a
compatibility issue with the encoding/json package. The encoding/json
package compares reflect.Type variables against nil, which does not work
as long as reflect.Type is of integer type.

This also adds a reflect.RawType() function (like reflect.Type()) that
makes it easier to avoid working with interfaces in the runtime package.
It is internal only, but exported to let the runtime package use it.

This change introduces a small code size increase when working with the
reflect package, but I've tried to keep it to a minimum. Most programs
that don't make extensive use of the reflect package (and don't use
package like fmt) should not be impacted by this.
2021-03-23 14:32:33 +01:00
Ayke van Laethem e99b8a24fe runtime: allow ranging over a nil map
This appears to be allowed by the specification, at least it is allowed
by the main Go implementation: https://play.golang.org/p/S8jxAMytKDB

Allow it in TinyGo too, for consistency.

Found because it is triggered with `tinygo test flags`. This doesn't
make the flags package pass all tests, but is a step closer.
2020-10-29 21:53:41 +01:00
cornelk 2c71f08922 reflect: add Cap and Len support for map and chan 2020-05-12 01:17:27 +02:00
Ayke van Laethem 4dfc289ae5 compiler,runtime: support operations on nil map
The index expression and delete keyword are valid on nil maps, so the
runtime must be modified to support this.
2020-02-26 20:42:01 +01:00
Ayke van Laethem 0d34f933eb compiler,runtime: implement maps for arbitrary keys
This implementation simply casts types without special support to an
interface, to make the implementation simpler and possibly reducing the
code size too. It will likely be slower than the canonical Go
implementation though (which builds special compare and hash functions
at compile time).
2020-01-27 08:27:14 +01:00
Ayke van Laethem 763b9d7d10 runtime: implement growing hashmaps
Add support for growing hashmaps beyond their initial size.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 55fc7b904a compiler,runtime: use the size hint when creating a new map
It defaults to hint/8 number of buckets. This number may be tuned in the
future.
2019-05-14 09:59:00 +02:00
kyegupov fb8dcde2f9 runtime: update link to original Go hashmap source code 2018-12-31 13:33:18 +01:00
Ayke van Laethem ee7c276493 compiler: update integer type sizes
* Use 64-bit integers on 64-bit platforms, just like gc and gccgo:
  https://golang.org/doc/go1.1#int
* Do not use a separate length type. Instead, use uintptr everywhere a
  length is expected.
2018-11-14 14:01:04 +01:00
Ayke van Laethem 436901dc49 compiler: implement operations on nil hashmaps
* comparing a map against nil
  * getting the length of a nil map
2018-10-27 00:57:37 +02:00
Ayke van Laethem 7c2a6169b0 compiler: support comma-ok in map lookup 2018-10-20 17:54:16 +02:00
Ayke van Laethem c0c1ccb381 compiler, runtime: implement delete builtin 2018-10-20 16:18:55 +02:00