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