github.com/aykevl/tinygo@v0.5.0/interp/values.go (about) 1 package interp 2 3 // This file provides a litte bit of abstraction around LLVM values. 4 5 import ( 6 "strconv" 7 8 "tinygo.org/x/go-llvm" 9 ) 10 11 // A Value is a LLVM value with some extra methods attached for easier 12 // interpretation. 13 type Value interface { 14 Value() llvm.Value // returns a LLVM value 15 Type() llvm.Type // equal to Value().Type() 16 IsConstant() bool // returns true if this value is a constant value 17 Load() llvm.Value // dereference a pointer 18 Store(llvm.Value) // store to a pointer 19 GetElementPtr([]uint32) Value // returns an interior pointer 20 String() string // string representation, for debugging 21 } 22 23 // A type that simply wraps a LLVM constant value. 24 type LocalValue struct { 25 Eval *Eval 26 Underlying llvm.Value 27 } 28 29 // Value implements Value by returning the constant value itself. 30 func (v *LocalValue) Value() llvm.Value { 31 return v.Underlying 32 } 33 34 func (v *LocalValue) Type() llvm.Type { 35 return v.Underlying.Type() 36 } 37 38 func (v *LocalValue) IsConstant() bool { 39 if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok { 40 return false 41 } 42 return v.Underlying.IsConstant() 43 } 44 45 // Load loads a constant value if this is a constant pointer. 46 func (v *LocalValue) Load() llvm.Value { 47 if !v.Underlying.IsAGlobalVariable().IsNil() { 48 return v.Underlying.Initializer() 49 } 50 switch v.Underlying.Opcode() { 51 case llvm.GetElementPtr: 52 indices := v.getConstGEPIndices() 53 if indices[0] != 0 { 54 panic("invalid GEP") 55 } 56 global := v.Eval.getValue(v.Underlying.Operand(0)) 57 agg := global.Load() 58 return llvm.ConstExtractValue(agg, indices[1:]) 59 case llvm.BitCast: 60 panic("interp: load from a bitcast") 61 default: 62 panic("interp: load from a constant") 63 } 64 } 65 66 // Store stores to the underlying value if the value type is a pointer type, 67 // otherwise it panics. 68 func (v *LocalValue) Store(value llvm.Value) { 69 if !v.Underlying.IsAGlobalVariable().IsNil() { 70 if !value.IsConstant() { 71 v.MarkDirty() 72 v.Eval.builder.CreateStore(value, v.Underlying) 73 } else { 74 v.Underlying.SetInitializer(value) 75 } 76 return 77 } 78 switch v.Underlying.Opcode() { 79 case llvm.GetElementPtr: 80 indices := v.getConstGEPIndices() 81 if indices[0] != 0 { 82 panic("invalid GEP") 83 } 84 global := &LocalValue{v.Eval, v.Underlying.Operand(0)} 85 agg := global.Load() 86 agg = llvm.ConstInsertValue(agg, value, indices[1:]) 87 global.Store(agg) 88 return 89 default: 90 panic("interp: store on a constant") 91 } 92 } 93 94 // GetElementPtr returns a GEP when the underlying value is of pointer type. 95 func (v *LocalValue) GetElementPtr(indices []uint32) Value { 96 if !v.Underlying.IsAGlobalVariable().IsNil() { 97 int32Type := v.Underlying.Type().Context().Int32Type() 98 gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices)) 99 return &LocalValue{v.Eval, gep} 100 } 101 switch v.Underlying.Opcode() { 102 case llvm.GetElementPtr, llvm.IntToPtr: 103 int32Type := v.Underlying.Type().Context().Int32Type() 104 llvmIndices := getLLVMIndices(int32Type, indices) 105 return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)} 106 default: 107 panic("interp: GEP on a constant") 108 } 109 } 110 111 func (v *LocalValue) String() string { 112 isConstant := "false" 113 if v.IsConstant() { 114 isConstant = "true" 115 } 116 return "&LocalValue{Type: " + v.Type().String() + ", IsConstant: " + isConstant + "}" 117 } 118 119 // getConstGEPIndices returns indices of this constant GEP, if this is a GEP 120 // instruction. If it is not, the behavior is undefined. 121 func (v *LocalValue) getConstGEPIndices() []uint32 { 122 indices := make([]uint32, v.Underlying.OperandsCount()-1) 123 for i := range indices { 124 operand := v.Underlying.Operand(i + 1) 125 indices[i] = uint32(operand.ZExtValue()) 126 } 127 return indices 128 } 129 130 // MarkDirty marks this global as dirty, meaning that every load from and store 131 // to this global (from now on) must be performed at runtime. 132 func (v *LocalValue) MarkDirty() { 133 if v.Underlying.IsAGlobalVariable().IsNil() { 134 panic("trying to mark a non-global as dirty") 135 } 136 if !v.IsConstant() { 137 return // already dirty 138 } 139 v.Eval.dirtyGlobals[v.Underlying] = struct{}{} 140 } 141 142 // MapValue implements a Go map which is created at compile time and stored as a 143 // global variable. 144 type MapValue struct { 145 Eval *Eval 146 PkgName string 147 Underlying llvm.Value 148 Keys []Value 149 Values []Value 150 KeySize int 151 ValueSize int 152 KeyType llvm.Type 153 ValueType llvm.Type 154 } 155 156 func (v *MapValue) newBucket() llvm.Value { 157 ctx := v.Eval.Mod.Context() 158 i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) 159 bucketType := ctx.StructType([]llvm.Type{ 160 llvm.ArrayType(ctx.Int8Type(), 8), // tophash 161 i8ptrType, // next bucket 162 llvm.ArrayType(v.KeyType, 8), // key type 163 llvm.ArrayType(v.ValueType, 8), // value type 164 }, false) 165 bucketValue := getZeroValue(bucketType) 166 bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket") 167 bucket.SetInitializer(bucketValue) 168 bucket.SetLinkage(llvm.InternalLinkage) 169 bucket.SetUnnamedAddr(true) 170 return bucket 171 } 172 173 // Value returns a global variable which is a pointer to the actual hashmap. 174 func (v *MapValue) Value() llvm.Value { 175 if !v.Underlying.IsNil() { 176 return v.Underlying 177 } 178 179 ctx := v.Eval.Mod.Context() 180 i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) 181 182 var firstBucketGlobal llvm.Value 183 if len(v.Keys) == 0 { 184 // there are no buckets 185 firstBucketGlobal = llvm.ConstPointerNull(i8ptrType) 186 } else { 187 // create initial bucket 188 firstBucketGlobal = v.newBucket() 189 } 190 191 // Insert each key/value pair in the hashmap. 192 bucketGlobal := firstBucketGlobal 193 for i, key := range v.Keys { 194 var keyBuf []byte 195 llvmKey := key.Value() 196 llvmValue := v.Values[i].Value() 197 if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" { 198 keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0}) 199 keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1}) 200 keyPtrVal := v.Eval.getValue(keyPtr) 201 keyBuf = getStringBytes(keyPtrVal, keyLen) 202 } else if key.Type().TypeKind() == llvm.IntegerTypeKind { 203 keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type())) 204 n := key.Value().ZExtValue() 205 for i := range keyBuf { 206 keyBuf[i] = byte(n) 207 n >>= 8 208 } 209 } else if key.Type().TypeKind() == llvm.ArrayTypeKind && 210 key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind && 211 key.Type().ElementType().IntTypeWidth() == 8 { 212 keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type())) 213 for i := range keyBuf { 214 keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue()) 215 } 216 } else { 217 panic("interp: map key type not implemented: " + key.Type().String()) 218 } 219 hash := v.hash(keyBuf) 220 221 if i%8 == 0 && i != 0 { 222 // Bucket is full, create a new one. 223 newBucketGlobal := v.newBucket() 224 zero := llvm.ConstInt(ctx.Int32Type(), 0, false) 225 newBucketPtr := llvm.ConstInBoundsGEP(newBucketGlobal, []llvm.Value{zero}) 226 newBucketPtrCast := llvm.ConstBitCast(newBucketPtr, i8ptrType) 227 // insert pointer into old bucket 228 bucket := bucketGlobal.Initializer() 229 bucket = llvm.ConstInsertValue(bucket, newBucketPtrCast, []uint32{1}) 230 bucketGlobal.SetInitializer(bucket) 231 // switch to next bucket 232 bucketGlobal = newBucketGlobal 233 } 234 235 tophashValue := llvm.ConstInt(ctx.Int8Type(), uint64(v.topHash(hash)), false) 236 bucket := bucketGlobal.Initializer() 237 bucket = llvm.ConstInsertValue(bucket, tophashValue, []uint32{0, uint32(i % 8)}) 238 bucket = llvm.ConstInsertValue(bucket, llvmKey, []uint32{2, uint32(i % 8)}) 239 bucket = llvm.ConstInsertValue(bucket, llvmValue, []uint32{3, uint32(i % 8)}) 240 bucketGlobal.SetInitializer(bucket) 241 } 242 243 // Create the hashmap itself. 244 zero := llvm.ConstInt(ctx.Int32Type(), 0, false) 245 bucketPtr := llvm.ConstInBoundsGEP(firstBucketGlobal, []llvm.Value{zero}) 246 hashmapType := v.Type() 247 hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{ 248 llvm.ConstPointerNull(llvm.PointerType(hashmapType, 0)), // next 249 llvm.ConstBitCast(bucketPtr, i8ptrType), // buckets 250 llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.Keys)), false), // count 251 llvm.ConstInt(ctx.Int8Type(), uint64(v.KeySize), false), // keySize 252 llvm.ConstInt(ctx.Int8Type(), uint64(v.ValueSize), false), // valueSize 253 llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits 254 }) 255 256 // Create a pointer to this hashmap. 257 hashmapPtr := llvm.AddGlobal(v.Eval.Mod, hashmap.Type(), v.PkgName+"$map") 258 hashmapPtr.SetInitializer(hashmap) 259 hashmapPtr.SetLinkage(llvm.InternalLinkage) 260 hashmapPtr.SetUnnamedAddr(true) 261 v.Underlying = llvm.ConstInBoundsGEP(hashmapPtr, []llvm.Value{zero}) 262 return v.Underlying 263 } 264 265 // Type returns type runtime.hashmap, which is the actual hashmap type. 266 func (v *MapValue) Type() llvm.Type { 267 return v.Eval.Mod.GetTypeByName("runtime.hashmap") 268 } 269 270 func (v *MapValue) IsConstant() bool { 271 return true // TODO: dirty maps 272 } 273 274 // Load panics: maps are of reference type so cannot be dereferenced. 275 func (v *MapValue) Load() llvm.Value { 276 panic("interp: load from a map") 277 } 278 279 // Store panics: maps are of reference type so cannot be stored to. 280 func (v *MapValue) Store(value llvm.Value) { 281 panic("interp: store on a map") 282 } 283 284 // GetElementPtr panics: maps are of reference type so their (interior) 285 // addresses cannot be calculated. 286 func (v *MapValue) GetElementPtr(indices []uint32) Value { 287 panic("interp: GEP on a map") 288 } 289 290 // PutString does a map assign operation, assuming that the map is of type 291 // map[string]T. 292 func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) { 293 if !v.Underlying.IsNil() { 294 panic("map already created") 295 } 296 297 if valPtr.Underlying.Opcode() == llvm.BitCast { 298 valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} 299 } 300 value := valPtr.Load() 301 if v.ValueType.IsNil() { 302 v.ValueType = value.Type() 303 if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { 304 panic("interp: map store value type has the wrong size") 305 } 306 } else { 307 if value.Type() != v.ValueType { 308 panic("interp: map store value type is inconsistent") 309 } 310 } 311 312 keyType := v.Eval.Mod.GetTypeByName("runtime._string") 313 v.KeyType = keyType 314 key := getZeroValue(keyType) 315 key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0}) 316 key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1}) 317 318 // TODO: avoid duplicate keys 319 v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) 320 v.Values = append(v.Values, &LocalValue{v.Eval, value}) 321 } 322 323 // PutBinary does a map assign operation. 324 func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) { 325 if !v.Underlying.IsNil() { 326 panic("map already created") 327 } 328 329 if valPtr.Underlying.Opcode() == llvm.BitCast { 330 valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} 331 } 332 value := valPtr.Load() 333 if v.ValueType.IsNil() { 334 v.ValueType = value.Type() 335 if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { 336 panic("interp: map store value type has the wrong size") 337 } 338 } else { 339 if value.Type() != v.ValueType { 340 panic("interp: map store value type is inconsistent") 341 } 342 } 343 344 if keyPtr.Underlying.Opcode() == llvm.BitCast { 345 keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)} 346 } else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr { 347 keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)} 348 } 349 key := keyPtr.Load() 350 if v.KeyType.IsNil() { 351 v.KeyType = key.Type() 352 if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize { 353 panic("interp: map store key type has the wrong size") 354 } 355 } else { 356 if key.Type() != v.KeyType { 357 panic("interp: map store key type is inconsistent") 358 } 359 } 360 361 // TODO: avoid duplicate keys 362 v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) 363 v.Values = append(v.Values, &LocalValue{v.Eval, value}) 364 } 365 366 // Get FNV-1a hash of this string. 367 // 368 // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash 369 func (v *MapValue) hash(data []byte) uint32 { 370 var result uint32 = 2166136261 // FNV offset basis 371 for _, c := range data { 372 result ^= uint32(c) 373 result *= 16777619 // FNV prime 374 } 375 return result 376 } 377 378 // Get the topmost 8 bits of the hash, without using a special value (like 0). 379 func (v *MapValue) topHash(hash uint32) uint8 { 380 tophash := uint8(hash >> 24) 381 if tophash < 1 { 382 // 0 means empty slot, so make it bigger. 383 tophash += 1 384 } 385 return tophash 386 } 387 388 func (v *MapValue) String() string { 389 return "&MapValue{KeySize: " + strconv.Itoa(v.KeySize) + ", ValueSize: " + strconv.Itoa(v.ValueSize) + "}" 390 }