diff --git a/compiler.go b/compiler.go index 8d931ced..1a2094e3 100644 --- a/compiler.go +++ b/compiler.go @@ -2211,6 +2211,42 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { slice = c.builder.CreateInsertValue(slice, sliceLen, 1, "") slice = c.builder.CreateInsertValue(slice, sliceCap, 2, "") return slice, nil + case *ssa.Next: + if expr.IsString { + return llvm.Value{}, errors.New("todo: next: string") + } else { // map + fn := c.mod.NamedFunction("runtime.hashmapNext") + it, err := c.parseExpr(frame, expr.Iter) + if err != nil { + return llvm.Value{}, err + } + rangeMap := expr.Iter.(*ssa.Range).X + m, err := c.parseExpr(frame, rangeMap) + if err != nil { + return llvm.Value{}, err + } + + llvmKeyType, err := c.getLLVMType(rangeMap.Type().(*types.Map).Key()) + if err != nil { + return llvm.Value{}, err + } + llvmValueType, err := c.getLLVMType(rangeMap.Type().(*types.Map).Elem()) + if err != nil { + return llvm.Value{}, err + } + + mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key") + mapKeyPtr := c.builder.CreateBitCast(mapKeyAlloca, c.i8ptrType, "range.keyptr") + mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "range.value") + mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "range.valueptr") + ok := c.builder.CreateCall(fn, []llvm.Value{m, it, mapKeyPtr, mapValuePtr}, "range.next") + + tuple := llvm.Undef(llvm.StructType([]llvm.Type{llvm.Int1Type(), llvmKeyType, llvmValueType}, false)) + tuple = c.builder.CreateInsertValue(tuple, ok, 0, "") + tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "") + tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "") + return tuple, nil + } case *ssa.Phi: t, err := c.getLLVMType(expr.Type()) if err != nil { @@ -2219,6 +2255,23 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { phi := c.builder.CreatePHI(t, "") frame.phis = append(frame.phis, Phi{expr, phi}) return phi, nil + case *ssa.Range: + switch typ := expr.X.Type().Underlying().(type) { + case *types.Basic: + // string + return llvm.Value{}, errors.New("todo: range: string") + case *types.Map: + iteratorType := c.mod.GetTypeByName("runtime.hashmapIterator") + it := c.builder.CreateAlloca(iteratorType, "range.it") + zero, err := getZeroValue(iteratorType) + if err != nil { + return llvm.Value{}, nil + } + c.builder.CreateStore(zero, it) + return it, nil + default: + panic("unknown type in range: " + typ.String()) + } case *ssa.Slice: if expr.Max != nil { return llvm.Value{}, errors.New("todo: full slice expressions (with max): " + expr.Type().String()) diff --git a/src/examples/test/test.go b/src/examples/test/test.go index 1681dfb7..d1130689 100644 --- a/src/examples/test/test.go +++ b/src/examples/test/test.go @@ -111,6 +111,9 @@ func testBound(f func() string) { func readMap(m map[string]int, key string) { println("map length:", len(m)) println("map read:", key, "=", m[key]) + for k, v := range m { + println(" ", k, "=", v) + } } func hello(n int) { diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 30cc781e..b88b86c9 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -30,6 +30,12 @@ type hashmapBucket struct { // allocated but as they're of variable size they can't be shown here. } +type hashmapIterator struct { + bucketNumber uintptr + bucket *hashmapBucket + bucketIndex uint8 +} + // Get FNV-1a hash of this key. // // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash @@ -152,6 +158,45 @@ func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 memzero(value, uintptr(m.valueSize)) } +// Iterate over a hashmap. +//go:nobounds +func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool { + numBuckets := uintptr(1) << m.bucketBits + for { + if it.bucketIndex >= 8 { + // end of bucket, move to the next in the chain + it.bucketIndex = 0 + it.bucket = it.bucket.next + } + if it.bucket == nil { + if it.bucketNumber >= numBuckets { + // went through all buckets + return false + } + bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 + bucketAddr := uintptr(m.buckets) + bucketSize*it.bucketNumber + it.bucket = (*hashmapBucket)(unsafe.Pointer(bucketAddr)) + it.bucketNumber++ // next bucket + } + if it.bucket.tophash[it.bucketIndex] == 0 { + // slot is empty - move on + it.bucketIndex++ + continue + } + + bucketAddr := uintptr(unsafe.Pointer(it.bucket)) + slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(it.bucketIndex) + slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset) + slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex) + slotValue := unsafe.Pointer(bucketAddr + slotValueOffset) + memcpy(key, slotKey, uintptr(m.keySize)) + memcpy(value, slotValue, uintptr(m.valueSize)) + it.bucketIndex++ + + return true + } +} + // Hashmap with plain binary data keys (not containing strings etc.). func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {