diff --git a/compiler/compiler.go b/compiler/compiler.go index af9d269d..f78407f4 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1870,12 +1870,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string) // string or slice llvmLen = c.builder.CreateExtractValue(value, 1, "len") case *types.Map: - indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 2, false), // hashmap.count - } - ptr := c.builder.CreateGEP(value, indices, "lenptr") - llvmLen = c.builder.CreateLoad(ptr, "len") + llvmLen = c.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") default: return llvm.Value{}, errors.New("todo: len: unknown type") } @@ -3053,7 +3048,10 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value) ( default: return llvm.Value{}, errors.New("binop on interface: " + op.String()) } - case *types.Pointer: + case *types.Map, *types.Pointer: + // Maps are in general not comparable, but can be compared against nil + // (which is a nil pointer). This means they can be trivially compared + // by treating them as a pointer. switch op { case token.EQL: // == return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil @@ -3212,6 +3210,16 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) (llvm.Value, error llvmLen, // cap }, false) return slice, nil + case *types.Map: + if !expr.IsNil() { + // I believe this is not allowed by the Go spec. + panic("non-nil map constant") + } + llvmType, err := c.getLLVMType(typ) + if err != nil { + return llvm.Value{}, err + } + return c.getZeroValue(llvmType) default: return llvm.Value{}, errors.New("todo: unknown constant: " + expr.String()) } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 21ef1dd4..b770745c 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -71,6 +71,15 @@ func hashmapMake(keySize, valueSize uint8) *hashmap { } } +// Return the number of entries in this hashmap, called from the len builtin. +// A nil hashmap is defined as having length 0. +func hashmapLen(m *hashmap) int { + if m == nil { + return 0 + } + return int(m.count) +} + // Set a specified key to a given value. Grow the map if necessary. //go:nobounds func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { diff --git a/testdata/map.go b/testdata/map.go index 9c704cfc..66699390 100644 --- a/testdata/map.go +++ b/testdata/map.go @@ -26,6 +26,11 @@ func main() { readMap(testmap2, "seven") lookup(testmap2, "eight") lookup(testmap2, "nokey") + + // operations on nil maps + var nilmap map[string]int + println(m == nil, m != nil, len(m)) + println(nilmap == nil, nilmap != nil, len(nilmap)) } func readMap(m map[string]int, key string) { diff --git a/testdata/map.txt b/testdata/map.txt index d076a595..31ea290f 100644 --- a/testdata/map.txt +++ b/testdata/map.txt @@ -48,3 +48,5 @@ map read: seven = 7 twelve = 12 lookup with comma-ok: eight 8 true lookup with comma-ok: nokey 0 false +false true 2 +true false 0