1// Copyright 2017 Istio Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package interpreter 16 17import ( 18 "reflect" 19 "time" 20 21 "istio.io/istio/mixer/pkg/il" 22) 23 24// Extern represents an external, native function that is callable from within the interpreter, 25// during program execution. 26type Extern struct { 27 name string 28 paramTypes []il.Type 29 returnType il.Type 30 31 v reflect.Value 32} 33 34// ExternFromFn creates a new, reflection based Extern, based on the given function. It panics if the 35// function signature is incompatible to be an extern. 36// 37// A function can be extern under the following conditions: 38// - Input parameter types are one of the supported types: string, bool, int64, float64, map[string]string. 39// - The return types can be: 40// - none (i.e. func (...) {...}) 41// - a supported type (i.e. func (...) string {...}) 42// - error (i.e. func (...) error {...}) 43// - supported type and error (i.e. func (...) string, error {...}) 44// 45func ExternFromFn(name string, fn interface{}) Extern { 46 t := reflect.TypeOf(fn) 47 if t.Kind() != reflect.Func { 48 panic("interpreter.ExternFromFn: not a function") 49 } 50 51 // Validate and calculate return types. 52 iErr := reflect.TypeOf((*error)(nil)).Elem() 53 returnType := il.Void 54 switch t.NumOut() { 55 case 0: 56 57 case 1: 58 if !t.Out(0).Implements(iErr) { 59 returnType = ilType(t.Out(0)) 60 } 61 62 case 2: 63 returnType = ilType(t.Out(0)) 64 if !t.Out(1).Implements(iErr) { 65 panic("interpreter.ExternFromFn: the second return value is not an error") 66 } 67 68 default: 69 panic("interpreter.ExternFromFn: more than two return values are not allowed") 70 } 71 72 if returnType == il.Unknown { 73 panic("interpreter.ExternFromFn: incompatible return type") 74 } 75 76 // Calculate parameter types. 77 paramTypes := make([]il.Type, t.NumIn()) 78 for i := 0; i < t.NumIn(); i++ { 79 pt := t.In(i) 80 ilt := ilType(pt) 81 if ilt == il.Unknown { 82 panic("interpreter.ExternFromFn: incompatible parameter type") 83 } 84 paramTypes[i] = ilt 85 } 86 87 v := reflect.ValueOf(fn) 88 89 return Extern{ 90 name: name, 91 paramTypes: paramTypes, 92 returnType: returnType, 93 v: v, 94 } 95} 96 97// ilType maps the Go reflected type to its IL counterpart. 98func ilType(t reflect.Type) il.Type { 99 switch t.Kind() { 100 case reflect.String: 101 return il.String 102 case reflect.Bool: 103 return il.Bool 104 case reflect.Int64: 105 if t.Name() == "Duration" { 106 return il.Duration 107 } 108 return il.Integer 109 case reflect.Float64: 110 return il.Double 111 case reflect.Slice: 112 if t.Elem().Kind() == reflect.Uint8 { 113 return il.Interface 114 } 115 case reflect.Struct: 116 switch t.Name() { 117 case "Time": 118 return il.Interface 119 case "StringMap": 120 return il.Interface 121 } 122 case reflect.Interface: 123 if t.Name() == "StringMap" { 124 return il.Interface 125 } 126 } 127 128 panic("Unmapped go type: " + t.Name() + " " + t.String() + " kind:" + t.Kind().String()) 129} 130 131// invoke calls the extern function via reflection, using the interpreter's calling convention. 132// The parameters are read from the stack and get converted to Go values and the extern function 133// gets invoked. When the call completes, the return value, if any, gets converted back to the IL 134// type and pushed on to the stack. If the extern returns an error as one of the return values, 135// then the error is checked and raised in the IL if it is not nil. 136// 137// The function returns two uint32 values in the push order (i.e. first uint32 to be pushed on to 138// the stack first). 139func (e Extern) invoke(_ *il.StringTable, heap []interface{}, hp *uint32, stack []uint32, sp uint32) (uint32, uint32, error) { 140 141 // Convert the parameters on stack to reflect.Values. 142 ins := make([]reflect.Value, len(e.paramTypes)) 143 144 // ap is the index to the beginning of the arguments. 145 ap := sp - typesStackAllocSize(e.paramTypes) 146 for i := 0; i < len(e.paramTypes); i++ { 147 148 switch e.paramTypes[i] { 149 case il.String: 150 str := heap[stack[ap]].(string) 151 ins[i] = reflect.ValueOf(str) 152 153 case il.Bool: 154 b := stack[ap] != 0 155 ins[i] = reflect.ValueOf(b) 156 157 case il.Integer: 158 iv := il.ByteCodeToInteger(stack[ap+1], stack[ap]) 159 ins[i] = reflect.ValueOf(iv) 160 161 case il.Duration: 162 iv := il.ByteCodeToInteger(stack[ap+1], stack[ap]) 163 ins[i] = reflect.ValueOf(time.Duration(iv)) 164 165 case il.Double: 166 d := il.ByteCodeToDouble(stack[ap+1], stack[ap]) 167 ins[i] = reflect.ValueOf(d) 168 169 case il.Interface: 170 r := heap[stack[ap]] 171 ins[i] = reflect.ValueOf(r) 172 173 default: 174 panic("interpreter.Extern.invoke: unrecognized parameter type") 175 } 176 177 ap += typeStackAllocSize(e.paramTypes[i]) 178 } 179 180 // Perform the actual invocation through reflection. 181 outs := e.v.Call(ins) 182 183 // Convert the output values back to IL. 184 var rv reflect.Value 185 switch len(outs) { 186 case 1: 187 if e.returnType != il.Void { 188 rv = outs[0] 189 break 190 } 191 // If there is 1 return value in Go-space, but we expect the return type of the function to be 192 // Void in IL, then interpret the value as error. 193 if i := outs[0].Interface(); i != nil { 194 return 0, 0, i.(error) 195 } 196 case 2: 197 // If there are 2 return values in Go-space, interpret the first one as actual return value, 198 // and the second one as error. 199 rv = outs[0] 200 if i := outs[1].Interface(); i != nil { 201 return 0, 0, i.(error) 202 } 203 } 204 205 // Map the return value back to IL 206 switch e.returnType { 207 case il.String: 208 str := rv.String() 209 heap[*hp] = str 210 *hp++ 211 return *hp - 1, 0, nil 212 213 case il.Bool: 214 if rv.Bool() { 215 return 1, 0, nil 216 } 217 return 0, 0, nil 218 219 case il.Integer, il.Duration: 220 i := rv.Int() 221 o1, o2 := il.IntegerToByteCode(i) 222 return o2, o1, nil 223 224 case il.Double: 225 d := rv.Float() 226 o1, o2 := il.DoubleToByteCode(d) 227 return o2, o1, nil 228 229 case il.Interface: 230 // TODO(ozben): We should single-instance the values, as they are prone to mutation. 231 r := rv.Interface() 232 heap[*hp] = r 233 *hp++ 234 return *hp - 1, 0, nil 235 236 case il.Void: 237 return 0, 0, nil 238 239 default: 240 panic("interpreter.Extern.invoke: unrecognized return type") 241 } 242} 243