mxbuiltins.go raw

   1  package compiler
   2  
   3  // Moxie builtin interception.
   4  //
   5  // The source rewrite phase converts Moxie-specific syntax into calls to
   6  // stub functions (__moxie_concat for | pipe concatenation). This file
   7  // intercepts those calls at the SSA level and emits the correct LLVM IR.
   8  //
   9  // Pipe semantics:
  10  //   x = x | y / x |= y  → bytesConcatMove (extend in place, free old on grow)
  11  //   z := x | y           → bytesConcat     (alloc new, copy both, no free)
  12  //
  13  // The distinction is compile-time: if the Call result is stored back to the
  14  // same address the LHS was loaded from, it's a move. Otherwise it's a borrow.
  15  
  16  import (
  17  	"go/token"
  18  
  19  	"golang.org/x/tools/go/ssa"
  20  	"tinygo.org/x/go-llvm"
  21  )
  22  
  23  // createMoxieConcat handles __moxie_concat(a, b []byte) []byte.
  24  // Detects move vs borrow at the SSA level and emits the appropriate
  25  // runtime call.
  26  func (b *builder) createMoxieConcat(instr *ssa.CallCommon, call *ssa.Call) llvm.Value {
  27  	a := b.getValue(instr.Args[0], instr.Pos())
  28  	bv := b.getValue(instr.Args[1], instr.Pos())
  29  	if call != nil && isConcatMove(instr, call) {
  30  		return b.createRuntimeCall("bytesConcatMove", []llvm.Value{a, bv}, "")
  31  	}
  32  	return b.createRuntimeCall("bytesConcat", []llvm.Value{a, bv}, "")
  33  }
  34  
  35  // isConcatMove returns true if the concat result is stored back to the same
  36  // address the first argument was loaded from. This means the LHS is consumed
  37  // (move semantics) and the old buffer can be freed on growth.
  38  func isConcatMove(instr *ssa.CallCommon, call *ssa.Call) bool {
  39  	// Find the source address of the first argument (LHS of pipe).
  40  	lhs := instr.Args[0]
  41  	var srcAddr ssa.Value
  42  	if load, ok := lhs.(*ssa.UnOp); ok && load.Op == token.MUL {
  43  		srcAddr = load.X
  44  	}
  45  	if srcAddr == nil {
  46  		return false
  47  	}
  48  	// Check if the Call result is stored back to the same address.
  49  	refs := call.Referrers()
  50  	if refs == nil {
  51  		return false
  52  	}
  53  	for _, ref := range *refs {
  54  		if store, ok := ref.(*ssa.Store); ok {
  55  			if sameAddr(store.Addr, srcAddr) {
  56  				return true
  57  			}
  58  		}
  59  	}
  60  	return false
  61  }
  62  
  63  // sameAddr returns true if two SSA values refer to the same memory address.
  64  // Handles FieldAddr (same base + field), IndexAddr, and direct identity.
  65  func sameAddr(a, b ssa.Value) bool {
  66  	if a == b {
  67  		return true
  68  	}
  69  	fa, aOk := a.(*ssa.FieldAddr)
  70  	fb, bOk := b.(*ssa.FieldAddr)
  71  	if aOk && bOk && fa.Field == fb.Field {
  72  		return sameAddr(fa.X, fb.X)
  73  	}
  74  	ia, aOk := a.(*ssa.IndexAddr)
  75  	ib, bOk := b.(*ssa.IndexAddr)
  76  	if aOk && bOk && ia.Index == ib.Index {
  77  		return sameAddr(ia.X, ib.X)
  78  	}
  79  	return false
  80  }
  81  
  82  // createMoxieConcatMove handles __moxie_concat_move(a, b []byte) []byte.
  83  // Move semantics: extends a in place if capacity allows, otherwise grows
  84  // via sliceGrow (alloc new + copy + free old). Old a buffer is consumed.
  85  func (b *builder) createMoxieConcatMove(instr *ssa.CallCommon) llvm.Value {
  86  	a := b.getValue(instr.Args[0], instr.Pos())
  87  	bv := b.getValue(instr.Args[1], instr.Pos())
  88  	return b.createRuntimeCall("bytesConcatMove", []llvm.Value{a, bv}, "")
  89  }
  90  
  91  // createMoxieEq handles __moxie_eq(a, b []byte) bool.
  92  // Emits a call to runtime.stringEqual (same layout as []byte).
  93  func (b *builder) createMoxieEq(instr *ssa.CallCommon) llvm.Value {
  94  	a := b.getValue(instr.Args[0], instr.Pos())
  95  	bv := b.getValue(instr.Args[1], instr.Pos())
  96  	return b.createRuntimeCall("stringEqual", []llvm.Value{a, bv}, "")
  97  }
  98  
  99  // createMoxieLt handles __moxie_lt(a, b []byte) bool.
 100  // Emits a call to runtime.stringLess (same layout as []byte).
 101  func (b *builder) createMoxieLt(instr *ssa.CallCommon) llvm.Value {
 102  	a := b.getValue(instr.Args[0], instr.Pos())
 103  	bv := b.getValue(instr.Args[1], instr.Pos())
 104  	return b.createRuntimeCall("stringLess", []llvm.Value{a, bv}, "")
 105  }
 106  
 107  // createMoxieSecalloc handles __moxie_secalloc(n int32) []byte. The text
 108  // rewriter emits this for the `[]byte{:n, secure}` literal form. Emits a
 109  // call to runtime.SecureAlloc which mmap's a guarded arena and returns a
 110  // slice into it.
 111  func (b *builder) createMoxieSecalloc(instr *ssa.CallCommon) llvm.Value {
 112  	n := b.getValue(instr.Args[0], instr.Pos())
 113  	return b.createRuntimeCall("SecureAlloc", []llvm.Value{n}, "")
 114  }
 115