Skip to content

Commit 2e73e37

Browse files
authored
all: allow calling quasigo functions from quasigo (#378)
Now it's possible to call one bytecode function from another.
1 parent dbd4b2c commit 2e73e37

15 files changed

+301
-132
lines changed

analyzer/testdata/src/quasigo/rules.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build ignore
12
// +build ignore
23

34
package gorules
@@ -7,12 +8,16 @@ import (
78
"github.com/quasilyte/go-ruleguard/dsl/types"
89
)
910

11+
func derefPointer(ptr *types.Pointer) *types.Pointer {
12+
return types.AsPointer(ptr.Elem())
13+
}
14+
1015
func tooManyPointers(ctx *dsl.VarFilterContext) bool {
1116
indir := 0
1217
ptr := types.AsPointer(ctx.Type)
1318
for ptr != nil {
1419
indir++
15-
ptr = types.AsPointer(ptr.Elem())
20+
ptr = derefPointer(ptr)
1621
}
1722
return indir >= 3
1823
}
@@ -33,11 +38,16 @@ func isPointer(ctx *dsl.VarFilterContext) bool {
3338
return ptr != nil
3439
}
3540

36-
func isInterface(ctx *dsl.VarFilterContext) bool {
41+
func isInterfaceImpl(ctx *dsl.VarFilterContext) bool {
3742
// Nil can be used on either side.
3843
return nil != types.AsInterface(ctx.Type.Underlying())
3944
}
4045

46+
func isInterface(ctx *dsl.VarFilterContext) bool {
47+
// Forwarding a call to other function.
48+
return isInterfaceImpl(ctx)
49+
}
50+
4151
func isError(ctx *dsl.VarFilterContext) bool {
4252
// Testing Interface.String() method.
4353
iface := types.AsInterface(ctx.Type.Underlying())

ruleguard/ir_loader.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,10 @@ func (l *irLoader) compileFilterFuncs(filename string, irfile *ir.File) error {
206206
continue
207207
}
208208
ctx := &quasigo.CompileContext{
209-
Env: l.state.env,
210-
Types: f.Types,
211-
Fset: fset,
209+
Env: l.state.env,
210+
Package: f.Pkg,
211+
Types: f.Types,
212+
Fset: fset,
212213
}
213214
compiled, err := quasigo.Compile(ctx, decl)
214215
if err != nil {

ruleguard/quasigo/compile.go

+45-10
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,12 @@ func (cl *compiler) compileFunc(fn *ast.FuncDecl) *Func {
124124
}
125125

126126
compiled := &Func{
127-
code: cl.code,
128-
constants: cl.constants,
129-
intConstants: cl.intConstants,
127+
code: cl.code,
128+
constants: cl.constants,
129+
intConstants: cl.intConstants,
130+
numObjectParams: len(cl.params),
131+
numIntParams: len(cl.intParams),
132+
name: cl.ctx.Package.Path() + "." + fn.Name.String(),
130133
}
131134
if len(cl.locals) != 0 {
132135
dbg.localNames = make([]string, len(cl.locals))
@@ -575,19 +578,51 @@ func (cl *compiler) compileCallExpr(call *ast.CallExpr) {
575578
if sig.Variadic() {
576579
variadic = sig.Params().Len() - 1
577580
}
578-
if !cl.compileNativeCall(key, variadic, expr, call.Args) {
579-
panic(cl.errorf(call.Fun, "can't compile a call to %s func", key))
581+
if expr != nil {
582+
cl.compileExpr(expr)
583+
}
584+
if cl.compileNativeCall(key, variadic, expr, call.Args) {
585+
return
580586
}
587+
if cl.compileCall(key, sig, call.Args) {
588+
return
589+
}
590+
panic(cl.errorf(call.Fun, "can't compile a call to %s func", key))
581591
}
582592

583-
func (cl *compiler) compileNativeCall(key funcKey, variadic int, expr ast.Expr, args []ast.Expr) bool {
584-
funcID, ok := cl.ctx.Env.nameToNativeFuncID[key]
593+
func (cl *compiler) compileCall(key funcKey, sig *types.Signature, args []ast.Expr) bool {
594+
if sig.Variadic() {
595+
return false
596+
}
597+
598+
funcID, ok := cl.ctx.Env.nameToFuncID[key]
585599
if !ok {
586600
return false
587601
}
588-
if expr != nil {
589-
cl.compileExpr(expr)
602+
603+
for _, arg := range args {
604+
cl.compileExpr(arg)
605+
}
606+
607+
var op opcode
608+
if sig.Results().Len() == 0 {
609+
op = opVoidCall
610+
} else if typeIsInt(sig.Results().At(0).Type()) {
611+
op = opIntCall
612+
} else {
613+
op = opCall
590614
}
615+
616+
cl.emit16(op, int(funcID))
617+
return true
618+
}
619+
620+
func (cl *compiler) compileNativeCall(key funcKey, variadic int, funcExpr ast.Expr, args []ast.Expr) bool {
621+
funcID, ok := cl.ctx.Env.nameToNativeFuncID[key]
622+
if !ok {
623+
return false
624+
}
625+
591626
if len(args) == 1 {
592627
// Check that it's not a f(g()) call, where g() returns
593628
// a multi-value result; we can't compile that yet.
@@ -619,7 +654,7 @@ func (cl *compiler) compileNativeCall(key funcKey, variadic int, expr ast.Expr,
619654
}
620655
}
621656
if len(variadicArgs) > 255 {
622-
panic(cl.errorf(expr, "too many variadic args"))
657+
panic(cl.errorf(funcExpr, "too many variadic args"))
623658
}
624659
// Even if len(variadicArgs) is 0, we still need to overwrite
625660
// the old variadicLen value, so the variadic func is not confused

ruleguard/quasigo/compile_test.go

+44-27
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,28 @@ func TestCompile(t *testing.T) {
358358
` PushLocal 0 # v`,
359359
` ReturnTop`,
360360
},
361+
362+
`return add1(10)`: {
363+
` PushIntConst 0 # value=10`,
364+
` IntCall 0 # testpkg.add1`,
365+
` ReturnIntTop`,
366+
},
367+
368+
`return concat(concat("x", "y"), "z")`: {
369+
` PushConst 0 # value="x"`,
370+
` PushConst 1 # value="y"`,
371+
` Call 1 # testpkg.concat`,
372+
` PushConst 2 # value="z"`,
373+
` Call 1 # testpkg.concat`,
374+
` ReturnTop`,
375+
},
361376
}
362377

363378
makePackageSource := func(body string) string {
364379
return `
365-
package testpkg
380+
package ` + testPackage + `
381+
func add1(x int) int { return x + 1 }
382+
func concat(s1, s2 string) string { return s1 + s2 }
366383
func f(i int, s string, b bool, eface interface{}) interface{} {
367384
` + body + `
368385
}
@@ -373,37 +390,37 @@ func TestCompile(t *testing.T) {
373390
`
374391
}
375392

376-
env := quasigo.NewEnv()
377-
env.AddNativeFunc(testPackage, "imul", func(stack *quasigo.ValueStack) {
378-
panic("should not be called")
379-
})
380-
env.AddNativeFunc(testPackage, "idiv", func(stack *quasigo.ValueStack) {
381-
panic("should not be called")
382-
})
383-
env.AddNativeFunc(testPackage, "atoi", func(stack *quasigo.ValueStack) {
384-
panic("should not be called")
385-
})
386-
env.AddNativeFunc(testPackage, "sprintf", func(stack *quasigo.ValueStack) {
387-
panic("should not be called")
388-
})
389-
env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) {
390-
panic("should not be called")
391-
})
392-
env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) {
393-
panic("should not be called")
394-
})
395-
396393
for testSrc, disasmLines := range tests {
394+
env := quasigo.NewEnv()
395+
env.AddNativeFunc(testPackage, "imul", func(stack *quasigo.ValueStack) {
396+
panic("should not be called")
397+
})
398+
env.AddNativeFunc(testPackage, "idiv", func(stack *quasigo.ValueStack) {
399+
panic("should not be called")
400+
})
401+
env.AddNativeFunc(testPackage, "atoi", func(stack *quasigo.ValueStack) {
402+
panic("should not be called")
403+
})
404+
env.AddNativeFunc(testPackage, "sprintf", func(stack *quasigo.ValueStack) {
405+
panic("should not be called")
406+
})
407+
env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) {
408+
panic("should not be called")
409+
})
410+
env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) {
411+
panic("should not be called")
412+
})
397413
src := makePackageSource(testSrc)
398-
parsed, err := parseGoFile(src)
414+
parsed, err := parseGoFile(testPackage, src)
399415
if err != nil {
400-
t.Errorf("parse %s: %v", testSrc, err)
401-
continue
416+
t.Fatalf("parse %s: %v", testSrc, err)
402417
}
403-
compiled, err := compileTestFunc(env, "f", parsed)
418+
compiled, err := compileTestFile(env, "f", testPackage, parsed)
404419
if err != nil {
405-
t.Errorf("compile %s: %v", testSrc, err)
406-
continue
420+
t.Fatal(err)
421+
}
422+
if compiled == nil {
423+
t.Fatal("can't find f function")
407424
}
408425
want := disasmLines
409426
have := strings.Split(quasigo.Disasm(env, compiled), "\n")

ruleguard/quasigo/disasm.go

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ func disasm(env *Env, fn *Func) string {
3939
id := decode16(code, pc+1)
4040
arg = id
4141
comment = env.nativeFuncs[id].name
42+
case opCall, opIntCall, opVoidCall:
43+
id := decode16(code, pc+1)
44+
arg = id
45+
comment = env.userFuncs[id].name
4246
case opPushParam:
4347
index := int(code[pc+1])
4448
arg = index

ruleguard/quasigo/eval.go

+17
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,23 @@ func eval(env *EvalEnv, fn *Func, top, intTop int) CallResult {
129129
fn := env.nativeFuncs[id].mappedFunc
130130
fn(stack)
131131
pc += 3
132+
case opCall:
133+
id := decode16(code, pc+1)
134+
fn := env.userFuncs[id]
135+
result := eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams)
136+
stack.Push(result.Value())
137+
pc += 3
138+
case opIntCall:
139+
id := decode16(code, pc+1)
140+
fn := env.userFuncs[id]
141+
result := eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams)
142+
stack.PushInt(result.IntValue())
143+
pc += 3
144+
case opVoidCall:
145+
id := decode16(code, pc+1)
146+
fn := env.userFuncs[id]
147+
eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams)
148+
pc += 3
132149

133150
case opJump:
134151
offset := decode16(code, pc+1)

ruleguard/quasigo/eval_bench_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func pushArgs(env *quasigo.EvalEnv, args ...interface{}) {
143143
func compileBenchFunc(t testing.TB, paramsSig, bodySrc string) (*quasigo.Env, *quasigo.Func) {
144144
makePackageSource := func(body string) string {
145145
return `
146-
package test
146+
package ` + testPackage + `
147147
import "fmt"
148148
var _ = fmt.Sprintf
149149
func f(` + paramsSig + `) interface{} {
@@ -161,7 +161,7 @@ func compileBenchFunc(t testing.TB, paramsSig, bodySrc string) (*quasigo.Env, *q
161161
})
162162
qfmt.ImportAll(env)
163163
src := makePackageSource(bodySrc)
164-
parsed, err := parseGoFile(src)
164+
parsed, err := parseGoFile(testPackage, src)
165165
if err != nil {
166166
t.Fatalf("parse %s: %v", bodySrc, err)
167167
}

ruleguard/quasigo/eval_test.go

+6-22
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7-
"go/ast"
87
"io/ioutil"
98
"os"
109
"os/exec"
@@ -170,7 +169,7 @@ func TestEval(t *testing.T) {
170169
t.Fatalf("unexpected result type: %T", result)
171170
}
172171
return `
173-
package test
172+
package ` + testPackage + `
174173
import "github.com/quasilyte/go-ruleguard/ruleguard/quasigo/internal/evaltest"
175174
func target(i int, s string, b bool, foo, nilfoo *evaltest.Foo, nileface interface{}) ` + returnType + ` {
176175
` + body + `
@@ -213,7 +212,7 @@ func TestEval(t *testing.T) {
213212
for i := range tests {
214213
test := tests[i]
215214
src := makePackageSource(test.src, test.result)
216-
parsed, err := parseGoFile(src)
215+
parsed, err := parseGoFile(testPackage, src)
217216
if err != nil {
218217
t.Fatalf("parse %s: %v", test.src, err)
219218
}
@@ -261,7 +260,7 @@ func TestEvalFile(t *testing.T) {
261260
return "", err
262261
}
263262
env := quasigo.NewEnv()
264-
parsed, err := parseGoFile(string(src))
263+
parsed, err := parseGoFile("main", string(src))
265264
if err != nil {
266265
return "", fmt.Errorf("parse: %v", err)
267266
}
@@ -284,24 +283,9 @@ func TestEvalFile(t *testing.T) {
284283
qstrconv.ImportAll(env)
285284
qfmt.ImportAll(env)
286285

287-
var mainFunc *quasigo.Func
288-
for _, decl := range parsed.ast.Decls {
289-
decl, ok := decl.(*ast.FuncDecl)
290-
if !ok {
291-
continue
292-
}
293-
ctx := &quasigo.CompileContext{
294-
Env: env,
295-
Types: parsed.types,
296-
Fset: parsed.fset,
297-
}
298-
fn, err := quasigo.Compile(ctx, decl)
299-
if err != nil {
300-
return "", fmt.Errorf("compile %s func: %v", decl.Name, err)
301-
}
302-
if decl.Name.String() == "main" {
303-
mainFunc = fn
304-
}
286+
mainFunc, err := compileTestFile(env, "main", "main", parsed)
287+
if err != nil {
288+
return "", err
305289
}
306290
if mainFunc == nil {
307291
return "", errors.New("can't find main() function")

ruleguard/quasigo/gen_opcodes.go

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ var opcodePrototypes = []opcodeProto{
4545

4646
{"SetVariadicLen", "op len:u8", stackUnchanged},
4747
{"CallNative", "op funcid:u16", "(args...) -> (results...)"},
48+
{"Call", "op funcid:u16", "(args...) -> (result)"},
49+
{"IntCall", "op funcid:u16", "(args...) -> (result:int)"},
50+
{"VoidCall", "op funcid:u16", "(args...) -> ()"},
4851

4952
{"IsNil", "op", "(value) -> (result:bool)"},
5053
{"IsNotNil", "op", "(value) -> (result:bool)"},

0 commit comments

Comments
 (0)