channel.go raw

   1  package compiler
   2  
   3  // This file lowers channel operations (make/send/recv/close) to runtime calls
   4  // or pseudo-operations that are lowered during goroutine lowering.
   5  
   6  import (
   7  	"fmt"
   8  	"go/types"
   9  	"math"
  10  
  11  	"moxie/compiler/llvmutil"
  12  	"moxie/loader"
  13  	"golang.org/x/tools/go/ssa"
  14  	"tinygo.org/x/go-llvm"
  15  )
  16  
  17  func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
  18  	elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
  19  	elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
  20  	bufSize := b.getValue(expr.Size, getPos(expr))
  21  	b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
  22  	if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
  23  		bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
  24  	} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
  25  		bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
  26  	}
  27  	ch := b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
  28  	if b.PrintAllocs != nil {
  29  		b.emitLogAlloc(expr.Pos())
  30  	}
  31  	return ch
  32  }
  33  
  34  // createChanSend emits a pseudo chan send operation. It is lowered to the
  35  // actual channel send operation during goroutine lowering.
  36  func (b *builder) createChanSend(instr *ssa.Send) {
  37  	// Wasm spawn-boundary SAB channel: bypass runtime, call bridge directly.
  38  	if b.isWasmTarget() {
  39  		if handle, ok := b.sabChannels[instr.Chan]; ok {
  40  			b.createWasmSABChanSend(instr, handle)
  41  			return
  42  		}
  43  	}
  44  	// Native: any chan whose element type implements moxie.Codec routes
  45  	// through ChanSendDual at runtime, which dispatches on ch.pipeBoundFd.
  46  	// This preserves correctness through array / slice / struct-field
  47  	// indirection where the compiler's per-SSA-value tracking can't see
  48  	// that the chan was originally passed to spawn().
  49  	if b.elemTypeImplementsCodec(instr.X.Type()) {
  50  		b.createCodecChanSend(instr)
  51  		return
  52  	}
  53  
  54  	ch := b.getValue(instr.Chan, getPos(instr))
  55  	chanValue := b.getValue(instr.X, getPos(instr))
  56  
  57  	// store value-to-send
  58  	valueType := b.getLLVMType(instr.X.Type())
  59  	isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
  60  	var valueAlloca, valueAllocaSize llvm.Value
  61  	if isZeroSize {
  62  		valueAlloca = llvm.ConstNull(b.dataPtrType)
  63  	} else {
  64  		valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
  65  		b.CreateStore(chanValue, valueAlloca)
  66  	}
  67  
  68  	// Allocate buffer for the channel operation.
  69  	channelOp := b.getLLVMRuntimeType("channelOp")
  70  	channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
  71  
  72  	// Do the send.
  73  	b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
  74  
  75  	// End the lifetime of the allocas.
  76  	// This also works around a bug in CoroSplit, at least in LLVM 8:
  77  	// https://bugs.llvm.org/show_bug.cgi?id=41742
  78  	b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
  79  	if !isZeroSize {
  80  		b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
  81  	}
  82  }
  83  
  84  // createChanRecv emits a pseudo chan receive operation. It is lowered to the
  85  // actual channel receive operation during goroutine lowering.
  86  func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
  87  	// Wasm spawn-boundary SAB channel.
  88  	if b.isWasmTarget() {
  89  		if handle, ok := b.sabChannels[unop.X]; ok {
  90  			return b.createWasmSABChanRecv(unop, handle)
  91  		}
  92  	}
  93  	// Native: dual-path runtime dispatch for Codec-implementing types.
  94  	chanType := unop.X.Type().Underlying().(*types.Chan)
  95  	if b.elemTypeImplementsCodec(chanType.Elem()) {
  96  		return b.createCodecChanRecv(unop)
  97  	}
  98  
  99  	valueType := b.getLLVMType(chanType.Elem())
 100  	ch := b.getValue(unop.X, getPos(unop))
 101  
 102  	// Allocate memory to receive into.
 103  	isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
 104  	var valueAlloca, valueAllocaSize llvm.Value
 105  	if isZeroSize {
 106  		valueAlloca = llvm.ConstNull(b.dataPtrType)
 107  	} else {
 108  		valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
 109  	}
 110  
 111  	// Allocate buffer for the channel operation.
 112  	channelOp := b.getLLVMRuntimeType("channelOp")
 113  	channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
 114  
 115  	// Do the receive.
 116  	commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
 117  	var received llvm.Value
 118  	if isZeroSize {
 119  		received = llvm.ConstNull(valueType)
 120  	} else {
 121  		received = b.CreateLoad(valueType, valueAlloca, "chan.received")
 122  		b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
 123  	}
 124  	b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
 125  
 126  	if unop.CommaOk {
 127  		tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
 128  		tuple = b.CreateInsertValue(tuple, received, 0, "")
 129  		tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
 130  		return tuple
 131  	} else {
 132  		return received
 133  	}
 134  }
 135  
 136  // createChanClose closes the given channel.
 137  func (b *builder) createChanClose(ch llvm.Value) {
 138  	b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
 139  }
 140  
 141  // createWasmSABChanSend emits a bridge.channel_send call for spawn-boundary channels.
 142  func (b *builder) createWasmSABChanSend(instr *ssa.Send, handle llvm.Value) {
 143  	chanValue := b.getValue(instr.X, getPos(instr))
 144  	valueType := b.getLLVMType(instr.X.Type())
 145  	sz := b.targetData.TypeAllocSize(valueType)
 146  	i32 := b.ctx.Int32Type()
 147  
 148  	if sz == 0 {
 149  		b.emitBridgeCall("channel_send", []llvm.Value{
 150  			handle,
 151  			llvm.ConstNull(b.dataPtrType),
 152  			llvm.ConstInt(i32, 0, false),
 153  		}, b.ctx.VoidType())
 154  		return
 155  	}
 156  	alloca, allocaSize := b.createTemporaryAlloca(valueType, "sab.send.val")
 157  	b.CreateStore(chanValue, alloca)
 158  	b.emitBridgeCall("channel_send", []llvm.Value{
 159  		handle,
 160  		alloca,
 161  		llvm.ConstInt(i32, sz, false),
 162  	}, b.ctx.VoidType())
 163  	b.emitLifetimeEnd(alloca, allocaSize)
 164  }
 165  
 166  // createWasmSABChanRecv emits a bridge.channel_recv call for spawn-boundary channels.
 167  func (b *builder) createWasmSABChanRecv(unop *ssa.UnOp, handle llvm.Value) llvm.Value {
 168  	valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
 169  	sz := b.targetData.TypeAllocSize(valueType)
 170  	i32 := b.ctx.Int32Type()
 171  
 172  	alloca, allocaSize := b.createTemporaryAlloca(valueType, "sab.recv.val")
 173  	result := b.emitBridgeCall("channel_recv", []llvm.Value{
 174  		handle,
 175  		alloca,
 176  		llvm.ConstInt(i32, sz, false),
 177  	}, i32)
 178  
 179  	var received llvm.Value
 180  	if sz == 0 {
 181  		received = llvm.ConstNull(valueType)
 182  	} else {
 183  		received = b.CreateLoad(valueType, alloca, "sab.received")
 184  		b.emitLifetimeEnd(alloca, allocaSize)
 185  	}
 186  
 187  	if unop.CommaOk {
 188  		// result == -1 (all bits set as int32) means closed.
 189  		isOpen := b.CreateICmp(llvm.IntNE, result, llvm.ConstInt(i32, ^uint64(0), true), "sab.ok")
 190  		tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
 191  		tuple = b.CreateInsertValue(tuple, received, 0, "")
 192  		tuple = b.CreateInsertValue(tuple, isOpen, 1, "")
 193  		return tuple
 194  	}
 195  	return received
 196  }
 197  
 198  // createSelect emits all IR necessary for a select statements. That's a
 199  // non-trivial amount of code because select is very complex to implement.
 200  func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
 201  	if len(expr.States) == 0 {
 202  		// Shortcuts for some simple selects.
 203  		llvmType := b.getLLVMType(expr.Type())
 204  		if expr.Blocking {
 205  			// Blocks forever:
 206  			//     select {}
 207  			b.createRuntimeCall("deadlock", nil, "")
 208  			return llvm.Undef(llvmType)
 209  		} else {
 210  			// No-op:
 211  			//     select {
 212  			//     default:
 213  			//     }
 214  			retval := llvm.Undef(llvmType)
 215  			retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
 216  			return retval // {-1, false}
 217  		}
 218  	}
 219  
 220  	const maxSelectStates = math.MaxUint32 >> 2
 221  	if len(expr.States) > maxSelectStates {
 222  		// The runtime code assumes that the number of state must fit in 30 bits
 223  		// (so the select index can be stored in a uint32 with two bits reserved
 224  		// for other purposes). It seems unlikely that a real program would have
 225  		// that many states, but we check for this case anyway to be sure.
 226  		// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
 227  		// operations which aren't available everywhere.
 228  		b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
 229  
 230  		// Continue as usual (we'll generate broken code but the error will
 231  		// prevent the compilation to complete).
 232  	}
 233  
 234  	// This code create a (stack-allocated) slice containing all the select
 235  	// cases and then calls runtime.chanSelect to perform the actual select
 236  	// statement.
 237  	// Simple selects (blocking and with just one case) are already transformed
 238  	// into regular chan operations during SSA construction so we don't have to
 239  	// optimize such small selects.
 240  
 241  	// Go through all the cases. Create the selectStates slice and and
 242  	// determine the receive buffer size and alignment.
 243  	recvbufSize := uint64(0)
 244  	recvbufAlign := 0
 245  	var selectStates []llvm.Value
 246  	chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
 247  	for _, state := range expr.States {
 248  		ch := b.getValue(state.Chan, state.Pos)
 249  		selectState := llvm.ConstNull(chanSelectStateType)
 250  		selectState = b.CreateInsertValue(selectState, ch, 0, "")
 251  		switch state.Dir {
 252  		case types.RecvOnly:
 253  			// Make sure the receive buffer is big enough and has the correct alignment.
 254  			llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
 255  			if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
 256  				recvbufSize = size
 257  			}
 258  			if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
 259  				recvbufAlign = align
 260  			}
 261  		case types.SendOnly:
 262  			// Store this value in an alloca and put a pointer to this alloca
 263  			// in the send state.
 264  			sendValue := b.getValue(state.Send, state.Pos)
 265  			alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
 266  			b.CreateStore(sendValue, alloca)
 267  			selectState = b.CreateInsertValue(selectState, alloca, 1, "")
 268  		default:
 269  			panic("unreachable")
 270  		}
 271  		selectStates = append(selectStates, selectState)
 272  	}
 273  
 274  	// Create a receive buffer, where the received value will be stored.
 275  	recvbuf := llvm.Undef(b.dataPtrType)
 276  	if recvbufSize != 0 {
 277  		allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
 278  		recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
 279  		recvbufAlloca.SetAlignment(recvbufAlign)
 280  		recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
 281  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 282  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 283  		}, "select.recvbuf")
 284  	}
 285  
 286  	// Create the states slice (allocated on the stack).
 287  	statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
 288  	statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
 289  	for i, state := range selectStates {
 290  		// Set each slice element to the appropriate channel.
 291  		gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
 292  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 293  			llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
 294  		}, "")
 295  		b.CreateStore(state, gep)
 296  	}
 297  	statesPtr := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
 298  		llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 299  		llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 300  	}, "select.states")
 301  	statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
 302  
 303  	// Do the select in the runtime.
 304  	var results llvm.Value
 305  	if expr.Blocking {
 306  		// Stack-allocate operation structures.
 307  		// If these were simply created as a slice, they would heap-allocate.
 308  		opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
 309  		opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
 310  		opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
 311  		opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
 312  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 313  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
 314  		}, "select.block")
 315  
 316  		results = b.createRuntimeCall("chanSelect", []llvm.Value{
 317  			recvbuf,
 318  			statesPtr, statesLen, statesLen, // []chanSelectState
 319  			opsPtr, opsLen, opsLen, // []channelOp
 320  		}, "select.result")
 321  
 322  		// Terminate the lifetime of the operation structures.
 323  		b.emitLifetimeEnd(opsAlloca, opsSize)
 324  	} else {
 325  		opsPtr := llvm.ConstNull(b.dataPtrType)
 326  		opsLen := llvm.ConstInt(b.uintptrType, 0, false)
 327  		results = b.createRuntimeCall("chanSelect", []llvm.Value{
 328  			recvbuf,
 329  			statesPtr, statesLen, statesLen, // []chanSelectState
 330  			opsPtr, opsLen, opsLen, // []channelOp (nil slice)
 331  		}, "select.result")
 332  	}
 333  
 334  	// Terminate the lifetime of the states alloca.
 335  	b.emitLifetimeEnd(statesAlloca, statesSize)
 336  
 337  	// The result value does not include all the possible received values,
 338  	// because we can't load them in advance. Instead, the *ssa.Extract
 339  	// instruction will treat a *ssa.Select specially and load it there inline.
 340  	// Store the receive alloca in a sidetable until we hit this extract
 341  	// instruction.
 342  	if b.selectRecvBuf == nil {
 343  		b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
 344  	}
 345  	b.selectRecvBuf[expr] = recvbuf
 346  
 347  	return results
 348  }
 349  
 350  // getChanSelectResult returns the special values from a *ssa.Extract expression
 351  // when extracting a value from a select statement (*ssa.Select). Because
 352  // *ssa.Select cannot load all values in advance, it does this later in the
 353  // *ssa.Extract expression.
 354  func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
 355  	if expr.Index == 0 {
 356  		// index
 357  		value := b.getValue(expr.Tuple, getPos(expr))
 358  		index := b.CreateExtractValue(value, expr.Index, "")
 359  		if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
 360  			index = b.CreateSExt(index, b.intType, "")
 361  		}
 362  		return index
 363  	} else if expr.Index == 1 {
 364  		// comma-ok
 365  		value := b.getValue(expr.Tuple, getPos(expr))
 366  		return b.CreateExtractValue(value, expr.Index, "")
 367  	} else {
 368  		// Select statements are (index, ok, ...) where ... is a number of
 369  		// received values, depending on how many receive statements there
 370  		// are. They are all combined into one alloca (because only one
 371  		// receive can proceed at a time) so we'll get that alloca, bitcast
 372  		// it to the correct type, and dereference it.
 373  		recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
 374  		typ := b.getLLVMType(expr.Type())
 375  		return b.CreateLoad(typ, recvbuf, "")
 376  	}
 377  }
 378  
 379  // createNativePipeChanSend emits the native spawn-boundary chan send
 380  // path. Wraps the value as a moxie.Codec interface (with *T as the
 381  // underlying value so both value- and pointer-receiver methods are
 382  // dispatchable) and calls runtime.PipeChanSendCodec, which invokes
 383  // the user-defined EncodeTo to serialize correctly across the fork.
 384  //
 385  // Codec replaces raw-memcpy: types containing slices, maps, or other
 386  // indirect data now serialize their byte payload, not just their
 387  // pointer headers. The compiler's stack allocas stay T-sized (no
 388  // quadratic codegen blowup from huge inline allocas) because EncodeTo
 389  // emits bytes incrementally.
 390  // createCodecChanSend emits the dual-path chan send for any chan whose
 391  // element type implements moxie.Codec. The runtime helper ChanSendDual
 392  // branches on ch.pipeBoundFd: if pipe-bound, EncodeTo then PipeSend; if
 393  // local, the existing chanSend in-runtime queue path.
 394  //
 395  // Compile-time emission shape:
 396  //   alloca v: T            // store the value-to-send
 397  //   alloca op: channelOp   // for local-queue path
 398  //   codec := iface{*T, &v}
 399  //   ChanSendDual(ch, codec, &v, &op)
 400  func (b *builder) createCodecChanSend(instr *ssa.Send) {
 401  	ch := b.getValue(instr.Chan, getPos(instr))
 402  	chanValue := b.getValue(instr.X, getPos(instr))
 403  	elemType := instr.X.Type()
 404  	elemLLVM := b.getLLVMType(elemType)
 405  
 406  	valAlloca, valAllocaSize := b.createTemporaryAlloca(elemLLVM, "chan.value")
 407  	b.CreateStore(chanValue, valAlloca)
 408  	codecIface := b.createPipeChanCodecIface(elemType, valAlloca)
 409  
 410  	channelOpType := b.getLLVMRuntimeType("channelOp")
 411  	opAlloca, opAllocaSize := b.createTemporaryAlloca(channelOpType, "chan.op")
 412  
 413  	runtimePkg := b.program.ImportedPackage("runtime")
 414  	sendFn := runtimePkg.Members["ChanSendDual"].(*ssa.Function)
 415  	sendType, sendLLVM := b.getFunction(sendFn)
 416  
 417  	args := []llvm.Value{ch}
 418  	args = append(args, b.expandFormalParam(codecIface)...)
 419  	args = append(args, valAlloca, opAlloca)
 420  	args = append(args, llvm.Undef(b.dataPtrType))
 421  	b.createCall(sendType, sendLLVM, args, "")
 422  
 423  	b.emitLifetimeEnd(opAlloca, opAllocaSize)
 424  	b.emitLifetimeEnd(valAlloca, valAllocaSize)
 425  }
 426  
 427  // createCodecChanRecv emits the dual-path chan recv counterpart of
 428  // createCodecChanSend.
 429  func (b *builder) createCodecChanRecv(unop *ssa.UnOp) llvm.Value {
 430  	elemType := unop.X.Type().Underlying().(*types.Chan).Elem()
 431  	elemLLVM := b.getLLVMType(elemType)
 432  	ch := b.getValue(unop.X, getPos(unop))
 433  
 434  	valAlloca, valAllocaSize := b.createTemporaryAlloca(elemLLVM, "chan.value")
 435  	b.CreateStore(llvm.ConstNull(elemLLVM), valAlloca)
 436  	codecIface := b.createPipeChanCodecIface(elemType, valAlloca)
 437  
 438  	channelOpType := b.getLLVMRuntimeType("channelOp")
 439  	opAlloca, opAllocaSize := b.createTemporaryAlloca(channelOpType, "chan.op")
 440  
 441  	runtimePkg := b.program.ImportedPackage("runtime")
 442  	recvFn := runtimePkg.Members["ChanRecvDual"].(*ssa.Function)
 443  	recvType, recvLLVM := b.getFunction(recvFn)
 444  
 445  	args := []llvm.Value{ch}
 446  	args = append(args, b.expandFormalParam(codecIface)...)
 447  	args = append(args, valAlloca, opAlloca)
 448  	args = append(args, llvm.Undef(b.dataPtrType))
 449  	okBool := b.createCall(recvType, recvLLVM, args, "chan.recv.ok")
 450  
 451  	received := b.CreateLoad(elemLLVM, valAlloca, "chan.received")
 452  	b.emitLifetimeEnd(opAlloca, opAllocaSize)
 453  	b.emitLifetimeEnd(valAlloca, valAllocaSize)
 454  
 455  	if unop.CommaOk {
 456  		tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{elemLLVM, b.ctx.Int1Type()}, false))
 457  		tuple = b.CreateInsertValue(tuple, received, 0, "")
 458  		tuple = b.CreateInsertValue(tuple, okBool, 1, "")
 459  		return tuple
 460  	}
 461  	return received
 462  }
 463  
 464  // elemTypeImplementsCodec reports whether the given chan element type
 465  // satisfies the moxie.Codec interface (or *T does — covers value- and
 466  // pointer-receiver methods). Used to decide whether to emit the dual-
 467  // path chan op or the local-only path.
 468  func (b *builder) elemTypeImplementsCodec(t types.Type) bool {
 469  	if ch, ok := t.Underlying().(*types.Chan); ok {
 470  		t = ch.Elem()
 471  	}
 472  	moxiePkg := b.program.ImportedPackage("moxie")
 473  	if moxiePkg == nil {
 474  		return false
 475  	}
 476  	codecObj := moxiePkg.Pkg.Scope().Lookup("Codec")
 477  	if codecObj == nil {
 478  		return false
 479  	}
 480  	codecIface, ok := codecObj.Type().Underlying().(*types.Interface)
 481  	if !ok {
 482  		return false
 483  	}
 484  	return loader.ImplementsCodecIface(t, codecIface)
 485  }
 486  
 487  // createPipeChanCodecIface builds a moxie.Codec interface value whose
 488  // typecode is *T (so value-receiver and pointer-receiver methods on T
 489  // both dispatch correctly) and whose valuePtr points at the supplied
 490  // alloca holding (or to be filled with) a T value.
 491  //
 492  // Avoids createMakeInterface — that would heap-allocate the value via
 493  // emitPointerPack for non-pointer-typed inputs. Here valPtr is already
 494  // a pointer; we splice it directly into the _interface struct.
 495  func (b *builder) createPipeChanCodecIface(elemType types.Type, valPtr llvm.Value) llvm.Value {
 496  	ptrType := types.NewPointer(elemType)
 497  	typecode := b.getTypeCode(ptrType)
 498  	itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
 499  	itf = b.CreateInsertValue(itf, typecode, 0, "")
 500  	itf = b.CreateInsertValue(itf, valPtr, 1, "")
 501  	return itf
 502  }
 503