diff --git a/compiler/channel.go b/compiler/channel.go index 82139de8b..444c1f2b1 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -30,7 +30,6 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { // actual channel send operation during goroutine lowering. func (b *builder) createChanSend(instr *ssa.Send) { ch := b.getValue(instr.Chan, getPos(instr)) - chanValue := b.getValue(instr.X, getPos(instr)) // store value-to-send valueType := b.getLLVMType(instr.X.Type()) @@ -39,8 +38,7 @@ func (b *builder) createChanSend(instr *ssa.Send) { if isZeroSize { valueAlloca = llvm.ConstNull(b.dataPtrType) } else { - valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") - b.CreateStore(chanValue, valueAlloca) + valueAlloca, valueAllocaSize = b.getValueStorage(instr.X, "chan.value") } // Allocate buffer for the channel operation. @@ -65,38 +63,17 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) ch := b.getValue(unop.X, getPos(unop)) - // Allocate memory to receive into. - isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 - var valueAlloca, valueAllocaSize llvm.Value - if isZeroSize { - valueAlloca = llvm.ConstNull(b.dataPtrType) - } else { - valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") - } + result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan") // Allocate buffer for the channel operation. channelOp := b.getLLVMRuntimeType("channelOp") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the receive. - commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") - var received llvm.Value - if isZeroSize { - received = llvm.ConstNull(valueType) - } else { - received = b.CreateLoad(valueType, valueAlloca, "chan.received") - b.emitLifetimeEnd(valueAlloca, valueAllocaSize) - } + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "") + received := result.finish(b, commaOk, "chan.received") b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) - - if unop.CommaOk { - tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) - tuple = b.CreateInsertValue(tuple, received, 0, "") - tuple = b.CreateInsertValue(tuple, commaOk, 1, "") - return tuple - } else { - return received - } + return received } // createChanClose closes the given channel. @@ -170,9 +147,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. - sendValue := b.getValue(state.Send, state.Pos) - alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") - b.CreateStore(sendValue, alloca) + alloca := b.getSelectSendStorage(state.Send) selectState = b.CreateInsertValue(selectState, alloca, 1, "") default: panic("unreachable") @@ -280,7 +255,14 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { // receive can proceed at a time) so we'll get that alloca, bitcast // it to the correct type, and dereference it. recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] - typ := b.getLLVMType(expr.Type()) - return b.CreateLoad(typ, recvbuf, "") + return b.loadFromStorage(recvbuf, expr.Type(), "select.received") } } + +func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value { + typ := b.getLLVMType(value.Type()) + llvmValue := b.getValue(value, getPos(value)) + ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value") + b.CreateStore(llvmValue, ptr) + return ptr +} diff --git a/compiler/compiler.go b/compiler/compiler.go index f51a9871c..90776b892 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1546,10 +1546,8 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.CreateBr(blockJump) case *ssa.MapUpdate: m := b.getValue(instr.Map, getPos(instr)) - key := b.getValue(instr.Key, getPos(instr)) - value := b.getValue(instr.Value, getPos(instr)) mapType := instr.Map.Type().Underlying().(*types.Map) - b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos()) case *ssa.Panic: value := b.getValue(instr.X, getPos(instr)) b.createRuntimeInvoke("_panic", []llvm.Value{value}, "") @@ -1584,18 +1582,80 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.createChanSend(instr) case *ssa.Store: llvmAddr := b.getValue(instr.Addr, getPos(instr)) - llvmVal := b.getValue(instr.Val, getPos(instr)) b.createNilCheck(instr.Addr, llvmAddr, "store") - if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { + llvmType := b.getLLVMType(instr.Val.Type()) + if b.targetData.TypeAllocSize(llvmType) == 0 { // nothing to store return } - b.CreateStore(llvmVal, llvmAddr) + b.storeValue(llvmAddr, instr.Val) default: b.addError(instr.Pos(), "unknown instruction: "+instr.String()) } } +func (b *builder) storeValue(dst llvm.Value, value ssa.Value) { + b.CreateStore(b.getValue(value, getPos(value)), dst) +} + +func (b *builder) loadFromStorage(ptr llvm.Value, typ types.Type, name string) llvm.Value { + return b.CreateLoad(b.getLLVMType(typ), ptr, name) +} + +func (b *builder) getValueStorage(value ssa.Value, name string) (ptr, size llvm.Value) { + typ := b.getLLVMType(value.Type()) + ptr, size = b.createTemporaryAlloca(typ, name) + b.storeValue(ptr, value) + return ptr, size +} + +type runtimeValueResult struct { + valueType llvm.Type + resultType llvm.Type + valuePtr llvm.Value + valueSize llvm.Value + temporary bool + zero bool + commaOk bool +} + +func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsNull bool, name string) runtimeValueResult { + result := runtimeValueResult{ + valueType: valueType, + resultType: valueType, + commaOk: commaOk, + } + if commaOk { + result.resultType = b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false) + } + if zeroAsNull && b.targetData.TypeAllocSize(valueType) == 0 { + result.valuePtr = llvm.ConstNull(b.dataPtrType) + result.zero = true + return result + } + result.valuePtr, result.valueSize = b.createTemporaryAlloca(valueType, name+".value") + result.temporary = true + return result +} + +func (r runtimeValueResult) finish(b *builder, commaOk llvm.Value, name string) llvm.Value { + var value llvm.Value + if r.zero { + value = llvm.ConstNull(r.valueType) + } else { + value = b.CreateLoad(r.valueType, r.valuePtr, name) + } + if r.temporary { + b.emitLifetimeEnd(r.valuePtr, r.valueSize) + } + if !r.commaOk { + return value + } + result := llvm.Undef(r.resultType) + result = b.CreateInsertValue(result, value, 0, "") + return b.CreateInsertValue(result, commaOk, 1, "") +} + // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // LLVM IR. It uses runtime calls for some builtins. func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) { @@ -2270,11 +2330,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { case *ssa.Global: panic("global is not an expression") case *ssa.Index: - collection := b.getValue(expr.X, getPos(expr)) index := b.getValue(expr.Index, getPos(expr)) switch xType := expr.X.Type().Underlying().(type) { case *types.Basic: // extract byte from string + collection := b.getValue(expr.X, getPos(expr)) // Value type must be a string, which is a basic type. if xType.Info()&types.IsString == 0 { panic("lookup on non-string?") @@ -2308,12 +2368,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { // Can't load directly from array (as index is non-constant), so // have to do it using an alloca+gep+load. - arrayType := collection.Type() - alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") - b.CreateStore(collection, alloca) + arrayType := b.getLLVMType(expr.X.Type()) + alloca, allocaSize := b.getValueStorage(expr.X, "index.alloca") zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") - result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") + result := b.loadFromStorage(ptr, expr.Type(), "index.load") b.emitLifetimeEnd(alloca, allocaSize) return result, nil default: @@ -2373,12 +2432,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { } case *ssa.Lookup: // map lookup value := b.getValue(expr.X, getPos(expr)) - index := b.getValue(expr.Index, getPos(expr)) valueType := expr.Type() if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } - return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos()) + return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.Index, expr.CommaOk, expr.Pos()) case *ssa.MakeChan: return b.createMakeChan(expr), nil case *ssa.MakeClosure: @@ -3453,8 +3511,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { return fn, nil } else { b.createNilCheck(unop.X, x, "deref") - load := b.CreateLoad(valueType, x, "") - return load, nil + return b.loadFromStorage(x, unop.Type(), ""), nil } case token.XOR: // ^x, toggle all bits in integer return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil diff --git a/compiler/goroutine.go b/compiler/goroutine.go index b3e02c200..3d2dac49b 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -50,7 +50,7 @@ func (b *builder) createGo(instr *ssa.Go) { // Get all function parameters to pass to the goroutine. var params []llvm.Value for _, param := range instr.Call.Args { - params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...) + params = append(params, b.getGoroutineCallArgument(param)...) } var prefix string @@ -122,6 +122,10 @@ func (b *builder) createGo(instr *ssa.Go) { b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") } +func (b *builder) getGoroutineCallArgument(value ssa.Value) []llvm.Value { + return b.expandFormalParam(b.getValue(value, getPos(value))) +} + // Create an exported wrapper function for functions with the //go:wasmexport // pragma. This wrapper function is quite complex when the scheduler is enabled: // it needs to start a new goroutine each time the exported function is called. diff --git a/compiler/map.go b/compiler/map.go index 4f9ec66ea..b2478b19c 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -72,19 +72,20 @@ func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llv // createMapLookup returns the value in a map. It calls a runtime function // depending on the map key type to load the map value and its comma-ok value. -func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { +func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { llvmValueType := b.getLLVMType(valueType) // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") + result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap") + mapValueAlloca := result.valuePtr // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on // nil maps, and they'll need to zero the value pointer by that number of // bytes. - mapValueSize := mapValueAllocaSize + mapValueSize := result.valueSize if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) } @@ -94,13 +95,12 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} + params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else { // Key stored at actual type: either binary-comparable or with // compiler-generated hash/equal. - mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") - b.CreateStore(key, mapKeyAlloca) + mapKeyAlloca, mapKeySize := b.getValueStorage(key, "hashmap.key") params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize} fnName := "hashmapBinaryGet" if !hashmapIsBinaryKey(keyType) { @@ -110,35 +110,22 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) } - // Load the resulting value from the hashmap. The value is set to the zero - // value if the key doesn't exist in the hashmap. - mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") - b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) - - if commaOk { - tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) - tuple = b.CreateInsertValue(tuple, mapValue, 0, "") - tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "") - return tuple, nil - } else { - return mapValue, nil - } + // The value is set to the zero value if the key doesn't exist. + return result.finish(b, commaOkValue, ""), nil } // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. -func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") - b.CreateStore(value, valueAlloca) +func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) { + valueAlloca, valueSize := b.getValueStorage(value, "hashmap.value") keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, key, valueAlloca} + params := []llvm.Value{m, b.getValue(key, getPos(key)), valueAlloca} b.createRuntimeInvoke("hashmapStringSet", params, "") } else { // Key stored at actual type. - keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") - b.CreateStore(key, keyAlloca) + keyAlloca, keySize := b.getValueStorage(key, "hashmap.key") fnName := "hashmapBinarySet" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericSet"