mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
Merge pull request #2594 from nspcc-dev/compiler-inline-return
compiler: allow to use multiple returns in inlined functions
This commit is contained in:
commit
71b721c72a
5 changed files with 141 additions and 67 deletions
|
@ -62,9 +62,8 @@ type codegen struct {
|
||||||
labels map[labelWithType]uint16
|
labels map[labelWithType]uint16
|
||||||
// A list of nested label names together with evaluation stack depth.
|
// A list of nested label names together with evaluation stack depth.
|
||||||
labelList []labelWithStackSize
|
labelList []labelWithStackSize
|
||||||
// inlineLabelOffsets contains size of labelList at the start of inline call processing.
|
// inlineContext contains info about inlined function calls.
|
||||||
// For such calls, we need to drop only the newly created part of stack.
|
inlineContext []inlineContextSingle
|
||||||
inlineLabelOffsets []int
|
|
||||||
// globalInlineCount contains the amount of auxiliary variables introduced by
|
// globalInlineCount contains the amount of auxiliary variables introduced by
|
||||||
// function inlining during global variables initialization.
|
// function inlining during global variables initialization.
|
||||||
globalInlineCount int
|
globalInlineCount int
|
||||||
|
@ -146,6 +145,14 @@ type nameWithLocals struct {
|
||||||
count int
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type inlineContextSingle struct {
|
||||||
|
// labelOffset contains size of labelList at the start of inline call processing.
|
||||||
|
// For such calls, we need to drop only the newly created part of stack.
|
||||||
|
labelOffset int
|
||||||
|
// returnLabel contains label ID pointing to the first instruction right after the call.
|
||||||
|
returnLabel uint16
|
||||||
|
}
|
||||||
|
|
||||||
type varType int
|
type varType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -680,8 +687,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
cnt := 0
|
cnt := 0
|
||||||
start := 0
|
start := 0
|
||||||
if len(c.inlineLabelOffsets) > 0 {
|
if len(c.inlineContext) > 0 {
|
||||||
start = c.inlineLabelOffsets[len(c.inlineLabelOffsets)-1]
|
start = c.inlineContext[len(c.inlineContext)-1].labelOffset
|
||||||
}
|
}
|
||||||
for i := start; i < len(c.labelList); i++ {
|
for i := start; i < len(c.labelList); i++ {
|
||||||
cnt += c.labelList[i].sz
|
cnt += c.labelList[i].sz
|
||||||
|
@ -711,6 +718,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.saveSequencePoint(n)
|
c.saveSequencePoint(n)
|
||||||
if len(c.pkgInfoInline) == 0 {
|
if len(c.pkgInfoInline) == 0 {
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
||||||
|
} else {
|
||||||
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, c.inlineContext[len(c.inlineContext)-1].returnLabel)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -2211,7 +2220,7 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
|
||||||
|
|
||||||
func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
ctx := vm.NewContext(b)
|
ctx := vm.NewContext(b)
|
||||||
var offsets []int
|
var nopOffsets []int
|
||||||
for op, param, err := ctx.Next(); err == nil && ctx.IP() < len(b); op, param, err = ctx.Next() {
|
for op, param, err := ctx.Next(); err == nil && ctx.IP() < len(b); op, param, err = ctx.Next() {
|
||||||
switch op {
|
switch op {
|
||||||
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
||||||
|
@ -2235,13 +2244,20 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
||||||
offsets = append(offsets, ctx.IP())
|
if op == opcode.JMPL && offset == 5 {
|
||||||
|
copy(b[ctx.IP():], []byte{byte(opcode.NOP), byte(opcode.NOP), byte(opcode.NOP), byte(opcode.NOP), byte(opcode.NOP)})
|
||||||
|
nopOffsets = append(nopOffsets, ctx.IP(), ctx.IP()+1, ctx.IP()+2, ctx.IP()+3, ctx.IP()+4)
|
||||||
|
} else {
|
||||||
|
copy(b[ctx.IP():], []byte{byte(toShortForm(op)), byte(offset), byte(opcode.NOP), byte(opcode.NOP), byte(opcode.NOP)})
|
||||||
|
nopOffsets = append(nopOffsets, ctx.IP()+2, ctx.IP()+3, ctx.IP()+4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case opcode.INITSLOT:
|
case opcode.INITSLOT:
|
||||||
nextIP := ctx.NextIP()
|
nextIP := ctx.NextIP()
|
||||||
info := c.reverseOffsetMap[ctx.IP()]
|
info := c.reverseOffsetMap[ctx.IP()]
|
||||||
if argCount := b[nextIP-1]; info.count == 0 && argCount == 0 {
|
if argCount := b[nextIP-1]; info.count == 0 && argCount == 0 {
|
||||||
offsets = append(offsets, ctx.IP())
|
copy(b[ctx.IP():], []byte{byte(opcode.NOP), byte(opcode.NOP), byte(opcode.NOP)})
|
||||||
|
nopOffsets = append(nopOffsets, ctx.IP(), ctx.IP()+1, ctx.IP()+2)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2253,20 +2269,20 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.deployEndOffset >= 0 {
|
if c.deployEndOffset >= 0 {
|
||||||
_, end := correctRange(uint16(c.initEndOffset+1), uint16(c.deployEndOffset), offsets)
|
_, end := correctRange(uint16(c.initEndOffset+1), uint16(c.deployEndOffset), nopOffsets)
|
||||||
c.deployEndOffset = int(end)
|
c.deployEndOffset = int(end)
|
||||||
}
|
}
|
||||||
if c.initEndOffset > 0 {
|
if c.initEndOffset > 0 {
|
||||||
_, end := correctRange(0, uint16(c.initEndOffset), offsets)
|
_, end := correctRange(0, uint16(c.initEndOffset), nopOffsets)
|
||||||
c.initEndOffset = int(end)
|
c.initEndOffset = int(end)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Correct function ip range.
|
// Correct function ip range.
|
||||||
// Note: indices are sorted in increasing order.
|
// Note: indices are sorted in increasing order.
|
||||||
for _, f := range c.funcs {
|
for _, f := range c.funcs {
|
||||||
f.rng.Start, f.rng.End = correctRange(f.rng.Start, f.rng.End, offsets)
|
f.rng.Start, f.rng.End = correctRange(f.rng.Start, f.rng.End, nopOffsets)
|
||||||
}
|
}
|
||||||
return shortenJumps(b, offsets), nil
|
return removeNOPs(b, nopOffsets), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func correctRange(start, end uint16, offsets []int) (uint16, uint16) {
|
func correctRange(start, end uint16, offsets []int) (uint16, uint16) {
|
||||||
|
@ -2277,10 +2293,10 @@ loop:
|
||||||
case ind > int(end):
|
case ind > int(end):
|
||||||
break loop
|
break loop
|
||||||
case ind < int(start):
|
case ind < int(start):
|
||||||
newStart -= longToShortRemoveCount
|
newStart--
|
||||||
newEnd -= longToShortRemoveCount
|
newEnd--
|
||||||
case ind >= int(start):
|
case ind >= int(start):
|
||||||
newEnd -= longToShortRemoveCount
|
newEnd--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newStart, newEnd
|
return newStart, newEnd
|
||||||
|
@ -2303,21 +2319,22 @@ func (c *codegen) replaceLabelWithOffset(ip int, arg []byte) (int, error) {
|
||||||
return offset, nil
|
return offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// longToShortRemoveCount is a difference between short and long instruction sizes in bytes.
|
// removeNOPs converts b to a program where all long JMP*/CALL* specified by absolute offsets
|
||||||
// By pure coincidence, this is also the size of `INITSLOT` instruction.
|
|
||||||
const longToShortRemoveCount = 3
|
|
||||||
|
|
||||||
// shortenJumps converts b to a program where all long JMP*/CALL* specified by absolute offsets
|
|
||||||
// are replaced with their corresponding short counterparts. It panics if either b or offsets are invalid.
|
// are replaced with their corresponding short counterparts. It panics if either b or offsets are invalid.
|
||||||
// This is done in 2 passes:
|
// This is done in 2 passes:
|
||||||
// 1. Alter jump offsets taking into account parts to be removed.
|
// 1. Alter jump offsets taking into account parts to be removed.
|
||||||
// 2. Perform actual removal of jump targets.
|
// 2. Perform actual removal of jump targets.
|
||||||
// Note: after jump offsets altering, there can appear new candidates for conversion.
|
// Note: after jump offsets altering, there can appear new candidates for conversion.
|
||||||
// These are ignored for now.
|
// These are ignored for now.
|
||||||
func shortenJumps(b []byte, offsets []int) []byte {
|
func removeNOPs(b []byte, nopOffsets []int) []byte {
|
||||||
if len(offsets) == 0 {
|
if len(nopOffsets) == 0 {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
for i := range nopOffsets {
|
||||||
|
if b[nopOffsets[i]] != byte(opcode.NOP) {
|
||||||
|
panic("NOP offset is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Alter existing jump offsets.
|
// 1. Alter existing jump offsets.
|
||||||
ctx := vm.NewContext(b)
|
ctx := vm.NewContext(b)
|
||||||
|
@ -2330,14 +2347,14 @@ func shortenJumps(b []byte, offsets []int) []byte {
|
||||||
opcode.JMPEQ, opcode.JMPNE,
|
opcode.JMPEQ, opcode.JMPNE,
|
||||||
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT, opcode.ENDTRY:
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT, opcode.ENDTRY:
|
||||||
offset := int(int8(b[nextIP-1]))
|
offset := int(int8(b[nextIP-1]))
|
||||||
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
offset += calcOffsetCorrection(ip, ip+offset, nopOffsets)
|
||||||
b[nextIP-1] = byte(offset)
|
b[nextIP-1] = byte(offset)
|
||||||
case opcode.TRY:
|
case opcode.TRY:
|
||||||
catchOffset := int(int8(b[nextIP-2]))
|
catchOffset := int(int8(b[nextIP-2]))
|
||||||
catchOffset += calcOffsetCorrection(ip, ip+catchOffset, offsets)
|
catchOffset += calcOffsetCorrection(ip, ip+catchOffset, nopOffsets)
|
||||||
b[nextIP-1] = byte(catchOffset)
|
b[nextIP-1] = byte(catchOffset)
|
||||||
finallyOffset := int(int8(b[nextIP-1]))
|
finallyOffset := int(int8(b[nextIP-1]))
|
||||||
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, offsets)
|
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, nopOffsets)
|
||||||
b[nextIP-1] = byte(finallyOffset)
|
b[nextIP-1] = byte(finallyOffset)
|
||||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
||||||
opcode.JMPEQL, opcode.JMPNEL,
|
opcode.JMPEQL, opcode.JMPNEL,
|
||||||
|
@ -2345,42 +2362,31 @@ func shortenJumps(b []byte, offsets []int) []byte {
|
||||||
opcode.CALLL, opcode.PUSHA, opcode.ENDTRYL:
|
opcode.CALLL, opcode.PUSHA, opcode.ENDTRYL:
|
||||||
arg := b[nextIP-4:]
|
arg := b[nextIP-4:]
|
||||||
offset := int(int32(binary.LittleEndian.Uint32(arg)))
|
offset := int(int32(binary.LittleEndian.Uint32(arg)))
|
||||||
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
offset += calcOffsetCorrection(ip, ip+offset, nopOffsets)
|
||||||
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
||||||
case opcode.TRYL:
|
case opcode.TRYL:
|
||||||
arg := b[nextIP-8:]
|
arg := b[nextIP-8:]
|
||||||
catchOffset := int(int32(binary.LittleEndian.Uint32(arg)))
|
catchOffset := int(int32(binary.LittleEndian.Uint32(arg)))
|
||||||
catchOffset += calcOffsetCorrection(ip, ip+catchOffset, offsets)
|
catchOffset += calcOffsetCorrection(ip, ip+catchOffset, nopOffsets)
|
||||||
binary.LittleEndian.PutUint32(arg, uint32(catchOffset))
|
binary.LittleEndian.PutUint32(arg, uint32(catchOffset))
|
||||||
arg = b[nextIP-4:]
|
arg = b[nextIP-4:]
|
||||||
finallyOffset := int(int32(binary.LittleEndian.Uint32(arg)))
|
finallyOffset := int(int32(binary.LittleEndian.Uint32(arg)))
|
||||||
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, offsets)
|
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, nopOffsets)
|
||||||
binary.LittleEndian.PutUint32(arg, uint32(finallyOffset))
|
binary.LittleEndian.PutUint32(arg, uint32(finallyOffset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Convert instructions.
|
// 2. Convert instructions.
|
||||||
copyOffset := 0
|
copyOffset := 0
|
||||||
l := len(offsets)
|
l := len(nopOffsets)
|
||||||
if op := opcode.Opcode(b[offsets[0]]); op != opcode.INITSLOT {
|
|
||||||
b[offsets[0]] = byte(toShortForm(op))
|
|
||||||
}
|
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
start := offsets[i] + 2
|
start := nopOffsets[i]
|
||||||
if b[offsets[i]] == byte(opcode.INITSLOT) {
|
|
||||||
start = offsets[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
end := len(b)
|
end := len(b)
|
||||||
if i != l-1 {
|
if i != l-1 {
|
||||||
end = offsets[i+1]
|
end = nopOffsets[i+1]
|
||||||
if op := opcode.Opcode(b[offsets[i+1]]); op != opcode.INITSLOT {
|
|
||||||
end += 2
|
|
||||||
b[offsets[i+1]] = byte(toShortForm(op))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
copy(b[start-copyOffset:], b[start+3:end])
|
copy(b[start-copyOffset:], b[start+1:end])
|
||||||
copyOffset += longToShortRemoveCount
|
copyOffset++
|
||||||
}
|
}
|
||||||
return b[:len(b)-copyOffset]
|
return b[:len(b)-copyOffset]
|
||||||
}
|
}
|
||||||
|
@ -2392,9 +2398,8 @@ func calcOffsetCorrection(ip, target int, offsets []int) int {
|
||||||
})
|
})
|
||||||
for i := start; i < len(offsets) && (offsets[i] < target || offsets[i] <= ip); i++ {
|
for i := start; i < len(offsets) && (offsets[i] < target || offsets[i] <= ip); i++ {
|
||||||
ind := offsets[i]
|
ind := offsets[i]
|
||||||
if ip <= ind && ind < target ||
|
if ip <= ind && ind < target || target <= ind && ind < ip {
|
||||||
ind != ip && target <= ind && ind <= ip {
|
cnt++
|
||||||
cnt += longToShortRemoveCount
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ip < target {
|
if ip < target {
|
||||||
|
|
|
@ -21,12 +21,15 @@ import (
|
||||||
// <inline body of f directly>
|
// <inline body of f directly>
|
||||||
// }
|
// }
|
||||||
func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
||||||
labelSz := len(c.labelList)
|
offSz := len(c.inlineContext)
|
||||||
offSz := len(c.inlineLabelOffsets)
|
c.inlineContext = append(c.inlineContext, inlineContextSingle{
|
||||||
c.inlineLabelOffsets = append(c.inlineLabelOffsets, labelSz)
|
labelOffset: len(c.labelList),
|
||||||
|
returnLabel: c.newLabel(),
|
||||||
|
})
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
c.inlineLabelOffsets = c.inlineLabelOffsets[:offSz]
|
c.labelList = c.labelList[:c.inlineContext[offSz].labelOffset]
|
||||||
c.labelList = c.labelList[:labelSz]
|
c.inlineContext = c.inlineContext[:offSz]
|
||||||
}()
|
}()
|
||||||
|
|
||||||
pkg := c.packageCache[f.pkg.Path()]
|
pkg := c.packageCache[f.pkg.Path()]
|
||||||
|
@ -113,6 +116,7 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
||||||
c.fillImportMap(f.file, pkg)
|
c.fillImportMap(f.file, pkg)
|
||||||
ast.Inspect(f.decl, c.scope.analyzeVoidCalls)
|
ast.Inspect(f.decl, c.scope.analyzeVoidCalls)
|
||||||
ast.Walk(c, f.decl.Body)
|
ast.Walk(c, f.decl.Body)
|
||||||
|
c.setLabel(c.inlineContext[offSz].returnLabel)
|
||||||
if c.scope.voidCalls[n] {
|
if c.scope.voidCalls[n] {
|
||||||
for i := 0; i < f.decl.Type.Results.NumFields(); i++ {
|
for i := 0; i < f.decl.Type.Results.NumFields(); i++ {
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
|
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
|
||||||
|
|
|
@ -374,3 +374,46 @@ func TestInlinedMethodWithPointer(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
eval(t, src, big.NewInt(100542))
|
eval(t, src, big.NewInt(100542))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInlineConditionalReturn(t *testing.T) {
|
||||||
|
srcTmpl := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/c"
|
||||||
|
func Main() int {
|
||||||
|
x := %d
|
||||||
|
if c.Is42(x) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}`
|
||||||
|
t.Run("true", func(t *testing.T) {
|
||||||
|
eval(t, fmt.Sprintf(srcTmpl, 123), big.NewInt(10))
|
||||||
|
})
|
||||||
|
t.Run("false", func(t *testing.T) {
|
||||||
|
eval(t, fmt.Sprintf(srcTmpl, 42), big.NewInt(100))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineDoubleConditionalReturn(t *testing.T) {
|
||||||
|
srcTmpl := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/c"
|
||||||
|
func Main() int {
|
||||||
|
return c.Transform(%d, %d)
|
||||||
|
}`
|
||||||
|
|
||||||
|
testCase := []struct {
|
||||||
|
name string
|
||||||
|
a, b, result int
|
||||||
|
}{
|
||||||
|
{"true, true, small", 42, 3, 6},
|
||||||
|
{"true, true, big", 42, 15, 15},
|
||||||
|
{"true, false", 42, 42, 42},
|
||||||
|
{"false, true", 3, 11, 6},
|
||||||
|
{"false, false", 3, 42, 6},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCase {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
eval(t, fmt.Sprintf(srcTmpl, tc.a, tc.b), big.NewInt(int64(tc.result)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ func testShortenJumps(t *testing.T, before, after []opcode.Opcode, indices []int
|
||||||
for i := range before {
|
for i := range before {
|
||||||
prog[i] = byte(before[i])
|
prog[i] = byte(before[i])
|
||||||
}
|
}
|
||||||
raw := shortenJumps(prog, indices)
|
raw := removeNOPs(prog, indices)
|
||||||
actual := make([]opcode.Opcode, len(raw))
|
actual := make([]opcode.Opcode, len(raw))
|
||||||
for i := range raw {
|
for i := range raw {
|
||||||
actual[i] = opcode.Opcode(raw[i])
|
actual[i] = opcode.Opcode(raw[i])
|
||||||
|
@ -36,53 +36,53 @@ func TestShortenJumps(t *testing.T) {
|
||||||
for op, sop := range testCases {
|
for op, sop := range testCases {
|
||||||
t.Run(op.String(), func(t *testing.T) {
|
t.Run(op.String(), func(t *testing.T) {
|
||||||
before := []opcode.Opcode{
|
before := []opcode.Opcode{
|
||||||
op, 6, 0, 0, 0, opcode.PUSH1, opcode.NOP, // <- first jump to here
|
sop, 6, opcode.NOP, opcode.NOP, opcode.NOP, opcode.PUSH1, opcode.NOP, // <- first jump to here
|
||||||
op, 9, 12, 0, 0, opcode.PUSH1, opcode.NOP, // <- last jump to here
|
op, 9, 12, 0, 0, opcode.PUSH1, opcode.NOP, // <- last jump to here
|
||||||
op, 255, 0, 0, 0, op, 0xFF - 5, 0xFF, 0xFF, 0xFF,
|
sop, 249, opcode.NOP, opcode.NOP, opcode.NOP, sop, 0xFF - 5, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
}
|
}
|
||||||
after := []opcode.Opcode{
|
after := []opcode.Opcode{
|
||||||
sop, 3, opcode.PUSH1, opcode.NOP,
|
sop, 3, opcode.PUSH1, opcode.NOP,
|
||||||
op, 3, 12, 0, 0, opcode.PUSH1, opcode.NOP,
|
op, 3, 12, 0, 0, opcode.PUSH1, opcode.NOP,
|
||||||
sop, 249, sop, 0xFF - 2,
|
sop, 249, sop, 0xFF - 2,
|
||||||
}
|
}
|
||||||
testShortenJumps(t, before, after, []int{0, 14, 19})
|
testShortenJumps(t, before, after, []int{2, 3, 4, 16, 17, 18, 21, 22, 23})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
t.Run("NoReplace", func(t *testing.T) {
|
t.Run("NoReplace", func(t *testing.T) {
|
||||||
b := []byte{0, 1, 2, 3, 4, 5}
|
b := []byte{0, 1, 2, 3, 4, 5}
|
||||||
expected := []byte{0, 1, 2, 3, 4, 5}
|
expected := []byte{0, 1, 2, 3, 4, 5}
|
||||||
require.Equal(t, expected, shortenJumps(b, nil))
|
require.Equal(t, expected, removeNOPs(b, nil))
|
||||||
})
|
})
|
||||||
t.Run("InvalidIndex", func(t *testing.T) {
|
t.Run("InvalidIndex", func(t *testing.T) {
|
||||||
before := []byte{byte(opcode.PUSH1), 0, 0, 0, 0}
|
before := []byte{byte(opcode.PUSH1), 0, 0, 0, 0}
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
shortenJumps(before, []int{0})
|
removeNOPs(before, []int{0})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.Run("SideConditions", func(t *testing.T) {
|
t.Run("SideConditions", func(t *testing.T) {
|
||||||
t.Run("Forward", func(t *testing.T) {
|
t.Run("Forward", func(t *testing.T) {
|
||||||
before := []opcode.Opcode{
|
before := []opcode.Opcode{
|
||||||
opcode.JMPL, 5, 0, 0, 0,
|
opcode.JMP, 5, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
opcode.JMPL, 5, 0, 0, 0,
|
opcode.JMP, 5, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
}
|
}
|
||||||
after := []opcode.Opcode{
|
after := []opcode.Opcode{
|
||||||
opcode.JMP, 2,
|
opcode.JMP, 2,
|
||||||
opcode.JMP, 2,
|
opcode.JMP, 2,
|
||||||
}
|
}
|
||||||
testShortenJumps(t, before, after, []int{0, 5})
|
testShortenJumps(t, before, after, []int{2, 3, 4, 7, 8, 9})
|
||||||
})
|
})
|
||||||
t.Run("Backwards", func(t *testing.T) {
|
t.Run("Backwards", func(t *testing.T) {
|
||||||
before := []opcode.Opcode{
|
before := []opcode.Opcode{
|
||||||
opcode.JMPL, 5, 0, 0, 0,
|
opcode.JMP, 5, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
opcode.JMPL, 0xFF - 4, 0xFF, 0xFF, 0xFF,
|
opcode.JMP, 0xFF - 4, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
opcode.JMPL, 0xFF - 4, 0xFF, 0xFF, 0xFF,
|
opcode.JMP, 0xFF - 4, opcode.NOP, opcode.NOP, opcode.NOP,
|
||||||
}
|
}
|
||||||
after := []opcode.Opcode{
|
after := []opcode.Opcode{
|
||||||
opcode.JMPL, 5, 0, 0, 0,
|
opcode.JMP, 2,
|
||||||
opcode.JMP, 0xFF - 4,
|
opcode.JMP, 0xFF - 1,
|
||||||
opcode.JMP, 0xFF - 1,
|
opcode.JMP, 0xFF - 1,
|
||||||
}
|
}
|
||||||
testShortenJumps(t, before, after, []int{5, 10})
|
testShortenJumps(t, before, after, []int{2, 3, 4, 7, 8, 9, 12, 13, 14})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
22
pkg/compiler/testdata/inline/c/null.go
vendored
Normal file
22
pkg/compiler/testdata/inline/c/null.go
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package c
|
||||||
|
|
||||||
|
func Is42(a int) bool {
|
||||||
|
if a == 42 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func MulIfSmall(n int) int {
|
||||||
|
if n < 10 {
|
||||||
|
return n * 2
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func Transform(a, b int) int {
|
||||||
|
if Is42(a) && !Is42(b) {
|
||||||
|
return MulIfSmall(b)
|
||||||
|
}
|
||||||
|
return MulIfSmall(a)
|
||||||
|
}
|
Loading…
Reference in a new issue