You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

419 lines
13 KiB

package interp
// This file provides a litte bit of abstraction around LLVM values.
import (
"errors"
"strconv"
"tinygo.org/x/go-llvm"
)
// A Value is a LLVM value with some extra methods attached for easier
// interpretation.
type Value interface {
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() llvm.Value // dereference a pointer
Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging
}
// A type that simply wraps a LLVM constant value.
type LocalValue struct {
Eval *Eval
Underlying llvm.Value
}
// Value implements Value by returning the constant value itself.
func (v *LocalValue) Value() llvm.Value {
return v.Underlying
}
func (v *LocalValue) Type() llvm.Type {
return v.Underlying.Type()
}
func (v *LocalValue) IsConstant() bool {
if _, ok := v.Eval.dirtyGlobals[unwrap(v.Underlying)]; ok {
return false
}
return v.Underlying.IsConstant()
}
// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() llvm.Value {
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer()
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
panic("invalid GEP")
}
global := v.Eval.getValue(v.Underlying.Operand(0))
agg := global.Load()
return llvm.ConstExtractValue(agg, indices[1:])
case llvm.BitCast:
panic("interp: load from a bitcast")
default:
panic("interp: load from a constant")
}
}
// Store stores to the underlying value if the value type is a pointer type,
// otherwise it panics.
func (v *LocalValue) Store(value llvm.Value) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
} else {
v.Underlying.SetInitializer(value)
}
return
}
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
return
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
panic("invalid GEP")
}
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg := global.Load()
agg = llvm.ConstInsertValue(agg, value, indices[1:])
global.Store(agg)
return
default:
panic("interp: store on a constant")
}
}
// GetElementPtr returns a GEP when the underlying value is of pointer type.
func (v *LocalValue) GetElementPtr(indices []uint32) (Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}, nil
}
if !v.Underlying.IsAConstantExpr().IsNil() {
switch v.Underlying.Opcode() {
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
int32Type := v.Underlying.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, indices)
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}, nil
}
}
return nil, errors.New("interp: unknown GEP")
}
// stripPointerCasts removes all const bitcasts from pointer values, if there
// are any.
func (v *LocalValue) stripPointerCasts() *LocalValue {
value := v.Underlying
for {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.BitCast:
value = value.Operand(0)
continue
}
}
return &LocalValue{
Eval: v.Eval,
Underlying: value,
}
}
}
func (v *LocalValue) String() string {
isConstant := "false"
if v.IsConstant() {
isConstant = "true"
}
return "&LocalValue{Type: " + v.Type().String() + ", IsConstant: " + isConstant + "}"
}
// getConstGEPIndices returns indices of this constant GEP, if this is a GEP
// instruction. If it is not, the behavior is undefined.
func (v *LocalValue) getConstGEPIndices() []uint32 {
indices := make([]uint32, v.Underlying.OperandsCount()-1)
for i := range indices {
operand := v.Underlying.Operand(i + 1)
indices[i] = uint32(operand.ZExtValue())
}
return indices
}
// MarkDirty marks this global as dirty, meaning that every load from and store
// to this global (from now on) must be performed at runtime.
func (v *LocalValue) MarkDirty() {
underlying := unwrap(v.Underlying)
if underlying.IsAGlobalVariable().IsNil() {
panic("trying to mark a non-global as dirty")
}
if !v.IsConstant() {
return // already dirty
}
v.Eval.dirtyGlobals[underlying] = struct{}{}
}
// MapValue implements a Go map which is created at compile time and stored as a
// global variable.
type MapValue struct {
Eval *Eval
PkgName string
Underlying llvm.Value
Keys []Value
Values []Value
KeySize int
ValueSize int
KeyType llvm.Type
ValueType llvm.Type
}
func (v *MapValue) newBucket() llvm.Value {
ctx := v.Eval.Mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
bucketType := ctx.StructType([]llvm.Type{
llvm.ArrayType(ctx.Int8Type(), 8), // tophash
i8ptrType, // next bucket
llvm.ArrayType(v.KeyType, 8), // key type
llvm.ArrayType(v.ValueType, 8), // value type
}, false)
bucketValue := llvm.ConstNull(bucketType)
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
bucket.SetInitializer(bucketValue)
bucket.SetLinkage(llvm.InternalLinkage)
bucket.SetUnnamedAddr(true)
return bucket
}
// Value returns a global variable which is a pointer to the actual hashmap.
func (v *MapValue) Value() llvm.Value {
if !v.Underlying.IsNil() {
return v.Underlying
}
ctx := v.Eval.Mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
var firstBucketGlobal llvm.Value
if len(v.Keys) == 0 {
// there are no buckets
firstBucketGlobal = llvm.ConstPointerNull(i8ptrType)
} else {
// create initial bucket
firstBucketGlobal = v.newBucket()
}
// Insert each key/value pair in the hashmap.
bucketGlobal := firstBucketGlobal
for i, key := range v.Keys {
var keyBuf []byte
llvmKey := key.Value()
llvmValue := v.Values[i].Value()
if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" {
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr)
keyBuf = getStringBytes(keyPtrVal, keyLen)
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
hash := v.hash(keyBuf)
if i%8 == 0 && i != 0 {
// Bucket is full, create a new one.
newBucketGlobal := v.newBucket()
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
newBucketPtr := llvm.ConstInBoundsGEP(newBucketGlobal, []llvm.Value{zero})
newBucketPtrCast := llvm.ConstBitCast(newBucketPtr, i8ptrType)
// insert pointer into old bucket
bucket := bucketGlobal.Initializer()
bucket = llvm.ConstInsertValue(bucket, newBucketPtrCast, []uint32{1})
bucketGlobal.SetInitializer(bucket)
// switch to next bucket
bucketGlobal = newBucketGlobal
}
tophashValue := llvm.ConstInt(ctx.Int8Type(), uint64(v.topHash(hash)), false)
bucket := bucketGlobal.Initializer()
bucket = llvm.ConstInsertValue(bucket, tophashValue, []uint32{0, uint32(i % 8)})
bucket = llvm.ConstInsertValue(bucket, llvmKey, []uint32{2, uint32(i % 8)})
bucket = llvm.ConstInsertValue(bucket, llvmValue, []uint32{3, uint32(i % 8)})
bucketGlobal.SetInitializer(bucket)
}
// Create the hashmap itself.
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
bucketPtr := llvm.ConstInBoundsGEP(firstBucketGlobal, []llvm.Value{zero})
hashmapType := v.Type()
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
llvm.ConstPointerNull(llvm.PointerType(hashmapType, 0)), // next
llvm.ConstBitCast(bucketPtr, i8ptrType), // buckets
llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.Keys)), false), // count
llvm.ConstInt(ctx.Int8Type(), uint64(v.KeySize), false), // keySize
llvm.ConstInt(ctx.Int8Type(), uint64(v.ValueSize), false), // valueSize
llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits
})
// Create a pointer to this hashmap.
hashmapPtr := llvm.AddGlobal(v.Eval.Mod, hashmap.Type(), v.PkgName+"$map")
hashmapPtr.SetInitializer(hashmap)
hashmapPtr.SetLinkage(llvm.InternalLinkage)
hashmapPtr.SetUnnamedAddr(true)
v.Underlying = llvm.ConstInBoundsGEP(hashmapPtr, []llvm.Value{zero})
return v.Underlying
}
// Type returns type runtime.hashmap, which is the actual hashmap type.
func (v *MapValue) Type() llvm.Type {
return v.Eval.Mod.GetTypeByName("runtime.hashmap")
}
func (v *MapValue) IsConstant() bool {
return true // TODO: dirty maps
}
// Load panics: maps are of reference type so cannot be dereferenced.
func (v *MapValue) Load() llvm.Value {
panic("interp: load from a map")
}
// Store panics: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) {
panic("interp: store on a map")
}
// GetElementPtr panics: maps are of reference type so their (interior)
// addresses cannot be calculated.
func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
}
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := llvm.ConstNull(keyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
}
if !keyPtr.Underlying.IsAConstantExpr().IsNil() {
if keyPtr.Underlying.Opcode() == llvm.BitCast {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
}
}
key := keyPtr.Load()
if v.KeyType.IsNil() {
v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
panic("interp: map store key type has the wrong size")
}
} else {
if key.Type() != v.KeyType {
panic("interp: map store key type is inconsistent")
}
}
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func (v *MapValue) hash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func (v *MapValue) topHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash += 1
}
return tophash
}
func (v *MapValue) String() string {
return "&MapValue{KeySize: " + strconv.Itoa(v.KeySize) + ", ValueSize: " + strconv.Itoa(v.ValueSize) + "}"
}