aboutsummaryrefslogtreecommitdiffhomepage
path: root/compiler/wordpack.go
blob: 513970a1e79d56885d6470f87d506d7067c2d0ee (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
package compiler

// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.

import (
	"tinygo.org/x/go-llvm"
)

// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
	valueTypes := make([]llvm.Type, len(values))
	for i, value := range values {
		valueTypes[i] = value.Type()
	}
	packedType := c.ctx.StructType(valueTypes, false)

	// Allocate memory for the packed data.
	var packedAlloc, packedHeapAlloc llvm.Value
	size := c.targetData.TypeAllocSize(packedType)
	if size == 0 {
		return llvm.ConstPointerNull(c.i8ptrType)
	} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
		return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
	} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
		// Packed data fits in a pointer, so store it directly inside the
		// pointer.
		if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
			// Try to keep this cast in SSA form.
			return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
		}
		// Because packedType is a struct and we have to cast it to a *i8, store
		// it in an alloca first for bitcasting (store+bitcast+load).
		packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
	} else {
		// Packed data is bigger than a pointer, so allocate it on the heap.
		sizeValue := llvm.ConstInt(c.uintptrType, size, false)
		packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
		if c.needsStackObjects() {
			c.trackPointer(packedHeapAlloc)
		}
		packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
	}
	// Store all values in the alloca or heap pointer.
	for i, value := range values {
		indices := []llvm.Value{
			llvm.ConstInt(c.ctx.Int32Type(), 0, false),
			llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
		}
		gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
		c.builder.CreateStore(value, gep)
	}

	if packedHeapAlloc.IsNil() {
		// Load value (as *i8) from the alloca.
		packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
		result := c.builder.CreateLoad(packedAlloc, "")
		packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
		packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
		c.emitLifetimeEnd(packedPtr, packedSize)
		return result
	} else {
		// Get the original heap allocation pointer, which already is an *i8.
		return packedHeapAlloc
	}
}

// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
	packedType := c.ctx.StructType(valueTypes, false)

	// Get a correctly-typed pointer to the packed data.
	var packedAlloc, packedRawAlloc llvm.Value
	size := c.targetData.TypeAllocSize(packedType)
	if size == 0 {
		// No data to unpack.
	} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
		// A single pointer is always stored directly.
		return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
	} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
		// Packed data stored directly in pointer.
		if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
			// Keep this cast in SSA form.
			return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
		}
		// Fallback: load it using an alloca.
		packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
		packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
		c.builder.CreateStore(packedRawValue, packedRawAlloc)
		packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
	} else {
		// Packed data stored on the heap. Bitcast the passed-in pointer to the
		// correct pointer type.
		packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
	}
	// Load each value from the packed data.
	values := make([]llvm.Value, len(valueTypes))
	for i, valueType := range valueTypes {
		if c.targetData.TypeAllocSize(valueType) == 0 {
			// This value has length zero, so there's nothing to load.
			values[i] = llvm.ConstNull(valueType)
			continue
		}
		indices := []llvm.Value{
			llvm.ConstInt(c.ctx.Int32Type(), 0, false),
			llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
		}
		gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
		values[i] = c.builder.CreateLoad(gep, "")
	}
	if !packedRawAlloc.IsNil() {
		allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
		allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
		c.emitLifetimeEnd(allocPtr, allocSize)
	}
	return values
}