github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/loader/wrapper.go (about)

     1  /**
     2  * Copyright 2023 ByteDance Inc.
     3  *
     4  * Licensed under the Apache License, Version 2.0 (the "License");
     5  * you may not use this file except in compliance with the License.
     6  * You may obtain a copy of the License at
     7  *
     8  *     http://www.apache.org/licenses/LICENSE-2.0
     9  *
    10  * Unless required by applicable law or agreed to in writing, software
    11  * distributed under the License is distributed on an "AS IS" BASIS,
    12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  * See the License for the specific language governing permissions and
    14  * limitations under the License.
    15   */
    16  
    17  package loader
    18  
    19  import (
    20  	"reflect"
    21  	"unsafe"
    22  
    23  	"github.com/goshafaq/sonic/internal/abi"
    24  	"github.com/goshafaq/sonic/internal/rt"
    25  )
    26  
    27  var _C_Redzone = []bool{false, false, false, false}
    28  
    29  // CFunc is a function information for C func
    30  type CFunc struct {
    31  	// C function name
    32  	Name string
    33  
    34  	// entry pc relative to entire text segment
    35  	EntryOff uint32
    36  
    37  	// function text size in bytes
    38  	TextSize uint32
    39  
    40  	// maximum stack depth of the function
    41  	MaxStack uintptr
    42  
    43  	// PC->SP delta lists of the function
    44  	Pcsp [][2]uint32
    45  }
    46  
    47  // GoC is the wrapper for Go calls to C
    48  type GoC struct {
    49  	// CName is the name of corresponding C function
    50  	CName string
    51  
    52  	// CEntry points out where to store the entry address of corresponding C function.
    53  	// It won't be set if nil
    54  	CEntry *uintptr
    55  
    56  	// GoFunc is the POINTER of corresponding go stub function.
    57  	// It is used to generate Go-C ABI conversion wrapper and receive the wrapper's address
    58  	//   eg. &func(a int, b int) int
    59  	//     FOR
    60  	//     int add(int a, int b)
    61  	// It won't be set if nil
    62  	GoFunc interface{}
    63  }
    64  
    65  // WrapGoC wraps C functions and loader it into Go stubs
    66  func WrapGoC(text []byte, natives []CFunc, stubs []GoC, modulename string, filename string) {
    67  	funcs := make([]Func, len(natives))
    68  
    69  	// register C funcs
    70  	for i, f := range natives {
    71  		fn := Func{
    72  			Flag:     FuncFlag_ASM,
    73  			EntryOff: f.EntryOff,
    74  			TextSize: f.TextSize,
    75  			Name:     f.Name,
    76  		}
    77  		if len(f.Pcsp) != 0 {
    78  			fn.Pcsp = (*Pcdata)(unsafe.Pointer(&natives[i].Pcsp))
    79  		}
    80  		// NOTICE: always forbid async preempt
    81  		fn.PcUnsafePoint = &Pcdata{
    82  			{PC: f.TextSize, Val: PCDATA_UnsafePointUnsafe},
    83  		}
    84  		// NOTICE: always refer to first file
    85  		fn.Pcfile = &Pcdata{
    86  			{PC: f.TextSize, Val: 0},
    87  		}
    88  		// NOTICE: always refer to first line
    89  		fn.Pcline = &Pcdata{
    90  			{PC: f.TextSize, Val: 1},
    91  		}
    92  		// NOTICE: copystack need locals stackmap
    93  		fn.PcStackMapIndex = &Pcdata{
    94  			{PC: f.TextSize, Val: 0},
    95  		}
    96  		sm := rt.StackMapBuilder{}
    97  		sm.AddField(false)
    98  		fn.ArgsPointerMaps = sm.Build()
    99  		fn.LocalsPointerMaps = sm.Build()
   100  		funcs[i] = fn
   101  	}
   102  	rets := Load(text, funcs, modulename, []string{filename})
   103  
   104  	// got absolute entry address
   105  	native_entry := **(**uintptr)(unsafe.Pointer(&rets[0]))
   106  	// println("native_entry: ", native_entry)
   107  
   108  	wraps := make([]Func, 0, len(stubs))
   109  	wrapIds := make([]int, 0, len(stubs))
   110  	code := make([]byte, 0, len(wraps))
   111  	entryOff := uint32(0)
   112  
   113  	// register go wrappers
   114  	for i := range stubs {
   115  		for j := range natives {
   116  			if stubs[i].CName != natives[j].Name {
   117  				continue
   118  			}
   119  
   120  			// calculate corresponding C entry
   121  			pc := uintptr(native_entry + uintptr(natives[j].EntryOff))
   122  			if stubs[i].CEntry != nil {
   123  				*stubs[i].CEntry = pc
   124  			}
   125  
   126  			// no need to generate wrapper, continue next
   127  			if stubs[i].GoFunc == nil {
   128  				continue
   129  			}
   130  
   131  			// assemble wrapper codes
   132  			layout := abi.NewFunctionLayout(reflect.TypeOf(stubs[i].GoFunc).Elem())
   133  			frame := abi.NewFrame(&layout, _C_Redzone, true)
   134  			tcode := abi.CallC(pc, frame, natives[j].MaxStack)
   135  			code = append(code, tcode...)
   136  			size := uint32(len(tcode))
   137  
   138  			fn := Func{
   139  				Flag:     FuncFlag_ASM,
   140  				ArgsSize: int32(layout.ArgSize()),
   141  				EntryOff: entryOff,
   142  				TextSize: size,
   143  				Name:     stubs[i].CName + "_go",
   144  			}
   145  
   146  			// add check-stack and grow-stack texts' pcsp
   147  			fn.Pcsp = &Pcdata{
   148  				{PC: uint32(frame.StackCheckTextSize()), Val: 0},
   149  				{PC: size - uint32(frame.GrowStackTextSize()), Val: int32(frame.Size())},
   150  				{PC: size, Val: 0},
   151  			}
   152  			// NOTICE: always refer to first file
   153  			fn.Pcfile = &Pcdata{
   154  				{PC: size, Val: 0},
   155  			}
   156  			// NOTICE: always refer to first line
   157  			fn.Pcline = &Pcdata{
   158  				{PC: size, Val: 1},
   159  			}
   160  			// NOTICE: always forbid async preempt
   161  			fn.PcUnsafePoint = &Pcdata{
   162  				{PC: size, Val: PCDATA_UnsafePointUnsafe},
   163  			}
   164  
   165  			// register pointer stackmaps
   166  			fn.PcStackMapIndex = &Pcdata{
   167  				{PC: size, Val: 0},
   168  			}
   169  			fn.ArgsPointerMaps = frame.ArgPtrs()
   170  			fn.LocalsPointerMaps = frame.LocalPtrs()
   171  
   172  			entryOff += size
   173  			wraps = append(wraps, fn)
   174  			wrapIds = append(wrapIds, i)
   175  		}
   176  	}
   177  	gofuncs := Load(code, wraps, modulename+"/go", []string{filename + ".go"})
   178  
   179  	// set go func value
   180  	for i := range gofuncs {
   181  		idx := wrapIds[i]
   182  		w := rt.UnpackEface(stubs[idx].GoFunc)
   183  		*(*Function)(w.Value) = gofuncs[i]
   184  	}
   185  }