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  }