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  }