aboutsummaryrefslogtreecommitdiffhomepage
path: root/interp/values.go
blob: a6e48c358f9ca47727ebc13f9bb992da7593948f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
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) + "}"
}