github.com/nevalang/neva@v0.23.1-0.20240507185603-7696a9bb8dda/internal/compiler/irgen/network.go (about) 1 package irgen 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 src "github.com/nevalang/neva/internal/compiler/sourcecode" 9 "github.com/nevalang/neva/internal/runtime/ir" 10 ) 11 12 // processNetwork 13 // 1) inserts network connections 14 // 2) returns metadata about how subnodes are used by this network 15 func (g Generator) processNetwork( 16 conns []src.Connection, 17 nodeCtx nodeContext, 18 result *ir.Program, 19 ) (map[string]portsUsage, error) { 20 nodesPortsUsage := map[string]portsUsage{} 21 22 for _, conn := range conns { 23 // here's how we handle array-bypass connections 24 // sender is always component's inport 25 // based on that, we can to set receiver's inport slots 26 // to the value equal of the used slots of our inport 27 // that is known thanks to nodeCtx (metadata from parent node) 28 if conn.ArrayBypass != nil { 29 senderPortAddr := conn.ArrayBypass.SenderOutport 30 receiverPortAddr := conn.ArrayBypass.ReceiverInport 31 32 if _, ok := nodesPortsUsage[receiverPortAddr.Node]; !ok { 33 nodesPortsUsage[receiverPortAddr.Node] = portsUsage{ 34 in: map[relPortAddr]struct{}{}, 35 out: map[relPortAddr]struct{}{}, 36 } 37 } 38 39 var slotIdx uint8 = 0 40 for addr := range nodeCtx.portsUsage.in { 41 if addr.Port == senderPortAddr.Port { 42 addr := relPortAddr{Port: receiverPortAddr.Port, Idx: slotIdx} 43 nodesPortsUsage[receiverPortAddr.Node].in[addr] = struct{}{} 44 45 irSenderSide := ir.PortAddr{ 46 Path: joinNodePath(nodeCtx.path, senderPortAddr.Node), 47 Port: senderPortAddr.Port, 48 Idx: uint32(slotIdx), 49 } 50 51 irReceiverSide := ir.ReceiverConnectionSide{ 52 PortAddr: ir.PortAddr{ 53 Path: joinNodePath(nodeCtx.path, receiverPortAddr.Node) + "/in", 54 Port: receiverPortAddr.Port, 55 Idx: uint32(slotIdx), 56 }, 57 } 58 59 result.Connections = append(result.Connections, ir.Connection{ 60 SenderSide: irSenderSide, 61 ReceiverSides: []ir.ReceiverConnectionSide{ 62 irReceiverSide, 63 }, 64 }) 65 66 slotIdx++ 67 } 68 } 69 70 continue 71 } 72 73 senderSide := conn.Normal.SenderSide 74 75 irSenderSidePortAddr, err := g.processSenderSide( 76 nodeCtx, 77 senderSide, 78 nodesPortsUsage, 79 ) 80 if err != nil { 81 return nil, fmt.Errorf("process sender side: %w", err) 82 } 83 84 receiverSidesIR := make([]ir.ReceiverConnectionSide, 0, len(conn.Normal.ReceiverSide.Receivers)) 85 for _, receiverSide := range conn.Normal.ReceiverSide.Receivers { 86 receiverSideIR := g.mapReceiverSide(nodeCtx.path, receiverSide) 87 receiverSidesIR = append(receiverSidesIR, *receiverSideIR) 88 89 // same receiver can be used by multiple senders so we only add it once 90 if _, ok := nodesPortsUsage[receiverSide.PortAddr.Node]; !ok { 91 nodesPortsUsage[receiverSide.PortAddr.Node] = portsUsage{ 92 in: map[relPortAddr]struct{}{}, 93 out: map[relPortAddr]struct{}{}, 94 } 95 } 96 97 var idx uint8 98 if receiverSide.PortAddr.Idx != nil { 99 idx = *receiverSide.PortAddr.Idx 100 } 101 102 receiverNode := receiverSide.PortAddr.Node 103 receiverPortAddr := relPortAddr{ 104 Port: receiverSide.PortAddr.Port, 105 Idx: idx, 106 } 107 108 nodesPortsUsage[receiverNode].in[receiverPortAddr] = struct{}{} 109 } 110 111 result.Connections = append(result.Connections, ir.Connection{ 112 SenderSide: *irSenderSidePortAddr, 113 ReceiverSides: receiverSidesIR, 114 }) 115 } 116 117 return nodesPortsUsage, nil 118 } 119 120 func (g Generator) processSenderSide( 121 nodeCtx nodeContext, 122 senderSide src.ConnectionSenderSide, 123 result map[string]portsUsage, 124 ) (*ir.PortAddr, error) { 125 // there could be many connections with the same sender but we must only add it once 126 if _, ok := result[senderSide.PortAddr.Node]; !ok { 127 result[senderSide.PortAddr.Node] = portsUsage{ 128 in: map[relPortAddr]struct{}{}, 129 out: map[relPortAddr]struct{}{}, 130 } 131 } 132 133 var idx uint8 134 if senderSide.PortAddr.Idx != nil { 135 idx = *senderSide.PortAddr.Idx 136 } 137 138 // insert outport usage 139 result[senderSide.PortAddr.Node].out[relPortAddr{ 140 Port: senderSide.PortAddr.Port, 141 Idx: idx, 142 }] = struct{}{} 143 144 irSenderSide := &ir.PortAddr{ 145 Path: joinNodePath(nodeCtx.path, senderSide.PortAddr.Node), 146 Port: senderSide.PortAddr.Port, 147 Idx: uint32(idx), 148 } 149 150 if senderSide.PortAddr.Node == "in" { 151 return irSenderSide, nil 152 } 153 irSenderSide.Path += "/out" 154 155 return irSenderSide, nil 156 } 157 func (Generator) insertAndReturnInports( 158 nodeCtx nodeContext, 159 result *ir.Program, 160 ) []ir.PortAddr { 161 inports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.in)) 162 163 // in valid program all inports are used, so it's safe to depend on nodeCtx and not use component's IO 164 // actually we can't use IO because we need to know how many slots are used 165 for addr := range nodeCtx.portsUsage.in { 166 addr := &ir.PortAddr{ 167 Path: joinNodePath(nodeCtx.path, "in"), 168 Port: addr.Port, 169 Idx: uint32(addr.Idx), 170 } 171 result.Ports = append(result.Ports, ir.PortInfo{ 172 PortAddr: *addr, 173 BufSize: 0, 174 }) 175 inports = append(inports, *addr) 176 } 177 178 sortPortAddrs(inports) 179 180 return inports 181 } 182 183 // sortPortAddrs sorts port addresses by path, port and idx, 184 // this is very important because runtime function calls depends on this order. 185 func sortPortAddrs(addrs []ir.PortAddr) { 186 sort.Slice(addrs, func(i, j int) bool { 187 if addrs[i].Path != addrs[j].Path { 188 return addrs[i].Path < addrs[j].Path 189 } 190 if addrs[i].Port != addrs[j].Port { 191 return addrs[i].Port < addrs[j].Port 192 } 193 return addrs[i].Idx < addrs[j].Idx 194 }) 195 } 196 197 func (Generator) insertAndReturnOutports( 198 nodeCtx nodeContext, 199 result *ir.Program, 200 ) []ir.PortAddr { 201 outports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.out)) 202 203 // In a valid (desugared) program all outports are used so it's safe to depend on nodeCtx and not use component's IO. 204 // Actually we can't use IO because we need to know how many slots are used. 205 for addr := range nodeCtx.portsUsage.out { 206 irAddr := &ir.PortAddr{ 207 Path: joinNodePath(nodeCtx.path, "out"), 208 Port: addr.Port, 209 Idx: uint32(addr.Idx), 210 } 211 result.Ports = append(result.Ports, ir.PortInfo{ 212 PortAddr: *irAddr, 213 BufSize: 0, 214 }) 215 outports = append(outports, *irAddr) 216 } 217 218 sortPortAddrs(outports) 219 220 return outports 221 } 222 223 // mapReceiverSide maps compiler connection side to ir connection side 1-1 just making the port addr's path absolute 224 func (g Generator) mapReceiverSide(nodeCtxPath []string, side src.ConnectionReceiver) *ir.ReceiverConnectionSide { 225 var idx uint8 226 if side.PortAddr.Idx != nil { 227 idx = *side.PortAddr.Idx 228 } 229 230 result := &ir.ReceiverConnectionSide{ 231 PortAddr: ir.PortAddr{ 232 Path: joinNodePath(nodeCtxPath, side.PortAddr.Node), 233 Port: side.PortAddr.Port, 234 Idx: uint32(idx), 235 }, 236 } 237 if side.PortAddr.Node == "out" { // 'out' node is actually receiver but we don't want to have 'out.in' addresses 238 return result 239 } 240 result.PortAddr.Path += "/in" 241 return result 242 } 243 244 func joinNodePath(nodePath []string, nodeName string) string { 245 newPath := make([]string, len(nodePath)) 246 copy(newPath, nodePath) 247 newPath = append(newPath, nodeName) 248 return strings.Join(newPath, "/") 249 }