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 architectures, if TinyGo ever adds support for one.

import (
	"encoding/binary"
	"errors"
	"fmt"
	"maps"
	"math"
	"math/big"
	"slices"
	"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.GlobalValueType() 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
	align          int       // alignment of the object (may be 0 if unknown)
	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)
	}
	maps.Copy(mv.objects, sub.objects)
	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 _, llvmInst := range slices.Backward(mv.instructions) {

		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) error {
	return 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) error {
	return 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) error {
	if llvmValue.IsUndef() || llvmValue.IsNull() {
		// Null and undef definitely don't contain (valid) pointers.
		return nil
	}
	if !llvmValue.IsAInstruction().IsNil() || !llvmValue.IsAArgument().IsNil() {
		// These are considered external by default, there is nothing to mark.
		return nil
	}

	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.
					err := mv.markExternal(initializer, 2)
					if err != nil {
						return err
					}
				}
			} 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 := range numOperands {
							// Using mark '2' (which means read/write access)
							// because this might be a store instruction.
							err := mv.markExternal(inst.Operand(i), 2)
							if err != nil {
								return err
							}
						}
					}
				}
			}
		}
	} else if !llvmValue.IsAConstantExpr().IsNil() {
		switch llvmValue.Opcode() {
		case llvm.IntToPtr, llvm.PtrToInt, llvm.BitCast, llvm.GetElementPtr:
			err := mv.markExternal(llvmValue.Operand(0), mark)
			if err != nil {
				return err
			}
		case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
			// Integer binary operators. Mark both operands.
			err := mv.markExternal(llvmValue.Operand(0), mark)
			if err != nil {
				return err
			}
			err = mv.markExternal(llvmValue.Operand(1), mark)
			if err != nil {
				return err
			}
		default:
			return fmt.Errorf("interp: unknown constant expression '%s'", instructionNameMap[llvmValue.Opcode()])
		}
	} 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 := range numElements {
				element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
				err := mv.markExternal(element, mark)
				if err != nil {
					return err
				}
			}
		case llvm.ArrayTypeKind:
			numElements := llvmType.ArrayLength()
			for i := range numElements {
				element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
				err := mv.markExternal(element, mark)
				if err != nil {
					return err
				}
			}
		default:
			return errors.New("interp: unknown type kind in markExternalValue")
		}
	}
	return nil
}

// 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 or if the load is out of bounds of the object (in which
// case the caller defers the load to runtime).
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 p.offset()+size > obj.size {
		// The load is out of bounds of the object. This can happen for valid
		// Go programs, for example when dereferencing the pointer returned by
		// unsafe.SliceData on a zero-capacity slice (which points to a
		// zero-sized object). Return nil so the caller defers this load to
		// runtime instead of crashing the compiler.
		return nil
	}
	v := obj.buffer.asRawValue(mv.r)
	loadedBuf := v.buf[p.offset() : p.offset()+size]
	if _, writable := mv.objects[p.index()]; writable {
		// This object's buffer is owned by this view, which means a later
		// store may mutate it in place (see store below). Copy the loaded
		// slice so the returned value is not aliased with the live buffer.
		loadedBuf = append([]uint64(nil), loadedBuf...)
	}
	return rawValue{buf: loadedBuf}
}

// 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")
	}
	index := p.index()
	var obj object
	writable := false
	if mv.objects != nil {
		obj, writable = mv.objects[index]
	}
	if !writable {
		obj = mv.get(index)
	}
	if obj.buffer == nil {
		// External global, return false (for a failure).
		return false
	}
	valueLen := v.len(mv.r)
	if checks && p.offset()+valueLen > obj.size {
		panic("interp: store out of bounds")
	}
	if p.offset() == 0 && valueLen == obj.buffer.len(mv.r) {
		obj.buffer = v.clone()
	} else {
		if !writable {
			obj = obj.clone()
		}
		buffer := obj.buffer.asRawValue(mv.r)
		obj.buffer = buffer
		v := v.asRawValue(mv.r)
		for i := range valueLen {
			buffer.buf[p.offset()+i] = v.buf[i]
		}
	}
	mv.put(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(*runner) uint64
	Int(*runner) 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 any
}

// Make a literalValue given the number of bits.
func makeLiteralInt(value uint64, bits int) literalValue {
	switch bits {
	case 64:
		return literalValue{value}
	case 32:
		return literalValue{uint32(value)}
	case 16:
		return literalValue{uint16(value)}
	case 8:
		return literalValue{uint8(value)}
	default:
		panic("unknown integer size")
	}
}

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 {
	// Note: passing a nil *runner to v.Int because we know it won't use it.
	return strconv.FormatInt(v.Int(nil), 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)
		r.byteOrder.PutUint64(buf, value)
	case uint32:
		buf = make([]byte, 4)
		r.byteOrder.PutUint32(buf, uint32(value))
	case uint16:
		buf = make([]byte, 2)
		r.byteOrder.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(r *runner) 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(r *runner) 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 int64) (pointerValue, error) {
	result := pointerValue{v.pointer + uint64(offset)}
	if checks && v.index() != result.index() {
		return result, fmt.Errorf("interp: offset %d out of range for object %v", offset, v)
	}
	return result, nil
}

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(r *runner) uint64 {
	r.interpErr = errUnsupportedInst
	return 0
}

