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.

1300 lines
39 KiB

package interp
// This file implements memory as used by interp in a reversible way.
// Each new function call creates a new layer which is merged in the parent on
// successful return and is thrown away when the function couldn't complete (in
// which case the function call is done at runtime).
// Memory is not typed, except that there is a difference between pointer and
// non-pointer data. A pointer always points to an object. This implies:
// * Nil pointers are zero, and are not considered a pointer.
// * Pointers for memory-mapped I/O point to numeric pointer values, and are
// thus not considered pointers but regular values. Dereferencing them cannot be
// done in interp and results in a revert.
//
// Right now the memory is assumed to be little endian. This will need an update
// for big endian arcitectures, if TinyGo ever adds support for one.
import (
"encoding/binary"
"errors"
"math"
"math/big"
"strconv"
"strings"
"tinygo.org/x/go-llvm"
)
// An object is a memory buffer that may be an already existing global or a
// global created with runtime.alloc or the alloca instruction. If llvmGlobal is
// set, that's the global for this object, otherwise it needs to be created (if
// it is still reachable when the package initializer returns). The
// llvmLayoutType is not necessarily a complete type: it may need to be
// repeated (for example, for a slice value).
//
// Objects are copied in a memory view when they are stored to, to provide the
// ability to roll back interpreting a function.
type object struct {
llvmGlobal llvm.Value
llvmType llvm.Type // must match llvmGlobal.Type() if both are set, may be unset if llvmGlobal is set
llvmLayoutType llvm.Type // LLVM type based on runtime.alloc layout parameter, if available
globalName string // name, if not yet created (not guaranteed to be the final name)
buffer value // buffer with value as given by interp, nil if external
size uint32 // must match buffer.len(), if available
constant bool // true if this is a constant global
marked uint8 // 0 means unmarked, 1 means external read, 2 means external write
}
// clone() returns a cloned version of this object, for when an object needs to
// be written to for example.
func (obj object) clone() object {
if obj.buffer != nil {
obj.buffer = obj.buffer.clone()
}
return obj
}
// A memoryView is bound to a function activation. Loads are done from this view
// or a parent view (up to the *runner if it isn't included in a view). Stores
// copy the object to the current view.
//
// For details, see the README in the package.
type memoryView struct {
r *runner
parent *memoryView
objects map[uint32]object
// These instructions were added to runtime.initAll while interpreting a
// function. They are stored here in a list so they can be removed if the
// execution of the function needs to be rolled back.
instructions []llvm.Value
}
// extend integrates the changes done by the sub-memoryView into this memory
// view. This happens when a function is successfully interpreted and returns to
// the parent, in which case all changed objects should be included in this
// memory view.
func (mv *memoryView) extend(sub memoryView) {
if mv.objects == nil && len(sub.objects) != 0 {
mv.objects = make(map[uint32]object)
}
for key, value := range sub.objects {
mv.objects[key] = value
}
mv.instructions = append(mv.instructions, sub.instructions...)
}
// revert undoes changes done in this memory view: it removes all instructions
// created in this memoryView. Do not reuse this memoryView.
func (mv *memoryView) revert() {
// Erase instructions in reverse order.
for i := len(mv.instructions) - 1; i >= 0; i-- {
llvmInst := mv.instructions[i]
if llvmInst.IsAInstruction().IsNil() {
// The IR builder will try to create constant versions of
// instructions whenever possible. If it does this, it's not an
// instruction and thus shouldn't be removed.
continue
}
llvmInst.EraseFromParentAsInstruction()
}
}
// markExternalLoad marks the given LLVM value as having an external read. That
// means that the interpreter can still read from it, but cannot write to it as
// that would mean the external read (done at runtime) reads from a state that
// would not exist had the whole initialization been done at runtime.
func (mv *memoryView) markExternalLoad(llvmValue llvm.Value) {
mv.markExternal(llvmValue, 1)
}
// markExternalStore marks the given LLVM value as having an external write.
// This means that the interpreter can no longer read from it or write to it, as
// that would happen in a different order than if all initialization were
// happening at runtime.
func (mv *memoryView) markExternalStore(llvmValue llvm.Value) {
mv.markExternal(llvmValue, 2)
}
// markExternal is a helper for markExternalLoad and markExternalStore, and
// should not be called directly.
func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) {
if llvmValue.IsUndef() || llvmValue.IsNull() {
// Null and undef definitely don't contain (valid) pointers.
return
}
if !llvmValue.IsAInstruction().IsNil() || !llvmValue.IsAArgument().IsNil() {
// These are considered external by default, there is nothing to mark.
return
}
if !llvmValue.IsAGlobalValue().IsNil() {
objectIndex := mv.r.getValue(llvmValue).(pointerValue).index()
obj := mv.get(objectIndex)
if obj.marked < mark {
obj = obj.clone()
obj.marked = mark
if mv.objects == nil {
mv.objects = make(map[uint32]object)
}
mv.objects[objectIndex] = obj
if !llvmValue.IsAGlobalVariable().IsNil() {
initializer := llvmValue.Initializer()
if !initializer.IsNil() {
// Using mark '2' (which means read/write access) because
// even from an object that is only read from, the resulting
// loaded pointer can be written to.
mv.markExternal(initializer, 2)
}
} else {
// This is a function. Go through all instructions and mark all
// objects in there.
for bb := llvmValue.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
opcode := inst.InstructionOpcode()
if opcode == llvm.Call {
calledValue := inst.CalledValue()
if !calledValue.IsAFunction().IsNil() {
functionName := calledValue.Name()
if functionName == "llvm.dbg.value" || strings.HasPrefix(functionName, "llvm.lifetime.") {
continue
}
}
}
if opcode == llvm.Br || opcode == llvm.Switch {
// These don't affect memory. Skipped here because
// they also have a label as operand.
continue
}
numOperands := inst.OperandsCount()
for i := 0; i < numOperands; i++ {
// Using mark '2' (which means read/write access)
// because this might be a store instruction.
mv.markExternal(inst.Operand(i), 2)
}
}
}
}
}
} else if !llvmValue.IsAConstantExpr().IsNil() {
switch llvmValue.Opcode() {
case llvm.IntToPtr, llvm.PtrToInt, llvm.BitCast, llvm.GetElementPtr:
mv.markExternal(llvmValue.Operand(0), mark)
default:
panic("interp: unknown constant expression")
}
} else if !llvmValue.IsAInlineAsm().IsNil() {
// Inline assembly can modify globals but only exported globals. Let's
// hope the author knows what they're doing.
} else {
llvmType := llvmValue.Type()
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
// Nothing to do here. Integers and floats aren't pointers so don't
// need any marking.
case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount()
for i := 0; i < numElements; i++ {
element := llvm.ConstExtractValue(llvmValue, []uint32{uint32(i)})
mv.markExternal(element, mark)
}
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
for i := 0; i < numElements; i++ {
element := llvm.ConstExtractValue(llvmValue, []uint32{uint32(i)})
mv.markExternal(element, mark)
}
default:
panic("interp: unknown type kind in markExternalValue")
}
}
}
// hasExternalLoadOrStore returns true if this object has an external load or
// store. If this has happened, it is not possible for the interpreter to load
// from the object or store to it without affecting the behavior of the program.
func (mv *memoryView) hasExternalLoadOrStore(v pointerValue) bool {
obj := mv.get(v.index())
return obj.marked >= 1
}
// hasExternalStore returns true if this object has an external store. If this
// is true, stores to this object are no longer allowed by the interpreter.
// It returns false if it only has an external load, in which case it is still
// possible for the interpreter to read from the object.
func (mv *memoryView) hasExternalStore(v pointerValue) bool {
obj := mv.get(v.index())
return obj.marked >= 2 && !obj.constant
}
// get returns an object that can only be read from, as it may return an object
// of a parent view.
func (mv *memoryView) get(index uint32) object {
if obj, ok := mv.objects[index]; ok {
return obj
}
if mv.parent != nil {
return mv.parent.get(index)
}
return mv.r.objects[index]
}
// getWritable returns an object that can be written to.
func (mv *memoryView) getWritable(index uint32) object {
if obj, ok := mv.objects[index]; ok {
// Object is already in the current memory view, so can be modified.
return obj
}
// Object is not currently in this view. Get it, and clone it for use.
obj := mv.get(index).clone()
mv.r.objects[index] = obj
return obj
}
// Replace the object (indicated with index) with the given object. This put is
// only done at the current memory view, so that if this memory view is reverted
// the object is not changed.
func (mv *memoryView) put(index uint32, obj object) {
if mv.objects == nil {
mv.objects = make(map[uint32]object)
}
if checks && mv.get(index).buffer == nil {
panic("writing to external object")
}
if checks && mv.get(index).buffer.len(mv.r) != obj.buffer.len(mv.r) {
panic("put() with a differently-sized object")
}
if checks && obj.constant {
panic("interp: store to a constant")
}
mv.objects[index] = obj
}
// Load the value behind the given pointer. Returns nil if the pointer points to
// an external global.
func (mv *memoryView) load(p pointerValue, size uint32) value {
if checks && mv.hasExternalStore(p) {
panic("interp: load from object with external store")
}
obj := mv.get(p.index())
if obj.buffer == nil {
// External global, return nil.
return nil
}
if p.offset() == 0 && size == obj.size {
return obj.buffer.clone()
}
if checks && p.offset()+size > obj.size {
panic("interp: load out of bounds")
}
v := obj.buffer.asRawValue(mv.r)
loadedValue := rawValue{
buf: v.buf[p.offset() : p.offset()+size],
}
return loadedValue
}
// Store to the value behind the given pointer. This overwrites the value in the
// memory view, so that the changed value is discarded when the memory view is
// reverted. Returns true on success, false if the object to store to is
// external.
func (mv *memoryView) store(v value, p pointerValue) bool {
if checks && mv.hasExternalLoadOrStore(p) {
panic("interp: store to object with external load/store")
}
obj := mv.get(p.index())
if obj.buffer == nil {
// External global, return false (for a failure).
return false
}
if checks && p.offset()+v.len(mv.r) > obj.size {
panic("interp: store out of bounds")
}
if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) {
obj.buffer = v
} else {
obj = obj.clone()
buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer
v := v.asRawValue(mv.r)
for i := uint32(0); i < v.len(mv.r); i++ {
buffer.buf[p.offset()+i] = v.buf[i]
}
}
mv.put(p.index(), obj)
return true // success
}
// value is some sort of value, comparable to a LLVM constant. It can be
// implemented in various ways for efficiency, but the fallback value (that all
// implementations can be converted to except for localValue) is rawValue.
type value interface {
// len returns the length in bytes.
len(r *runner) uint32
clone() value
asPointer(*runner) (pointerValue, error)
asRawValue(*runner) rawValue
Uint() uint64
Int() int64
toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error)
String() string
}
// literalValue contains simple integer values that don't need to be stored in a
// buffer.
type literalValue struct {
value interface{}
}
func (v literalValue) len(r *runner) uint32 {
switch v.value.(type) {
case uint64:
return 8
case uint32:
return 4
case uint16:
return 2
case uint8:
return 1
default:
panic("unknown value type")
}
}
func (v literalValue) String() string {
return strconv.FormatInt(v.Int(), 10)
}
func (v literalValue) clone() value {
return v
}
func (v literalValue) asPointer(r *runner) (pointerValue, error) {
return pointerValue{}, errIntegerAsPointer
}
func (v literalValue) asRawValue(r *runner) rawValue {
var buf []byte
switch value := v.value.(type) {
case uint64:
buf = make([]byte, 8)
binary.LittleEndian.PutUint64(buf, value)
case uint32:
buf = make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(value))
case uint16:
buf = make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(value))
case uint8:
buf = []byte{uint8(value)}
default:
panic("unknown value type")
}
raw := newRawValue(uint32(len(buf)))
for i, b := range buf {
raw.buf[i] = uint64(b)
}
return raw
}
func (v literalValue) Uint() uint64 {
switch value := v.value.(type) {
case uint64:
return value
case uint32:
return uint64(value)
case uint16:
return uint64(value)
case uint8:
return uint64(value)
default:
panic("inpterp: unknown literal type")
}
}
func (v literalValue) Int() int64 {
switch value := v.value.(type) {
case uint64:
return int64(value)
case uint32:
return int64(int32(value))
case uint16:
return int64(int16(value))
case uint8:
return int64(int8(value))
default:
panic("inpterp: unknown literal type")
}
}
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
switch value := v.value.(type) {
case uint64:
return llvm.ConstInt(llvmType, value, false), nil
case uint32:
return llvm.ConstInt(llvmType, uint64(value), false), nil
case uint16:
return llvm.ConstInt(llvmType, uint64(value), false), nil
case uint8:
return llvm.ConstInt(llvmType, uint64(value), false), nil
default:
return llvm.Value{}, errors.New("interp: unknown literal type")
}
case llvm.DoubleTypeKind:
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64))), nil
case llvm.FloatTypeKind:
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32)))), nil
default:
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
}
}
// pointerValue contains a single pointer, with an offset into the underlying
// object.
type pointerValue struct {
pointer uint64 // low 32 bits are offset, high 32 bits are index
}
func newPointerValue(r *runner, index, offset int) pointerValue {
return pointerValue{
pointer: uint64(index)<<32 | uint64(offset),
}
}
func (v pointerValue) index() uint32 {
return uint32(v.pointer >> 32)
}
func (v pointerValue) offset() uint32 {
return uint32(v.pointer)
}
// addOffset essentially does a GEP operation (pointer arithmetic): it adds the
// offset to the pointer. It also checks that the offset doesn't overflow the
// maximum offset size (which is 4GB).
func (v pointerValue) addOffset(offset uint32) pointerValue {
result := pointerValue{v.pointer + uint64(offset)}
if checks && v.index() != result.index() {
panic("interp: offset out of range")
}
return result
}
func (v pointerValue) len(r *runner) uint32 {
return r.pointerSize
}
func (v pointerValue) String() string {
name := strconv.Itoa(int(v.index()))
if v.offset() == 0 {
return "<" + name + ">"
}
return "<" + name + "+" + strconv.Itoa(int(v.offset())) + ">"
}
func (v pointerValue) clone() value {
return v
}
func (v pointerValue) asPointer(r *runner) (pointerValue, error) {
return v, nil
}
func (v pointerValue) asRawValue(r *runner) rawValue {
rv := newRawValue(r.pointerSize)
for i := range rv.buf {
rv.buf[i] = v.pointer
}
return rv
}
func (v pointerValue) Uint() uint64 {
panic("cannot convert pointer to integer")
}
func (v pointerValue) Int() int64 {
panic("cannot convert pointer to integer")
}
func (v pointerValue) equal(rhs pointerValue) bool {
return v.pointer == rhs.pointer
}
func (v pointerValue) llvmValue(mem *memoryView) llvm.Value {
return mem.get(v.index()).llvmGlobal
}
// toLLVMValue returns the LLVM value for this pointer, which may be a GEP or
// bitcast. The llvm.Type parameter is optional, if omitted the pointer type may
// be different than expected.
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
// If a particular LLVM type is requested, cast to it.
if !llvmType.IsNil() && llvmType.TypeKind() != llvm.PointerTypeKind {
// The LLVM value has (or should have) the same bytes once compiled, but
// does not have the right LLVM type. This can happen for example when
// storing to a struct with a single pointer field: this pointer may
// then become the value even though the pointer should be wrapped in a
// struct.
// This can be worked around by simply converting to a raw value,
// rawValue knows how to create such structs.
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
}
// Obtain the llvmValue, creating it if it doesn't exist yet.
llvmValue := v.llvmValue(mem)
if llvmValue.IsNil() {
// The global does not yet exist. Probably this is the result of a
// runtime.alloc.
// First allocate a new global for this object.
obj := mem.get(v.index())
if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() {
// Create an initializer without knowing the global type.
// This is probably the result of a runtime.alloc call.
initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
if err != nil {
return llvm.Value{}, err
}
globalType := initializer.Type()
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
llvmValue.SetInitializer(initializer)
llvmValue.SetAlignment(mem.r.maxAlign)
obj.llvmGlobal = llvmValue
mem.put(v.index(), obj)
} else {
// The global type is known, or at least its structure.
var globalType llvm.Type
if !obj.llvmType.IsNil() {
// The exact type is known.
globalType = obj.llvmType.ElementType()
} else { // !obj.llvmLayoutType.IsNil()
// The exact type isn't known, but the object layout is known.
globalType = obj.llvmLayoutType
// The layout may not span the full size of the global because
// of repetition. One example would be make([]string, 5) which
// would be 10 words in size but the layout would only be two
// words (for the string type).
typeSize := mem.r.targetData.TypeAllocSize(globalType)
if typeSize != uint64(obj.size) {
globalType = llvm.ArrayType(globalType, int(uint64(obj.size)/typeSize))
}
}
if checks && mem.r.targetData.TypeAllocSize(globalType) != uint64(obj.size) {
panic("size of the globalType isn't the same as the object size")
}
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
obj.llvmGlobal = llvmValue
mem.put(v.index(), obj)
// Set the initializer for the global. Do this after creation to avoid
// infinite recursion between creating the global and creating the
// contents of the global (if the global contains itself).
initializer, err := obj.buffer.toLLVMValue(globalType, mem)
if err != nil {
return llvm.Value{}, err
}
if checks && initializer.Type() != globalType {
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
}
llvmValue.SetInitializer(initializer)
if obj.llvmType.IsNil() {
// The exact type isn't known (only the layout), so use the
// alignment that would normally be expected from runtime.alloc.
llvmValue.SetAlignment(mem.r.maxAlign)
}
}
// It should be included in r.globals because otherwise markExternal
// would consider it a new global (and would fail to mark this global as
// having an externa load/store).
mem.r.globals[llvmValue] = int(v.index())
llvmValue.SetLinkage(llvm.InternalLinkage)
}
if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the
// pointer.
// Cast to an i8* first (if needed) for easy indexing.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
llvmValue = llvm.ConstInBoundsGEP(llvmValue, []llvm.Value{
llvm.ConstInt(llvmValue.Type().Context().Int32Type(), uint64(v.offset()), false),
})
}
// If a particular LLVM pointer type is requested, cast to it.
if !llvmType.IsNil() && llvmType != llvmValue.Type() {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
return llvmValue, nil
}
// rawValue is a raw memory buffer that can store either pointers or regular
// data. This is the fallback data for everything that isn't clearly a
// literalValue or pointerValue.
type rawValue struct {
// An integer in buf contains either pointers or bytes.
// If it is a byte, it is smaller than 256.
// If it is a pointer, the index is contained in the upper 32 bits and the
// offset is contained in the lower 32 bits.
buf []uint64
}
func newRawValue(size uint32) rawValue {
return rawValue{make([]uint64, size)}
}
func (v rawValue) len(r *runner) uint32 {
return uint32(len(v.buf))
}
func (v rawValue) String() string {
if len(v.buf) == 2 || len(v.buf) == 4 || len(v.buf) == 8 {
// Format as a pointer if the entire buf is this pointer.
if v.buf[0] > 255 {
isPointer := true
for _, p := range v.buf {
if p != v.buf[0] {
isPointer = false
break
}
}
if isPointer {
return pointerValue{v.buf[0]}.String()
}
}
// Format as number if none of the buf is a pointer.
if !v.hasPointer() {
return strconv.FormatInt(v.Int(), 10)
}
}
return "<[…" + strconv.Itoa(len(v.buf)) + "]>"
}
func (v rawValue) clone() value {
newValue := v
newValue.buf = make([]uint64, len(v.buf))
copy(newValue.buf, v.buf)
return newValue
}
func (v rawValue) asPointer(r *runner) (pointerValue, error) {
if v.buf[0] <= 255 {
// Probably a null pointer or memory-mapped I/O.
return pointerValue{}, errIntegerAsPointer
}
return pointerValue{v.buf[0]}, nil
}
func (v rawValue) asRawValue(r *runner) rawValue {
return v
}
func (v rawValue) bytes() []byte {
buf := make([]byte, len(v.buf))
for i, p := range v.buf {
if p > 255 {
panic("cannot convert pointer value to byte")
}
buf[i] = byte(p)
}
return buf
}
func (v rawValue) Uint() uint64 {
buf := v.bytes()
switch len(v.buf) {
case 1:
return uint64(buf[0])
case 2:
return uint64(binary.LittleEndian.Uint16(buf))
case 4:
return uint64(binary.LittleEndian.Uint32(buf))
case 8:
return binary.LittleEndian.Uint64(buf)
default:
panic("unknown integer size")
}
}
func (v rawValue) Int() int64 {
switch len(v.buf) {
case 1:
return int64(int8(v.Uint()))
case 2:
return int64(int16(v.Uint()))
case 4:
return int64(int32(v.Uint()))
case 8:
return int64(int64(v.Uint()))
default:
panic("unknown integer size")
}
}
// equal returns true if (and only if) the value matches rhs.
func (v rawValue) equal(rhs rawValue) bool {
if len(v.buf) != len(rhs.buf) {
panic("comparing values of different size")
}
for i, p := range v.buf {
if rhs.buf[i] != p {
return false
}
}
return true
}
// rawLLVMValue returns a llvm.Value for this rawValue, making up a type as it
// goes. The resulting value does not have a specified type, but it will be the
// same size and have the same bytes if it was created with a provided LLVM type
// (through toLLVMValue).
func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
var structFields []llvm.Value
ctx := mem.r.mod.Context()
int8Type := ctx.Int8Type()
var bytesBuf []llvm.Value
// addBytes can be called after adding to bytesBuf to flush remaining bytes
// to a new array in structFields.
addBytes := func() {
if len(bytesBuf) == 0 {
return
}
if len(bytesBuf) == 1 {
structFields = append(structFields, bytesBuf[0])
} else {
structFields = append(structFields, llvm.ConstArray(int8Type, bytesBuf))
}
bytesBuf = nil
}
// Create structFields, converting the rawValue to a LLVM value.
for i := uint32(0); i < uint32(len(v.buf)); {
if v.buf[i] > 255 {
addBytes()
field, err := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
elementType := field.Type().ElementType()
if elementType.TypeKind() == llvm.StructTypeKind {
// There are some special pointer types that should be used as a
// ptrtoint, so that they can be used in certain optimizations.
name := elementType.StructName()
if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" {
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
field = llvm.ConstPtrToInt(field, uintptrType)
}
}
structFields = append(structFields, field)
i += mem.r.pointerSize
continue
}
val := llvm.ConstInt(int8Type, uint64(v.buf[i]), false)
bytesBuf = append(bytesBuf, val)
i++
}
addBytes()
// Return the created data.
if len(structFields) == 1 {
return structFields[0], nil
}
return ctx.ConstStruct(structFields, false), nil
}
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
isZero := true
for _, p := range v.buf {
if p != 0 {
isZero = false
break
}
}
if isZero {
return llvm.ConstNull(llvmType), nil
}
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
if v.buf[0] > 255 {
ptr, err := v.asPointer(mem.r)
if err != nil {
panic(err)
}
if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) {
// Probably trying to serialize a pointer to a byte array,
// perhaps as a result of rawLLVMValue() in a previous interp
// run.
return llvm.Value{}, errInvalidPtrToIntSize
}
v, err := ptr.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstPtrToInt(v, llvmType), nil
}
var n uint64
switch llvmType.IntTypeWidth() {
case 64:
n = rawValue{v.buf[:8]}.Uint()
case 32:
n = rawValue{v.buf[:4]}.Uint()
case 16:
n = rawValue{v.buf[:2]}.Uint()
case 8:
n = uint64(v.buf[0])
case 1:
n = uint64(v.buf[0])
if n != 0 && n != 1 {
panic("bool must be 0 or 1")
}
default:
panic("unknown integer size")
}
return llvm.ConstInt(llvmType, n, false), nil
case llvm.StructTypeKind:
fieldTypes := llvmType.StructElementTypes()
fields := make([]llvm.Value, len(fieldTypes))
for i, fieldType := range fieldTypes {
offset := mem.r.targetData.ElementOffset(llvmType, i)
field := rawValue{
buf: v.buf[offset:],
}
var err error
fields[i], err = field.toLLVMValue(fieldType, mem)
if err != nil {
return llvm.Value{}, err
}
}
if llvmType.StructName() != "" {
return llvm.ConstNamedStruct(llvmType, fields), nil
}
return llvmType.Context().ConstStruct(fields, false), nil
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
childType := llvmType.ElementType()
childTypeSize := mem.r.targetData.TypeAllocSize(childType)
fields := make([]llvm.Value, numElements)
for i := range fields {
offset := i * int(childTypeSize)
field := rawValue{
buf: v.buf[offset:],
}
var err error
fields[i], err = field.toLLVMValue(childType, mem)
if err != nil {
return llvm.Value{}, err
}
if checks && fields[i].Type() != childType {
panic("child type doesn't match")
}
}
return llvm.ConstArray(childType, fields), nil
case llvm.PointerTypeKind:
if v.buf[0] > 255 {
// This is a regular pointer.
llvmValue, err := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
if llvmValue.Type() != llvmType {
if llvmValue.Type().PointerAddressSpace() != llvmType.PointerAddressSpace() {
// Special case for AVR function pointers.
// Because go-llvm doesn't have addrspacecast at the moment,
// do it indirectly with a ptrtoint/inttoptr pair.
llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType)
} else {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
}
return llvmValue, nil
}
// This is either a null pointer or a raw pointer for memory-mapped I/O
// (such as 0xe000ed00).
ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint()
if ptr == 0 {
// Null pointer.
return llvm.ConstNull(llvmType), nil
}
var ptrValue llvm.Value // the underlying int
switch mem.r.pointerSize {
case 8:
ptrValue = llvm.ConstInt(llvmType.Context().Int64Type(), ptr, false)
case 4:
ptrValue = llvm.ConstInt(llvmType.Context().Int32Type(), ptr, false)
case 2:
ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false)
default:
return llvm.Value{}, errors.New("interp: unknown pointer size")
}
return llvm.ConstIntToPtr(ptrValue, llvmType), nil
case llvm.DoubleTypeKind:
b := rawValue{v.buf[:8]}.Uint()
f := math.Float64frombits(b)
return llvm.ConstFloat(llvmType, f), nil
case llvm.FloatTypeKind:
b := uint32(rawValue{v.buf[:4]}.Uint())
f := math.Float32frombits(b)
return llvm.ConstFloat(llvmType, float64(f)), nil
default:
return llvm.Value{}, errors.New("interp: todo: raw value to LLVM value: " + llvmType.String())
}
}
func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
if llvmValue.IsNull() {
// A zero value is common so check that first.
return
}
if !llvmValue.IsAGlobalValue().IsNil() {
ptrSize := r.pointerSize
ptr, err := r.getValue(llvmValue).asPointer(r)
if err != nil {
panic(err)
}
for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptr.pointer
}
} else if !llvmValue.IsAConstantExpr().IsNil() {
switch llvmValue.Opcode() {
case llvm.IntToPtr, llvm.PtrToInt, llvm.BitCast:
// All these instructions effectively just reinterprets the bits
// (like a bitcast) while no bits change and keeping the same
// length, so just read its contents.
v.set(llvmValue.Operand(0), r)
case llvm.GetElementPtr:
ptr := llvmValue.Operand(0)
index := llvmValue.Operand(1)
numOperands := llvmValue.OperandsCount()
elementType := ptr.Type().ElementType()
totalOffset := r.targetData.TypeAllocSize(elementType) * index.ZExtValue()
for i := 2; i < numOperands; i++ {
indexValue := llvmValue.Operand(i)
if checks && indexValue.IsAConstantInt().IsNil() {
panic("expected const gep index to be a constant integer")
}
index := indexValue.ZExtValue()
switch elementType.TypeKind() {
case llvm.StructTypeKind:
// Indexing into a struct field.
offsetInBytes := r.targetData.ElementOffset(elementType, int(index))
totalOffset += offsetInBytes
elementType = elementType.StructElementTypes()[index]
default:
// Indexing into an array.
elementType = elementType.ElementType()
elementSize := r.targetData.TypeAllocSize(elementType)
totalOffset += index * elementSize
}
}
ptrSize := r.pointerSize
ptrValue, err := r.getValue(ptr).asPointer(r)
if err != nil {
panic(err)
}
ptrValue.pointer += totalOffset
for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptrValue.pointer
}
default:
llvmValue.Dump()
println()
panic("unknown constant expr")
}
} else if llvmValue.IsUndef() {
// Let undef be zero, by lack of an explicit 'undef' marker.
} else {
if checks && llvmValue.IsAConstant().IsNil() {
panic("expected a constant")
}
llvmType := llvmValue.Type()
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
n := llvmValue.ZExtValue()
switch llvmValue.Type().IntTypeWidth() {
case 64:
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], n)
for i, b := range buf {
v.buf[i] = uint64(b)
}
case 32:
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], uint32(n))
for i, b := range buf {
v.buf[i] = uint64(b)
}
case 16:
var buf [2]byte
binary.LittleEndian.PutUint16(buf[:], uint16(n))
for i, b := range buf {
v.buf[i] = uint64(b)
}
case 8, 1:
v.buf[0] = n
default:
panic("unknown integer size")
}
case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount()
for i := 0; i < numElements; i++ {
offset := r.targetData.ElementOffset(llvmType, i)
field := rawValue{
buf: v.buf[offset:],
}
field.set(llvm.ConstExtractValue(llvmValue, []uint32{uint32(i)}), r)
}
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
childType := llvmType.ElementType()
childTypeSize := r.targetData.TypeAllocSize(childType)
for i := 0; i < numElements; i++ {
offset := i * int(childTypeSize)
field := rawValue{
buf: v.buf[offset:],
}
field.set(llvm.ConstExtractValue(llvmValue, []uint32{uint32(i)}), r)
}
case llvm.DoubleTypeKind:
f, _ := llvmValue.DoubleValue()
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f))
for i, b := range buf {
v.buf[i] = uint64(b)
}
case llvm.FloatTypeKind:
f, _ := llvmValue.DoubleValue()
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], math.Float32bits(float32(f)))
for i, b := range buf {
v.buf[i] = uint64(b)
}
default:
llvmValue.Dump()
println()
panic("unknown constant")
}
}
}
// hasPointer returns true if this raw value contains a pointer somewhere in the
// buffer.
func (v rawValue) hasPointer() bool {
for _, p := range v.buf {
if p > 255 {
return true
}
}
return false
}
// localValue is a special implementation of the value interface. It is a
// placeholder for other values in instruction operands, and is replaced with
// one of the others before executing.
type localValue struct {
value llvm.Value
}
func (v localValue) len(r *runner) uint32 {
panic("interp: localValue.len")
}
func (v localValue) String() string {
return "<!>"
}
func (v localValue) clone() value {
panic("interp: localValue.clone()")
}
func (v localValue) asPointer(r *runner) (pointerValue, error) {
return pointerValue{}, errors.New("interp: localValue.asPointer called")
}
func (v localValue) asRawValue(r *runner) rawValue {
panic("interp: localValue.asRawValue")
}
func (v localValue) Uint() uint64 {
panic("interp: localValue.Uint")
}
func (v localValue) Int() int64 {
panic("interp: localValue.Int")
}
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
return v.value, nil
}
func (r *runner) getValue(llvmValue llvm.Value) value {
if checks && llvmValue.IsNil() {
panic("nil llvmValue")
}
if !llvmValue.IsAGlobalValue().IsNil() {
index, ok := r.globals[llvmValue]
if !ok {
obj := object{
llvmGlobal: llvmValue,
}
index = len(r.objects)
r.globals[llvmValue] = index
r.objects = append(r.objects, obj)
if !llvmValue.IsAGlobalVariable().IsNil() {
obj.size = uint32(r.targetData.TypeAllocSize(llvmValue.Type().ElementType()))
if initializer := llvmValue.Initializer(); !initializer.IsNil() {
obj.buffer = r.getValue(initializer)
obj.constant = llvmValue.IsGlobalConstant()
}
} else if !llvmValue.IsAFunction().IsNil() {
// OK
} else {
panic("interp: unknown global value")
}
// Update the object after it has been created. This avoids an
// infinite recursion when using getValue on a global that contains
// a reference to itself.
r.objects[index] = obj
}
return newPointerValue(r, index, 0)
} else if !llvmValue.IsAConstant().IsNil() {
if !llvmValue.IsAConstantInt().IsNil() {
n := llvmValue.ZExtValue()
switch llvmValue.Type().IntTypeWidth() {
case 64:
return literalValue{n}
case 32:
return literalValue{uint32(n)}
case 16:
return literalValue{uint16(n)}
case 8, 1:
return literalValue{uint8(n)}
default:
panic("unknown integer size")
}
}
size := r.targetData.TypeAllocSize(llvmValue.Type())
v := newRawValue(uint32(size))
v.set(llvmValue, r)
return v
} else if !llvmValue.IsAInstruction().IsNil() || !llvmValue.IsAArgument().IsNil() {
return localValue{llvmValue}
} else if !llvmValue.IsAInlineAsm().IsNil() {
return localValue{llvmValue}
} else {
llvmValue.Dump()
println()
panic("unknown value")
}
}
// readObjectLayout reads the object layout as it is stored by the compiler. It
// returns the size in the number of words and the bitmap.
func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) {
pointerSize := layoutValue.len(r)
if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.i8ptrType) {
panic("inconsistent pointer size")
}
// The object layout can be stored in a global variable, directly as an
// integer value, or can be nil.
ptr, err := layoutValue.asPointer(r)
if err == errIntegerAsPointer {
// It's an integer, which means it's a small object or unknown.
layout := layoutValue.Uint()
if layout == 0 {
// Nil pointer, which means the layout is unknown.
return 0, nil
}
if layout%2 != 1 {
// Sanity check: the least significant bit must be set. This is how
// the runtime can separate pointers from integers.
panic("unexpected layout")
}
// Determine format of bitfields in the integer.
pointerBits := uint64(pointerSize * 8)
var sizeFieldBits uint64
switch pointerBits {
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
panic("unknown pointer size")
}
// Extract fields.
objectSizeWords := (layout >> 1) & (1<<sizeFieldBits - 1)
bitmap := new(big.Int).SetUint64(layout >> (1 + sizeFieldBits))
return objectSizeWords, bitmap
}
// Read the object size in words and the bitmap from the global.
buf := r.objects[ptr.index()].buffer.(rawValue)
objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint()
rawByteValues := buf.buf[r.pointerSize:]
rawBytes := make([]byte, len(rawByteValues))
for i, v := range rawByteValues {
if uint64(byte(v)) != v {
panic("found pointer in data array?") // sanity check
}
rawBytes[i] = byte(v)
}
bitmap := new(big.Int).SetBytes(rawBytes)
return objectSizeWords, bitmap
}
// getLLVMTypeFromLayout returns the 'layout type', which is an approximation of
// the real type. Pointers are in the correct location but the actual object may
// have some additional repetition, for example in the buffer of a slice.
func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type {
objectSizeWords, bitmap := r.readObjectLayout(layoutValue)
if bitmap == nil {
// No information available.
return llvm.Type{}
}
if bitmap.BitLen() == 0 {
// There are no pointers in this object, so treat this as a raw byte
// buffer. This is important because objects without pointers may have
// lower alignment.
return r.mod.Context().Int8Type()
}
// Create the LLVM type.
pointerSize := layoutValue.len(r)
pointerAlignment := r.targetData.PrefTypeAlignment(r.i8ptrType)
var fields []llvm.Type
for i := 0; i < int(objectSizeWords); {
if bitmap.Bit(i) != 0 {
// Pointer field.
fields = append(fields, r.i8ptrType)
i += int(pointerSize / uint32(pointerAlignment))
} else {
// Byte/word field.
fields = append(fields, r.mod.Context().IntType(pointerAlignment*8))
i += 1
}
}
var llvmLayoutType llvm.Type
if len(fields) == 1 {
llvmLayoutType = fields[0]
} else {
llvmLayoutType = r.mod.Context().StructType(fields, false)
}
objectSizeBytes := objectSizeWords * uint64(pointerAlignment)
if checks && r.targetData.TypeAllocSize(llvmLayoutType) != objectSizeBytes {
panic("unexpected size") // sanity check
}
return llvmLayoutType
}