Go+ SSA 脚本引擎简介
七叶
2021.12.22
七叶
2021.12.22
gossa 是一个基于 SSA 实现的 Go 语言解释器,可以直接从 Go/Go+ 源码运行程序。
pkg.go.dev/github.com/goplus/gossa
github.com/visualfc/gossa_demo
Go runtime 内存布局兼容性
func / var / const / struct / interface / methods
go run main.go
package main import "fmt" type T struct { n int } func (t *T) String() string { return fmt.Sprintf("T:%v", t.n) } func main() { fmt.Println("Hello World", &T{100}) }
T 实现了 fmt.Stringer 接口
yaegi run main.go
package main import "fmt" type T struct { n int } func (t *T) String() string { return fmt.Sprintf("T:%v", t.n) } func main() { fmt.Println("Hello World", &T{100}) }
yaegi 中 T 不兼容 fmt.Stringer 接口,打印原始值。
gossa run main.go
package main import "fmt" type T struct { n int } func (t *T) String() string { return fmt.Sprintf("T:%v", t.n) } func main() { fmt.Println("Hello World", &T{100}) }
gossa 中 T 兼容 fmt.Stringer 接口,正确输出。
支持 Go 语言规范 https://go.dev/ref/spec
内建支持 Go+ 源码运行
与 Go runtime 保持内存布局兼容
func / var / const / struct / interface / methods
无依赖执行
编译后不需要 Go 平台支持,独立运行和执行 Go/Go+ 源码。
自定义导入包
根据需要自定义允许使用的导入包,标准库可剪裁。提供 qexp 工具,方便自动生成需要的导入包。
多平台支持
Native 平台 Go1.16/Go1.17 (amd64 下需要设置 GOEXPERIMENT=noregabi) WASM/GopherJS 平台支持
目前只支持单个 package 的读取和运行。
只允许 gossa/interp 单个实例运行。
不支持 ASM,CGO,go:linkname symbol。
Go1.17 amd64 需要设置 GOEXPERIMENT=noregabi 编译。
GOEXPERIMENT=noregabi go build demo
Go1.18 amd64 默认使用 regabi 目前无法运行。
typed methods 数量限制,预分配 256 个。
import _ "github.com/reflectx/icall/icall[2^n]"
使用 reflectx/cmd/icall_gen 生成需要的 methods
GopherJS 支持 ( go 1.12 ~ go1.16 )
go run main.go
package main import "fmt" func main() { fmt.Println("Hello, World") }
gop run main.go
println "Hello, World"
go run -x main.go
package main import "fmt" func main() { fmt.Println("Hello, World") }
gossa run main.go
package main import "fmt" func main() { fmt.Println("Hello, World") }
gossa run main.gop
println "Hello, World"
gossa package
github.com/goplus/gossa
Go+ 编译支持
github.com/goplus/gossa/gopbuild
gossa 导入包
github.com/goplus/gossa/pkg/...
github.com/goplus/gossa/tree/main/pkg
gossa 命令程序,测试用
github.com/goplus/gossa/cmd/gossa
gossa 导入库生成工具
github.com/goplus/gossa/cmd/qexp
通常情况不会直接使用 cmd/gossa ,而是基于 github.com/goplus/gossa
做自定义应用程序。
import (
"github.com/goplus/gossa"
_ "github.com/goplus/gossa/pkg/fmt"
_ "github.com/goplus/gossa/pkg/io"
_ "github.com/goplus/gossa/pkg/os"
。。。
)
package main import ( "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") } ` func main() { _, err := gossa.RunFile("main.go", source, nil, 0) if err != nil { panic(err) } }
package main import ( "github.com/goplus/gossa" _ "github.com/goplus/gossa/gopbuild" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` println "Hello, World" ` func main() { _, err := gossa.RunFile("main.gop", source, nil, 0) if err != nil { panic(err) } }
package main import ( "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" type T struct {} func (t T) String() string { return "Hello, World" } func main() { fmt.Println(&T{}) } ` func main() { _, err := gossa.RunFile("main.go", source, nil, 0) if err != nil { panic(err) } }
type Stringer interface { String() string }
package main import ( "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" type M struct { N int} type T struct { N int} func (t T) String() string { return "T:String" } type U struct { T } func (u U) GoString() string { return "U:GoString" } func main() { m := &M{100} t := &T{100} u := &U{T{100}} fmt.Printf("%v %#v\n",m,m) fmt.Printf("%v %#v\n",t,t) fmt.Printf("%v %#v\n",u,u) } ` func main() { _, err := gossa.RunFile("main.go", source, nil, 0) if err != nil { panic(err) } }
package main import ( "fmt" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" _ "github.com/goplus/gossa/pkg/os" ) var source = ` package main import ( "fmt" "os" ) func main() { fmt.Println("Hello, World") os.Exit(2) } ` func main() { code, err := gossa.RunFile("main.go", source, nil, 0) if err != nil { panic(err) } fmt.Println("exit", code) }
package main import ( "fmt" "github.com/goplus/gossa" _ "github.com/goplus/gossa/gopbuild" _ "github.com/goplus/gossa/pkg/os" ) var source = ` import "os" println "Hello, World" os.exit(2) ` func main() { code, err := gossa.RunFile("main.gop", source, nil, 0) if err != nil { panic(err) } fmt.Println("exit code", code) }
package main import ( "fmt" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") panic("error") } ` func main() { _, err := gossa.RunFile("main.go", source, nil, 0) if err != nil { fmt.Println("PANIC:", err) } }
package main import ( "fmt" "github.com/goplus/gossa" _ "github.com/goplus/gossa/gopbuild" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` var i int println 100/i ` func main() { _, err := gossa.RunFile("main.gop", source, nil, 0) if err != nil { fmt.Println("PANIC:", err) } }
gossa.Context 上下文/环境控制
LoadFile/LoadAst/RunPkg/TestPkg/NewInterp 。。。
gossa.Interp 执行引擎
Run/RunFunc/GetFunc 。。。
package main import ( "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } _, err = ctx.RunPkg(pkg, "main.go", nil) if err != nil { log.Panicln("run", err) } }
package main import ( "go/parser" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") } ` func main() { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "main.go", source, parser.ParseComments) if err != nil { log.Panicln("parse", err) } ctx := gossa.NewContext(0) pkg, err := ctx.LoadAstFile(fset, f) if err != nil { log.Panicln("load", err) } _, err = ctx.RunPkg(pkg, "main.go", nil) if err != nil { log.Panicln("run", err) } }
package main import ( "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } _, err = interp.Run("main") if err != nil { log.Panicln("run", err) } }
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package main import "fmt" func main() { fmt.Println("Hello, World") } func add(i, j int) int { return i+j } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } v, err := interp.RunFunc("add", 100, 200) fmt.Println(v, err) }
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package bar func sum(n ...int) (r int) { for _, v := range n { r += v } return } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } v1, err := interp.RunFunc("sum", []int{100, 200}) fmt.Println(v1, err) v2, err := interp.RunFunc("sum", []int{}) fmt.Println(v2, err) // error call v3, err := interp.RunFunc("sum") fmt.Println(v3, err) }
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package bar func call(i, j int) (int,int) { return i+j, i*j } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } v, err := interp.RunFunc("call", 100, 200) fmt.Println(v, err) if t, ok := v.(gossa.Tuple); ok { fmt.Println(len(t), t[0], t[1]) } }
Run/RunFunc 函数带有错误处理功能。
GetFunc 函数
GetVarAddr 变量地址
GetConst 常量
GetType 类型
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package bar func add(i, j int) int { return i+j } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } if v, ok := interp.GetFunc("add"); ok { if fn, ok := v.(func(int, int) int); ok { r := fn(100, 200) fmt.Println(r) } } }
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package bar var index int = 100 var pindex *int = &index func show() { println(index) } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } if v, ok := interp.GetVarAddr("index"); ok { if p, ok := v.(*int); ok { fmt.Println(*p) *p = 200 } } if v, ok := interp.GetVarAddr("pindex"); ok { if p, ok := v.(**int); ok { fmt.Println(**p) **p = 300 } } if fn, ok := interp.GetFunc("show"); ok { fn.(func())() } }
package main import ( "fmt" "go/token" "log" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" _ "github.com/goplus/gossa/pkg/math" ) var source = ` package bar import "math" const Pi = math.Pi func add(i, j int) int { return i+j } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } // go/constant.Value if v, ok := interp.GetConst("Pi"); ok { fmt.Printf("%v %T\n", v, v) fmt.Println(v.ExactString()) } }
package main import ( "fmt" "go/token" "log" "reflect" "github.com/goplus/gossa" _ "github.com/goplus/gossa/pkg/fmt" ) var source = ` package bar import "fmt" type Point struct { x, y int } func (p *Point) Set(x, y int) { p.x,p.y = x,y } func (p *Point) String() string { return fmt.Sprintf("(%v,%v)",p.x,p.y) } ` func main() { fset := token.NewFileSet() ctx := gossa.NewContext(0) pkg, err := ctx.LoadFile(fset, "main.go", source) if err != nil { log.Panicln("load", err) } interp, err := ctx.NewInterp(pkg) if err != nil { log.Panicln("interp", err) } if typ, ok := interp.GetType("Point"); ok { v := reflect.New(typ) fn := v.MethodByName("Set") fn.Interface().(func(int, int))(100, 200) fmt.Println(v) } }
写一个 ispx 程序运行 github.com/goplus/spx 应用 ( Go+ class file )
ispx 项目地址
安装 ispx
$ go get github.com/goplus/ispx
下载 FlappyCalf
$ git clone https://github.com/goplus/FlappyCalf
运行方式 1
$ ispx FlappyCalf
运行方式 2
$ cd FlappyCalf
$ ispx .
package main //go:generate qexp -outdir pkg github.com/goplus/spx import ( "flag" "fmt" "log" "os" "path/filepath" "github.com/goplus/gossa" "github.com/goplus/gossa/gopbuild" "github.com/goplus/spx" _ "github.com/goplus/gossa/pkg/fmt" _ "github.com/goplus/gossa/pkg/math" _ "github.com/goplus/ispx/pkg/github.com/goplus/spx" _ "github.com/goplus/reflectx/icall/icall8192" ) var ( flagDumpSrc bool flagDumpPkg bool flagDumpSSA bool ) func init() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "ispc [-dumpsrc|-dumppkg|-dumpssa] dir\n") flag.PrintDefaults() } flag.BoolVar(&flagDumpSrc, "dumpsrc", false, "print source code") flag.BoolVar(&flagDumpPkg, "dumppkg", false, "print import packages") flag.BoolVar(&flagDumpSSA, "dumpssa", false, "print ssa code information") } func main() { flag.Parse() args := flag.Args() if len(args) != 1 { flag.Usage() return } path := args[0] var mode gossa.Mode if flagDumpPkg { mode |= gossa.EnableDumpPackage } if flagDumpSSA { mode |= gossa.EnableTracing } ctx := gossa.NewContext(mode) data, err := gopbuild.BuildDir(ctx, path) if err != nil { log.Panicln(err) } if flagDumpSrc { fmt.Println(string(data)) } if !filepath.IsAbs(path) { dir, _ := os.Getwd() path = filepath.Join(dir, path) } gossa.RegisterExternal("github.com/goplus/spx.Gopt_Game_Run", func(game spx.Gamer, resource interface{}, gameConf ...*spx.Config) { os.Chdir(path) spx.Gopt_Game_Run(game, resource, gameConf...) }) _, err = ctx.RunFile("main.go", data, nil) if err != nil { log.Panicln(err) } }
$ ispx -dumppkg .
$ ispx -dumppkg . 2021/12/23 08:27:56 imported package spx ("github.com/goplus/spx") 2021/12/23 08:27:56 imported package math ("math") 2021/12/23 08:27:56 indirect package camera ("github.com/goplus/spx/internal/camera") 2021/12/23 08:27:56 indirect package tools ("github.com/goplus/spx/internal/tools") 2021/12/23 08:27:56 indirect package atlas ("github.com/hajimehoshi/ebiten/v2/internal/atlas") 2021/12/23 08:27:56 indirect package graphicscommand ("github.com/hajimehoshi/ebiten/v2/internal/graphicscommand") 2021/12/23 08:27:56 indirect package constant ("go/constant") 2021/12/23 08:27:56 indirect package fs ("github.com/goplus/spx/fs") 2021/12/23 08:27:56 indirect package math32 ("github.com/goplus/spx/internal/math32") 2021/12/23 08:27:56 imported package fmt ("fmt") 2021/12/23 08:27:56 indirect package rand ("math/rand") 2021/12/23 08:27:56 indirect package audio ("github.com/hajimehoshi/ebiten/v2/audio") 2021/12/23 08:27:56 indirect package reflect ("reflect") 2021/12/23 08:27:56 indirect package ebiten ("github.com/hajimehoshi/ebiten/v2") 2021/12/23 08:27:56 indirect package mipmap ("github.com/hajimehoshi/ebiten/v2/internal/mipmap") 2021/12/23 08:27:57 indirect package restorable ("github.com/hajimehoshi/ebiten/v2/internal/restorable") 2021/12/23 08:27:57 imported package strconv ("strconv") 2021/12/23 08:27:57 indirect package io ("io") 2021/12/23 08:27:57 imported package builtin ("github.com/goplus/gop/builtin") 2021/12/23 08:27:57 indirect package driver ("github.com/hajimehoshi/ebiten/v2/internal/driver") 2021/12/23 08:27:57 indirect package v2 ("github.com/hajimehoshi/oto/v2") 2021/12/23 08:27:57 imported package unsafe ("unsafe") 2021/12/23 08:27:57 imported package strings ("strings") 2021/12/23 08:27:57 indirect package color ("image/color") 2021/12/23 08:27:57 indirect package time ("time") 2021/12/23 08:27:57 indirect package anim ("github.com/goplus/spx/internal/anim") 2021/12/23 08:27:57 indirect package buffered ("github.com/hajimehoshi/ebiten/v2/internal/buffered") 2021/12/23 08:27:57 indirect package affine ("github.com/hajimehoshi/ebiten/v2/internal/affine") 2021/12/23 08:27:57 indirect package shaderir ("github.com/hajimehoshi/ebiten/v2/internal/shaderir") 2021/12/23 08:27:57 indirect package packing ("github.com/hajimehoshi/ebiten/v2/internal/packing") 2021/12/23 08:27:57 indirect package gdi ("github.com/goplus/spx/internal/gdi") 2021/12/23 08:27:57 indirect package audiorecord ("github.com/goplus/spx/internal/audiorecord") 2021/12/23 08:27:57 imported package big ("math/big") 2021/12/23 08:27:57 indirect package sync ("sync") 2021/12/23 08:27:57 indirect package unicode ("unicode") 2021/12/23 08:27:57 indirect package image ("image") 2021/12/23 08:27:57 indirect package coroutine ("github.com/goplus/spx/internal/coroutine")
$ qexp -outdir pkg github.com/goplus/spx
// export by github.com/goplus/gossa/cmd/qexp package spx import ( q "github.com/goplus/spx" "go/constant" "reflect" "github.com/goplus/gossa" ) func init() { gossa.RegisterPackage(&gossa.Package{ Name: "spx", Path: "github.com/goplus/spx", Deps: map[string]string{ "encoding/json": "json", "errors": "errors", "flag": "flag", "fmt": "fmt", "github.com/goplus/spx/fs": "fs", "github.com/goplus/spx/fs/asset": "asset", "github.com/goplus/spx/fs/zip": "zip", "github.com/goplus/spx/internal/anim": "anim", "github.com/goplus/spx/internal/audiorecord": "audiorecord", "github.com/goplus/spx/internal/camera": "camera", "github.com/goplus/spx/internal/coroutine": "coroutine", "github.com/goplus/spx/internal/effect": "effect", "github.com/goplus/spx/internal/gdi": "gdi", "github.com/goplus/spx/internal/gdi/clrutil": "clrutil", "github.com/goplus/spx/internal/gdi/font": "font", "github.com/goplus/spx/internal/math32": "math32", "github.com/goplus/spx/internal/tools": "tools", "github.com/hajimehoshi/ebiten/v2": "ebiten", "github.com/hajimehoshi/ebiten/v2/audio": "audio", "github.com/pkg/errors": "errors", "github.com/qiniu/audio": "audio", "github.com/qiniu/audio/convert": "convert", "github.com/qiniu/audio/mp3": "mp3", "github.com/qiniu/audio/wav": "wav", "github.com/qiniu/audio/wav/adpcm": "adpcm", "golang.org/x/image/colornames": "colornames", "golang.org/x/image/font": "font", "image": "image", "image/color": "color", "image/jpeg": "jpeg", "image/png": "png", "io": "io", "log": "log", "math": "math", "math/rand": "rand", "os": "os", "path": "path", "path/filepath": "filepath", "reflect": "reflect", "strconv": "strconv", "strings": "strings", "sync": "sync", "sync/atomic": "atomic", "syscall": "syscall", "time": "time", "unsafe": "unsafe", }, Interfaces: map[string]reflect.Type{ "Gamer": reflect.TypeOf((*q.Gamer)(nil)).Elem(), "Shape": reflect.TypeOf((*q.Shape)(nil)).Elem(), }, NamedTypes: map[string]gossa.NamedType{ "Camera": {reflect.TypeOf((*q.Camera)(nil)).Elem(), "", "ChangeXYpos,On,SetXYpos,init,isWorldRange,render,screenToWorld,updateOnObj"}, "Config": {reflect.TypeOf((*q.Config)(nil)).Elem(), "", ""}, "EffectKind": {reflect.TypeOf((*q.EffectKind)(nil)).Elem(), "String", ""}, "Game": {reflect.TypeOf((*q.Game)(nil)).Elem(), "", "Answer,Ask,Broadcast__0,Broadcast__1,Broadcast__2,ChangeEffect,ChangeVolume,ClearSoundEffects,Draw,EraseAll,HideVar,KeyPressed,Layout,Loudness,MouseHitItem,MousePressed,MouseX,MouseY,NextScene,Play__0,PrevScene,ResetTimer,SceneIndex,SceneName,SetEffect,SetVolume,ShowVar,StartScene,StopAllSounds,Timer,Update,Username,Volume,Wait,activateShape,addClonedShape,addShape,addSpecialShape,addStageSprite,addStageSprites,currentTPS,doBroadcast,doFindSprite,doWhenLeftButtonDown,doWindowSize,doWorldSize,drawBackground,endLoad,eventLoop,findSprite,fireEvent,getItems,getMousePos,getSharedImgs,getTurtle,getWidth,goBackByLayers,handleEvent,initEventLoop,initGame,loadIndex,loadSound,loadSprite,movePen,objectPos,onDraw,onHit,removeShape,reset,runLoop,setStageMonitor,stampCostume,startLoad,startTick,touchingPoint,touchingSpriteBy,updateMousePos,windowSize_,worldSize_"}, "List": {reflect.TypeOf((*q.List)(nil)).Elem(), "", "Append,At,Contains,Delete,Init,InitFrom,Insert,Len,Set,String"}, "MovingInfo": {reflect.TypeOf((*q.MovingInfo)(nil)).Elem(), "", "Dx,Dy,StopMoving"}, "RotationStyle": {reflect.TypeOf((*q.RotationStyle)(nil)).Elem(), "", ""}, "Sound": {reflect.TypeOf((*q.Sound)(nil)).Elem(), "", ""}, "Sprite": {reflect.TypeOf((*q.Sprite)(nil)).Elem(), "", "Animate,Ask,BounceOffEdge,Bounds,ChangeEffect,ChangeHeading,ChangePenColor,ChangePenHue,ChangePenShade,ChangePenSize,ChangeSize,ChangeXYpos,ChangeXpos,ChangeYpos,ClearGraphEffects,CostumeHeight,CostumeIndex,CostumeName,CostumeWidth,Destroy,Die,DistanceTo,Glide__0,Glide__1,GoBackLayers,Goto,GotoBack,GotoFront,Heading,Hide,HideVar,InitFrom,IsCloned,Move__0,Move__1,NextCostume,OnCloned__0,OnCloned__1,OnMoving__0,OnMoving__1,OnTouched__0,OnTouched__1,OnTouched__2,OnTouched__3,OnTouched__4,OnTouched__5,OnTurning__0,OnTurning__1,Parent,PenDown,PenUp,Pixel,PrevCostume,Say,SetCostume,SetDying,SetEffect,SetHeading,SetPenColor,SetPenHue,SetPenShade,SetPenSize,SetRotationStyle,SetSize,SetXYpos,SetXpos,SetYpos,Show,ShowVar,Size,Stamp,Step__0,Step__1,Step__2,Think,Touching,TouchingColor,Turn,TurnTo,Visible,Xpos,Ypos,checkTouchingScreen,doDestroy,doMoveTo,doMoveToForAnim,doStopSay,doTurnTogether,doUpdatePenColor,draw,fireTouched,fixWorldRange,getDrawInfo,getFromAnToForAni,getRotatedRect,getTrackPos,getXY,goAnimate,goMoveForward,hit,init,requireGreffUniforms,sayOrThink,setDirection,setPenHue,setPenShade,setPenWidth,touchPoint,touchRotatedRect,touchedColor_,touchingSprite,waitStopSay"}, "StopKind": {reflect.TypeOf((*q.StopKind)(nil)).Elem(), "", ""}, "TurningInfo": {reflect.TypeOf((*q.TurningInfo)(nil)).Elem(), "", "Dir"}, "Value": {reflect.TypeOf((*q.Value)(nil)).Elem(), "Equal,Float,Int,String", ""}, }, AliasTypes: map[string]reflect.Type{ "Color": reflect.TypeOf((*q.Color)(nil)).Elem(), "Key": reflect.TypeOf((*q.Key)(nil)).Elem(), "Spriter": reflect.TypeOf((*q.Spriter)(nil)).Elem(), }, Vars: map[string]reflect.Value{}, Funcs: map[string]reflect.Value{ "Exit__0": reflect.ValueOf(q.Exit__0), "Exit__1": reflect.ValueOf(q.Exit__1), "Gopt_Game_Main": reflect.ValueOf(q.Gopt_Game_Main), "Gopt_Game_Reload": reflect.ValueOf(q.Gopt_Game_Reload), "Gopt_Game_Run": reflect.ValueOf(q.Gopt_Game_Run), "Gopt_Sprite_Clone__0": reflect.ValueOf(q.Gopt_Sprite_Clone__0), "Gopt_Sprite_Clone__1": reflect.ValueOf(q.Gopt_Sprite_Clone__1), "Iround": reflect.ValueOf(q.Iround), "RGB": reflect.ValueOf(q.RGB), "RGBA": reflect.ValueOf(q.RGBA), "Rand__0": reflect.ValueOf(q.Rand__0), "Rand__1": reflect.ValueOf(q.Rand__1), "Sched": reflect.ValueOf(q.Sched), "SchedNow": reflect.ValueOf(q.SchedNow), "SetDebug": reflect.ValueOf(q.SetDebug), }, TypedConsts: map[string]gossa.TypedConst{ "AllOtherScripts": {reflect.TypeOf(q.AllOtherScripts), constant.MakeInt64(int64(q.AllOtherScripts))}, "AllSprites": {reflect.TypeOf(q.AllSprites), constant.MakeInt64(int64(q.AllSprites))}, "BrightnessEffect": {reflect.TypeOf(q.BrightnessEffect), constant.MakeInt64(int64(q.BrightnessEffect))}, "ColorEffect": {reflect.TypeOf(q.ColorEffect), constant.MakeInt64(int64(q.ColorEffect))}, "Down": {reflect.TypeOf(q.Down), constant.MakeInt64(int64(q.Down))}, "Edge": {reflect.TypeOf(q.Edge), constant.MakeInt64(int64(q.Edge))}, "EdgeBottom": {reflect.TypeOf(q.EdgeBottom), constant.MakeInt64(int64(q.EdgeBottom))}, "EdgeLeft": {reflect.TypeOf(q.EdgeLeft), constant.MakeInt64(int64(q.EdgeLeft))}, "EdgeRight": {reflect.TypeOf(q.EdgeRight), constant.MakeInt64(int64(q.EdgeRight))}, "EdgeTop": {reflect.TypeOf(q.EdgeTop), constant.MakeInt64(int64(q.EdgeTop))}, "Key0": {reflect.TypeOf(q.Key0), constant.MakeInt64(int64(q.Key0))}, "Key1": {reflect.TypeOf(q.Key1), constant.MakeInt64(int64(q.Key1))}, "Key2": {reflect.TypeOf(q.Key2), constant.MakeInt64(int64(q.Key2))}, "Key3": {reflect.TypeOf(q.Key3), constant.MakeInt64(int64(q.Key3))}, "Key4": {reflect.TypeOf(q.Key4), constant.MakeInt64(int64(q.Key4))}, "Key5": {reflect.TypeOf(q.Key5), constant.MakeInt64(int64(q.Key5))}, "Key6": {reflect.TypeOf(q.Key6), constant.MakeInt64(int64(q.Key6))}, "Key7": {reflect.TypeOf(q.Key7), constant.MakeInt64(int64(q.Key7))}, "Key8": {reflect.TypeOf(q.Key8), constant.MakeInt64(int64(q.Key8))}, "Key9": {reflect.TypeOf(q.Key9), constant.MakeInt64(int64(q.Key9))}, "KeyA": {reflect.TypeOf(q.KeyA), constant.MakeInt64(int64(q.KeyA))}, "KeyAlt": {reflect.TypeOf(q.KeyAlt), constant.MakeInt64(int64(q.KeyAlt))}, "KeyAny": {reflect.TypeOf(q.KeyAny), constant.MakeInt64(int64(q.KeyAny))}, "KeyApostrophe": {reflect.TypeOf(q.KeyApostrophe), constant.MakeInt64(int64(q.KeyApostrophe))}, "KeyB": {reflect.TypeOf(q.KeyB), constant.MakeInt64(int64(q.KeyB))}, "KeyBackslash": {reflect.TypeOf(q.KeyBackslash), constant.MakeInt64(int64(q.KeyBackslash))}, "KeyBackspace": {reflect.TypeOf(q.KeyBackspace), constant.MakeInt64(int64(q.KeyBackspace))}, "KeyC": {reflect.TypeOf(q.KeyC), constant.MakeInt64(int64(q.KeyC))}, "KeyCapsLock": {reflect.TypeOf(q.KeyCapsLock), constant.MakeInt64(int64(q.KeyCapsLock))}, "KeyComma": {reflect.TypeOf(q.KeyComma), constant.MakeInt64(int64(q.KeyComma))}, "KeyControl": {reflect.TypeOf(q.KeyControl), constant.MakeInt64(int64(q.KeyControl))}, "KeyD": {reflect.TypeOf(q.KeyD), constant.MakeInt64(int64(q.KeyD))}, "KeyDelete": {reflect.TypeOf(q.KeyDelete), constant.MakeInt64(int64(q.KeyDelete))}, "KeyDown": {reflect.TypeOf(q.KeyDown), constant.MakeInt64(int64(q.KeyDown))}, "KeyE": {reflect.TypeOf(q.KeyE), constant.MakeInt64(int64(q.KeyE))}, "KeyEnd": {reflect.TypeOf(q.KeyEnd), constant.MakeInt64(int64(q.KeyEnd))}, "KeyEnter": {reflect.TypeOf(q.KeyEnter), constant.MakeInt64(int64(q.KeyEnter))}, "KeyEqual": {reflect.TypeOf(q.KeyEqual), constant.MakeInt64(int64(q.KeyEqual))}, "KeyEscape": {reflect.TypeOf(q.KeyEscape), constant.MakeInt64(int64(q.KeyEscape))}, "KeyF": {reflect.TypeOf(q.KeyF), constant.MakeInt64(int64(q.KeyF))}, "KeyF1": {reflect.TypeOf(q.KeyF1), constant.MakeInt64(int64(q.KeyF1))}, "KeyF10": {reflect.TypeOf(q.KeyF10), constant.MakeInt64(int64(q.KeyF10))}, "KeyF11": {reflect.TypeOf(q.KeyF11), constant.MakeInt64(int64(q.KeyF11))}, "KeyF12": {reflect.TypeOf(q.KeyF12), constant.MakeInt64(int64(q.KeyF12))}, "KeyF2": {reflect.TypeOf(q.KeyF2), constant.MakeInt64(int64(q.KeyF2))}, "KeyF3": {reflect.TypeOf(q.KeyF3), constant.MakeInt64(int64(q.KeyF3))}, "KeyF4": {reflect.TypeOf(q.KeyF4), constant.MakeInt64(int64(q.KeyF4))}, "KeyF5": {reflect.TypeOf(q.KeyF5), constant.MakeInt64(int64(q.KeyF5))}, "KeyF6": {reflect.TypeOf(q.KeyF6), constant.MakeInt64(int64(q.KeyF6))}, "KeyF7": {reflect.TypeOf(q.KeyF7), constant.MakeInt64(int64(q.KeyF7))}, "KeyF8": {reflect.TypeOf(q.KeyF8), constant.MakeInt64(int64(q.KeyF8))}, "KeyF9": {reflect.TypeOf(q.KeyF9), constant.MakeInt64(int64(q.KeyF9))}, "KeyG": {reflect.TypeOf(q.KeyG), constant.MakeInt64(int64(q.KeyG))}, "KeyGraveAccent": {reflect.TypeOf(q.KeyGraveAccent), constant.MakeInt64(int64(q.KeyGraveAccent))}, "KeyH": {reflect.TypeOf(q.KeyH), constant.MakeInt64(int64(q.KeyH))}, "KeyHome": {reflect.TypeOf(q.KeyHome), constant.MakeInt64(int64(q.KeyHome))}, "KeyI": {reflect.TypeOf(q.KeyI), constant.MakeInt64(int64(q.KeyI))}, "KeyInsert": {reflect.TypeOf(q.KeyInsert), constant.MakeInt64(int64(q.KeyInsert))}, "KeyJ": {reflect.TypeOf(q.KeyJ), constant.MakeInt64(int64(q.KeyJ))}, "KeyK": {reflect.TypeOf(q.KeyK), constant.MakeInt64(int64(q.KeyK))}, "KeyKP0": {reflect.TypeOf(q.KeyKP0), constant.MakeInt64(int64(q.KeyKP0))}, "KeyKP1": {reflect.TypeOf(q.KeyKP1), constant.MakeInt64(int64(q.KeyKP1))}, "KeyKP2": {reflect.TypeOf(q.KeyKP2), constant.MakeInt64(int64(q.KeyKP2))}, "KeyKP3": {reflect.TypeOf(q.KeyKP3), constant.MakeInt64(int64(q.KeyKP3))}, "KeyKP4": {reflect.TypeOf(q.KeyKP4), constant.MakeInt64(int64(q.KeyKP4))}, "KeyKP5": {reflect.TypeOf(q.KeyKP5), constant.MakeInt64(int64(q.KeyKP5))}, "KeyKP6": {reflect.TypeOf(q.KeyKP6), constant.MakeInt64(int64(q.KeyKP6))}, "KeyKP7": {reflect.TypeOf(q.KeyKP7), constant.MakeInt64(int64(q.KeyKP7))}, "KeyKP8": {reflect.TypeOf(q.KeyKP8), constant.MakeInt64(int64(q.KeyKP8))}, "KeyKP9": {reflect.TypeOf(q.KeyKP9), constant.MakeInt64(int64(q.KeyKP9))}, "KeyKPDecimal": {reflect.TypeOf(q.KeyKPDecimal), constant.MakeInt64(int64(q.KeyKPDecimal))}, "KeyKPDivide": {reflect.TypeOf(q.KeyKPDivide), constant.MakeInt64(int64(q.KeyKPDivide))}, "KeyKPEnter": {reflect.TypeOf(q.KeyKPEnter), constant.MakeInt64(int64(q.KeyKPEnter))}, "KeyKPEqual": {reflect.TypeOf(q.KeyKPEqual), constant.MakeInt64(int64(q.KeyKPEqual))}, "KeyKPMultiply": {reflect.TypeOf(q.KeyKPMultiply), constant.MakeInt64(int64(q.KeyKPMultiply))}, "KeyKPSubtract": {reflect.TypeOf(q.KeyKPSubtract), constant.MakeInt64(int64(q.KeyKPSubtract))}, "KeyL": {reflect.TypeOf(q.KeyL), constant.MakeInt64(int64(q.KeyL))}, "KeyLeft": {reflect.TypeOf(q.KeyLeft), constant.MakeInt64(int64(q.KeyLeft))}, "KeyLeftBracket": {reflect.TypeOf(q.KeyLeftBracket), constant.MakeInt64(int64(q.KeyLeftBracket))}, "KeyM": {reflect.TypeOf(q.KeyM), constant.MakeInt64(int64(q.KeyM))}, "KeyMax": {reflect.TypeOf(q.KeyMax), constant.MakeInt64(int64(q.KeyMax))}, "KeyMenu": {reflect.TypeOf(q.KeyMenu), constant.MakeInt64(int64(q.KeyMenu))}, "KeyMinus": {reflect.TypeOf(q.KeyMinus), constant.MakeInt64(int64(q.KeyMinus))}, "KeyN": {reflect.TypeOf(q.KeyN), constant.MakeInt64(int64(q.KeyN))}, "KeyNumLock": {reflect.TypeOf(q.KeyNumLock), constant.MakeInt64(int64(q.KeyNumLock))}, "KeyO": {reflect.TypeOf(q.KeyO), constant.MakeInt64(int64(q.KeyO))}, "KeyP": {reflect.TypeOf(q.KeyP), constant.MakeInt64(int64(q.KeyP))}, "KeyPageDown": {reflect.TypeOf(q.KeyPageDown), constant.MakeInt64(int64(q.KeyPageDown))}, "KeyPageUp": {reflect.TypeOf(q.KeyPageUp), constant.MakeInt64(int64(q.KeyPageUp))}, "KeyPause": {reflect.TypeOf(q.KeyPause), constant.MakeInt64(int64(q.KeyPause))}, "KeyPeriod": {reflect.TypeOf(q.KeyPeriod), constant.MakeInt64(int64(q.KeyPeriod))}, "KeyPrintScreen": {reflect.TypeOf(q.KeyPrintScreen), constant.MakeInt64(int64(q.KeyPrintScreen))}, "KeyQ": {reflect.TypeOf(q.KeyQ), constant.MakeInt64(int64(q.KeyQ))}, "KeyR": {reflect.TypeOf(q.KeyR), constant.MakeInt64(int64(q.KeyR))}, "KeyRight": {reflect.TypeOf(q.KeyRight), constant.MakeInt64(int64(q.KeyRight))}, "KeyRightBracket": {reflect.TypeOf(q.KeyRightBracket), constant.MakeInt64(int64(q.KeyRightBracket))}, "KeyS": {reflect.TypeOf(q.KeyS), constant.MakeInt64(int64(q.KeyS))}, "KeyScrollLock": {reflect.TypeOf(q.KeyScrollLock), constant.MakeInt64(int64(q.KeyScrollLock))}, "KeySemicolon": {reflect.TypeOf(q.KeySemicolon), constant.MakeInt64(int64(q.KeySemicolon))}, "KeyShift": {reflect.TypeOf(q.KeyShift), constant.MakeInt64(int64(q.KeyShift))}, "KeySlash": {reflect.TypeOf(q.KeySlash), constant.MakeInt64(int64(q.KeySlash))}, "KeySpace": {reflect.TypeOf(q.KeySpace), constant.MakeInt64(int64(q.KeySpace))}, "KeyT": {reflect.TypeOf(q.KeyT), constant.MakeInt64(int64(q.KeyT))}, "KeyTab": {reflect.TypeOf(q.KeyTab), constant.MakeInt64(int64(q.KeyTab))}, "KeyU": {reflect.TypeOf(q.KeyU), constant.MakeInt64(int64(q.KeyU))}, "KeyUp": {reflect.TypeOf(q.KeyUp), constant.MakeInt64(int64(q.KeyUp))}, "KeyV": {reflect.TypeOf(q.KeyV), constant.MakeInt64(int64(q.KeyV))}, "KeyW": {reflect.TypeOf(q.KeyW), constant.MakeInt64(int64(q.KeyW))}, "KeyX": {reflect.TypeOf(q.KeyX), constant.MakeInt64(int64(q.KeyX))}, "KeyY": {reflect.TypeOf(q.KeyY), constant.MakeInt64(int64(q.KeyY))}, "KeyZ": {reflect.TypeOf(q.KeyZ), constant.MakeInt64(int64(q.KeyZ))}, "Left": {reflect.TypeOf(q.Left), constant.MakeInt64(int64(q.Left))}, "Mouse": {reflect.TypeOf(q.Mouse), constant.MakeInt64(int64(q.Mouse))}, "Next": {reflect.TypeOf(q.Next), constant.MakeInt64(int64(q.Next))}, "OtherScriptsInSprite": {reflect.TypeOf(q.OtherScriptsInSprite), constant.MakeInt64(int64(q.OtherScriptsInSprite))}, "Prev": {reflect.TypeOf(q.Prev), constant.MakeInt64(int64(q.Prev))}, "Right": {reflect.TypeOf(q.Right), constant.MakeInt64(int64(q.Right))}, "ThisScript": {reflect.TypeOf(q.ThisScript), constant.MakeInt64(int64(q.ThisScript))}, "ThisSprite": {reflect.TypeOf(q.ThisSprite), constant.MakeInt64(int64(q.ThisSprite))}, "Up": {reflect.TypeOf(q.Up), constant.MakeInt64(int64(q.Up))}, }, UntypedConsts: map[string]gossa.UntypedConst{ "All": {"untyped int", constant.MakeInt64(int64(q.All))}, "DbgFlagAll": {"untyped int", constant.MakeInt64(int64(q.DbgFlagAll))}, "DbgFlagEvent": {"untyped int", constant.MakeInt64(int64(q.DbgFlagEvent))}, "DbgFlagInstr": {"untyped int", constant.MakeInt64(int64(q.DbgFlagInstr))}, "DbgFlagLoad": {"untyped int", constant.MakeInt64(int64(q.DbgFlagLoad))}, "GopPackage": {"untyped bool", constant.MakeBool(bool(q.GopPackage))}, "Gop_sched": {"untyped string", constant.MakeString(string(q.Gop_sched))}, "Invalid": {"untyped int", constant.MakeInt64(int64(q.Invalid))}, "Last": {"untyped int", constant.MakeInt64(int64(q.Last))}, "LeftRight": {"untyped int", constant.MakeInt64(int64(q.LeftRight))}, "None": {"untyped int", constant.MakeInt64(int64(q.None))}, "Normal": {"untyped int", constant.MakeInt64(int64(q.Normal))}, "Random": {"untyped int", constant.MakeInt64(int64(q.Random))}, }, }) }
cannot alloc method 3522 > 256, import _ "github.com/goplus/reflectx/icall/icall[2^n]"
fatal error: runtime: text offset base pointer out of ranges
github.com/goplus/reflectx/icall/icall[2^n] 1024 2048 4096 。。。65536
$ go get github.com/goplus/reflectx/cmd/icall_gen
示例:生成 20000 个预分配表
//go:generate icall_gen -o icall.go -pkg main -size 20000
Package ssa defines a representation of the elements of Go programs (packages, types, functions, variables and constants) using a static single-assignment (SSA) form intermediate representation (IR) for the bodies of functions.
pkg.go.dev/golang.org/x/tools/go/ssa
预注册导入包 imports -> reflect package -> register pkg
import _ "github.com/goplus/gossa/pkg/fmt"
Go+ Source -> Go Source
import _ "github.com/goplus/gopbuild"
Go Source -> ast.Package
ast.Package -> types.Package
查找和安装导入包 reflect package -> types.Package
直接依赖包/间接依赖包 ( qexp 生成的导入包 )
types.Package -> ssa.Program / ssa.Package
gossa.Interp 加载 ssa.Program / ssa.Package
类型转换
ssa.Type -> reflect.Type
ssa.Value -> gossa.value/interface{}
指令执行
ssa.Instruction
Function / Block / Var / BinOp 。。。
Go runtime 内存布局兼容 methods
reflectx NamedTypeOf / InterfaceOf / SetMethodSet 。。。
typed methods 数量限制,预分配 256 个。
import _ "github.com/reflectx/icall/icall[2^n]"
使用 reflectx/cmd/icall_gen 生成需要的 methods
多源码包加载
需要 gossa 自行实现 go module 加载机制。
Go1.17 引入的 regabi 寄存器调用规范
Go1.17 amd64 需要设置 GOEXPERIMENT=noregabi 编译。
GOEXPERIMENT=noregabi go build demo
Go1.18 amd64 默认使用 regabi 目前无法运行。
reflectx 需要使用 ASM 编写 regabi 兼容 method provider
源码
module github.com/goplusjs/play
go 1.16
require (
github.com/goplus/gop v1.0.33
github.com/goplus/gossa v0.2.6
github.com/goplus/gox v1.8.1
github.com/goplus/reflectx v0.6.8
github.com/goplusjs/gopherjs v1.2.5
golang.org/x/tools v0.1.8
)
replace github.com/goplus/reflectx => github.com/goplusjs/reflectx v0.5.6
参考 https://github.com/visualfc/gossa_demo 示例编写和测试程序。
使用 WASM 在浏览器中通过 gossa 运行 Go/ Go+ 源码。
参考 https://github.com/goplus/ispx 使用 qexp 生成自己需要的导入库来做一些脚本应用实现。
https://github.com/goplus/reflectx 的 regabi asm 实现。