func (v pointerValue) Int(r *runner) int64 {
	r.interpErr = errUnsupportedInst
	return 0
}

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())
		alignment := obj.align
		if alignment == 0 {
			// Unknown alignment, perhaps from a direct call to runtime.alloc in
			// the runtime. Use a conservative default instead.
			alignment = mem.r.maxAlign
		}
		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(alignment)
			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
			} 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)
			llvmValue.SetAlignment(alignment)
		}

		// 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.
		llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{
			llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false),
		})
	}

	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() {
			// Construct a fake runner, which is little endian.
			// We only use String() for debugging, so this is is good enough
			// (the printed value will just be slightly wrong when debugging the
			// interp package with GOOS=mips for example).
			r := &runner{byteOrder: binary.LittleEndian}
			return strconv.FormatInt(v.Int(r), 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(r *runner) []byte {
	buf := make([]byte, len(v.buf))
	for i, p := range v.buf {
		if p > 255 {
			r.interpErr = errUnsupportedInst
			return buf
		}
		buf[i] = byte(p)
	}
	return buf
}

func (v rawValue) Uint(r *runner) uint64 {
	buf := v.bytes(r)
	if r.interpErr != nil {
		return 0
	}

	switch len(v.buf) {
	case 1:
		return uint64(buf[0])
	case 2:
		return uint64(r.byteOrder.Uint16(buf))
	case 4:
		return uint64(r.byteOrder.Uint32(buf))
	case 8:
		return r.byteOrder.Uint64(buf)
	default:
		panic("unknown integer size")
	}
}

func (v rawValue) Int(r *runner) int64 {
	switch len(v.buf) {
	case 1:
		return int64(int8(v.Uint(r)))
	case 2:
		return int64(int16(v.Uint(r)))
	case 4:
		return int64(int32(v.Uint(r)))
	case 8:
		return int64(int64(v.Uint(r)))
	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
			}
			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.dataPtrType) {
				// 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(mem.r)
		case 32:
			n = rawValue{v.buf[:4]}.Uint(mem.r)
		case 16:
			n = rawValue{v.buf[:2]}.Uint(mem.r)
		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
			}
		}
		// Always use ConstNamedStruct to preserve the exact type identity.
		// ConstStruct creates a literal struct type which may differ from an
		// anonymous identified struct even when structurally identical.
		return llvm.ConstNamedStruct(llvmType, fields), 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)
				}
			}
			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(mem.r)
		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(mem.r)
		f := math.Float64frombits(b)
		return llvm.ConstFloat(llvmType, f), nil
	case llvm.FloatTypeKind:
		b := uint32(rawValue{v.buf[:4]}.Uint(mem.r))
		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 := range ptrSize {
			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 := llvmValue.GEPSourceElementType()
			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 := range ptrSize {
				v.buf[i] = ptrValue.pointer
			}
		case llvm.ICmp:
			// Note: constant icmp isn't supported anymore in LLVM 19.
			// Once we drop support for LLVM 18, this can be removed.
			size := r.targetData.TypeAllocSize(llvmValue.Operand(0).Type())
			lhs := newRawValue(uint32(size))
			rhs := newRawValue(uint32(size))
			lhs.set(llvmValue.Operand(0), r)
			rhs.set(llvmValue.Operand(1), r)
			if r.interpretICmp(lhs, rhs, llvmValue.IntPredicate()) {
				v.buf[0] = 1 // result is true
			} else {
				v.buf[0] = 0 // result is false
			}
		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
				r.byteOrder.PutUint64(buf[:], n)
				for i, b := range buf {
					v.buf[i] = uint64(b)
				}
			case 32:
				var buf [4]byte
				r.byteOrder.PutUint32(buf[:], uint32(n))
				for i, b := range buf {
					v.buf[i] = uint64(b)
				}
			case 16:
				var buf [2]byte
				r.byteOrder.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 := range numElements {
				offset := r.targetData.ElementOffset(llvmType, i)
				field := rawValue{
					buf: v.buf[offset:],
				}
				field.set(r.builder.CreateExtractValue(llvmValue, i, ""), r)
			}
		case llvm.ArrayTypeKind:
			numElements := llvmType.ArrayLength()
			childType := llvmType.ElementType()
			childTypeSize := r.targetData.TypeAllocSize(childType)
			for i := range numElements {
				offset := i * int(childTypeSize)
				field := rawValue{
					buf: v.buf[offset:],
				}
				field.set(r.builder.CreateExtractValue(llvmValue, i, ""), r)
			}
		case llvm.DoubleTypeKind:
			f, _ := llvmValue.DoubleValue()
			var buf [8]byte
			r.byteOrder.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
			r.byteOrder.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(r *runner) uint64 {
	panic("interp: localValue.Uint")
}

func (v localValue) Int(r *runner) 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.GlobalValueType()))
				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.
//
// For details on this format, see src/runtime/gc_precise.go.
func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) {
	pointerSize := layoutValue.len(r)
	if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.dataPtrType) {
		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(r)
		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(r)
	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)
	}
	reverseBytes(rawBytes) // little-endian to big-endian
	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.dataPtrType)
	var fields []llvm.Type
	for i := 0; i < int(objectSizeWords); {
		if bitmap.Bit(i) != 0 {
			// Pointer field.
			fields = append(fields, r.dataPtrType)
			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
}

// Reverse a slice of bytes. From the wiki:
// https://github.com/golang/go/wiki/SliceTricks#reversing
func reverseBytes(buf []byte) {
	for i := len(buf)/2 - 1; i >= 0; i-- {
		opp := len(buf) - 1 - i
		buf[i], buf[opp] = buf[opp], buf[i]
	}
}
