Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions interp/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsALoadInst().IsNil():
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
var value llvm.Value
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
if inst.IsVolatile() || fr.Eval.isDirty(operand.Value()) {
fr.Eval.markDirty(operand.Value())
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
var err error
Expand All @@ -108,7 +109,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsAStoreInst().IsNil():
value := fr.getLocal(inst.Operand(0))
ptr := fr.getLocal(inst.Operand(1))
if inst.IsVolatile() {
if inst.IsVolatile() || fr.Eval.isDirty(ptr.Value()) {
fr.Eval.markDirty(ptr.Value())
fr.builder.CreateStore(value.Value(), ptr.Value())
} else {
err := ptr.Store(value.Value())
Expand Down
39 changes: 20 additions & 19 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,27 @@ func (e *Eval) getValue(v llvm.Value) Value {
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
// when it encounters a constant GEP on a global, it marks the global dirty.
func (e *Eval) markDirty(v llvm.Value) {
if !v.IsAGlobalVariable().IsNil() {
if v.IsGlobalConstant() {
return
}
if _, ok := e.dirtyGlobals[v]; !ok {
e.dirtyGlobals[v] = struct{}{}
e.sideEffectFuncs = nil // re-calculate all side effects
}
} else if v.IsConstant() {
if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
// looks like a constant getelementptr of a global.
// TODO: find a way to make sure it really is: v.Opcode() returns 0.
e.markDirty(v.Operand(0))
if v.Type().TypeKind() != llvm.PointerTypeKind {
return
}
v = unwrap(v)
if v.IsAGlobalVariable().IsNil() {
if !v.IsAUndefValue().IsNil() || !v.IsAConstantPointerNull().IsNil() {
// Nothing to mark here: these definitely don't point to globals.
return
}
return // nothing to mark
} else if !v.IsAGetElementPtrInst().IsNil() {
panic("interp: todo: GEP")
} else {
// Not constant and not a global or GEP so doesn't have to be marked
// non-constant.
panic("interp: trying to mark a non-global as dirty")
}
e.dirtyGlobals[v] = struct{}{}
}

// isDirty returns whether the value is tracked as part of the set of dirty
// globals or not. If it's not dirty, it means loads/stores can be done at
// compile time.
func (e *Eval) isDirty(v llvm.Value) bool {
v = unwrap(v)
if _, ok := e.dirtyGlobals[v]; ok {
return true
}
return false
}
31 changes: 16 additions & 15 deletions interp/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,22 @@ func isZero(v llvm.Value) (result bool, ok bool) {
return false, false // not valid
}

// unwrap returns the underlying value, with GEPs removed. This can be useful to
// get the underlying global of a GEP pointer.
// unwrap returns the underlying global value, with GEPs and bitcasts removed.
// This can be useful to get the underlying global of a GEP pointer.
func unwrap(value llvm.Value) llvm.Value {
for {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.GetElementPtr:
value = value.Operand(0)
continue
}
} else if !value.IsAGetElementPtrInst().IsNil() {
value = value.Operand(0)
continue
}
break
var opcode llvm.Opcode
if !value.IsAConstantExpr().IsNil() {
opcode = value.Opcode()
} else if !value.IsAInstruction().IsNil() {
opcode = value.InstructionOpcode()
} else {
return value
}
switch opcode {
case llvm.BitCast, llvm.GetElementPtr, llvm.PtrToInt:
// Make sure dirty globals are caught through pointer casts.
return unwrap(value.Operand(0))
default:
return value
}
return value
}
49 changes: 37 additions & 12 deletions interp/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,28 @@ func (v *LocalValue) Type() llvm.Type {
}

func (v *LocalValue) IsConstant() bool {
if _, ok := v.Eval.dirtyGlobals[unwrap(v.Underlying)]; ok {
if v.Eval.isDirty(v.Underlying) {
return false
}
return v.Underlying.IsConstant()
}

// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() (llvm.Value, error) {
if v.Eval.isDirty(v.Underlying) {
// This may indicate a bug elsewhere.
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer(), nil
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
return llvm.Value{}, errors.New("invalid GEP")
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
global := v.Eval.getValue(v.Underlying.Operand(0))
agg, err := global.Load()
Expand All @@ -61,9 +67,35 @@ func (v *LocalValue) Load() (llvm.Value, error) {
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
case llvm.BitCast:
return llvm.Value{}, errors.New("interp: load from a bitcast")
// Assuming a pointer bitcast, such as i64* to double*.
// By loading the pre-bitcast value and then bitcasting the loaded value
// we work around the problem.
sourceType := v.Underlying.Operand(0).Type().ElementType()
destType := v.Underlying.Type().ElementType()
if v.Eval.TargetData.TypeAllocSize(sourceType) != v.Eval.TargetData.TypeAllocSize(destType) {
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
if !isScalar(sourceType) || !isScalar(destType) {
// Actually bitcasts are a bit more flexible than this: any
// non-aggregate first-class type can be casted.
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
valueBeforeCast := LocalValue{v.Eval, v.Underlying.Operand(0)}
llvmValueBeforeCast, err := valueBeforeCast.Load()
if err != nil {
return llvm.Value{}, err
}
if !llvmValueBeforeCast.IsConstant() {
// Unlikely but check for it anyway.
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
return llvm.ConstBitCast(llvmValueBeforeCast, destType), nil
default:
return llvm.Value{}, errors.New("interp: load from a constant")
v.MarkDirty()
return v.Eval.builder.CreateLoad(v.Underlying, ""), nil
}
}

Expand Down Expand Up @@ -161,14 +193,7 @@ func (v *LocalValue) getConstGEPIndices() []uint32 {
// 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{}{}
v.Eval.markDirty(v.Underlying)
}

// MapValue implements a Go map which is created at compile time and stored as a
Expand Down