refactor to use Build and BuildStmt, remove a buttload of code
This commit is contained in:
parent
bdd5711773
commit
813117c0f4
28
expr/build.go
Normal file
28
expr/build.go
Normal file
@ -0,0 +1,28 @@
|
||||
package expr
|
||||
|
||||
import "llvm.org/llvm/bindings/go/llvm"
|
||||
|
||||
type LLVMCtx struct {
|
||||
B llvm.Builder
|
||||
M llvm.Module
|
||||
}
|
||||
|
||||
func Build(ctx *Ctx, lctx LLVMCtx, stmts []Expr) {
|
||||
for _, stmt := range stmts {
|
||||
BuildStmt(ctx, lctx, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func BuildStmt(ctx *Ctx, lctx LLVMCtx, stmtE Expr) {
|
||||
s := stmtE.Actual.(Statement)
|
||||
m := s.Op.Actual.(Macro)
|
||||
|
||||
fn := ctx.GetMacro(m)
|
||||
if fn == nil {
|
||||
panicf("unknown macro: %q", m)
|
||||
}
|
||||
lv, ok := fn(ctx, lctx, s.Arg)
|
||||
if ok {
|
||||
ctx.LastVal = lv
|
||||
}
|
||||
}
|
10
expr/ctx.go
10
expr/ctx.go
@ -1,17 +1,23 @@
|
||||
package expr
|
||||
|
||||
import "llvm.org/llvm/bindings/go/llvm"
|
||||
|
||||
type MacroFn func(*Ctx, LLVMCtx, Expr) (llvm.Value, bool)
|
||||
|
||||
// Ctx contains all the Macros and Identifiers available. A Ctx is based on the
|
||||
// parent it was created from. If the current Ctx doesn't have a particular key
|
||||
// being looked up, the parent is called instead, and so on. A consequence of
|
||||
// this is that keys in the children take precedence over the parent's
|
||||
type Ctx struct {
|
||||
Parent *Ctx
|
||||
Macros map[Macro]func(Expr) (Expr, error)
|
||||
Macros map[Macro]MacroFn
|
||||
|
||||
LastVal llvm.Value
|
||||
}
|
||||
|
||||
// GetMacro returns the first instance of the given of the given Macro found. If
|
||||
// not found nil is returned.
|
||||
func (c *Ctx) GetMacro(m Macro) func(Expr) (Expr, error) {
|
||||
func (c *Ctx) GetMacro(m Macro) MacroFn {
|
||||
if c.Macros != nil {
|
||||
if fn, ok := c.Macros[m]; ok {
|
||||
return fn
|
||||
|
275
expr/expr.go
275
expr/expr.go
@ -2,7 +2,6 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"llvm.org/llvm/bindings/go/llvm"
|
||||
|
||||
@ -13,25 +12,11 @@ import (
|
||||
// TODO empty parenthesis
|
||||
// TODO need to figure out how to test LLVMVal stuff
|
||||
// TODO once we're a bit more confident, make ActualFunc
|
||||
// TODO LLVMVal -> LLVMBuild?
|
||||
|
||||
type LLVMCtx struct {
|
||||
B llvm.Builder
|
||||
M llvm.Module
|
||||
}
|
||||
|
||||
// Actual represents the actual expression in question, and has certain
|
||||
// properties. It is wrapped by Expr which also holds onto contextual
|
||||
// information, like the token to which Actual was originally parsed from
|
||||
// Actual represents the actual expression in question. It is wrapped by Expr
|
||||
// which also holds onto contextual information, like the token to which Actual
|
||||
// was originally parsed from
|
||||
type Actual interface {
|
||||
// Returns the llvm.Type which the expression accepts as an input, if any
|
||||
LLVMInType(ctx *Ctx) llvm.Type
|
||||
|
||||
// Returns the llvm.Type which the expressions outputs
|
||||
LLVMOutType(ctx *Ctx) llvm.Type
|
||||
|
||||
// Initializes an llvm.Value and returns it.
|
||||
LLVMVal(*Ctx, LLVMCtx) llvm.Value
|
||||
}
|
||||
|
||||
// equaler is used to compare two expressions. The comparison should not take
|
||||
@ -52,27 +37,6 @@ type Expr struct {
|
||||
val *llvm.Value
|
||||
}
|
||||
|
||||
// LLVMInType passes through to the method on the underlying Actual
|
||||
func (e Expr) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
return e.Actual.LLVMInType(ctx)
|
||||
}
|
||||
|
||||
// LLVMOutType passes through to the method on the underlying Actual
|
||||
func (e Expr) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return e.Actual.LLVMOutType(ctx)
|
||||
}
|
||||
|
||||
// LLVMVal passes its arguments to the underlying Actual instance. It caches the
|
||||
// result, so if this is called multiple times the underlying one is only called
|
||||
// the first time.
|
||||
func (e Expr) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
if e.val == nil {
|
||||
v := e.Actual.LLVMVal(ctx, lctx)
|
||||
e.val = &v
|
||||
}
|
||||
return *e.val
|
||||
}
|
||||
|
||||
// will panic if either Expr's Actual doesn't implement equaler
|
||||
func (e Expr) equal(e2 Expr) bool {
|
||||
eq1, ok1 := e.Actual.(equaler)
|
||||
@ -88,21 +52,9 @@ func (e Expr) equal(e2 Expr) bool {
|
||||
// Void represents no data (size = 0)
|
||||
type Void struct{}
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (v Void) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("Void has no InType")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (v Void) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return llvm.VoidType()
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (v Void) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
// Kind of weird that this only works for return type, but I guess makes
|
||||
// sense
|
||||
return lctx.B.CreateRetVoid()
|
||||
func (v Void) equal(e equaler) bool {
|
||||
_, ok := e.(Void)
|
||||
return ok
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -110,21 +62,6 @@ func (v Void) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
// Bool represents a true or false value
|
||||
type Bool bool
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (b Bool) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("Bool has no InType")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (b Bool) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return llvm.IntType(1)
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (b Bool) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
return llvm.Value{}
|
||||
}
|
||||
|
||||
func (b Bool) equal(e equaler) bool {
|
||||
bb, ok := e.(Bool)
|
||||
if !ok {
|
||||
@ -138,18 +75,7 @@ func (b Bool) equal(e equaler) bool {
|
||||
// Int represents an integer value
|
||||
type Int int64
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (i Int) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("Int has no InType")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (i Int) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return llvm.Int64Type()
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (i Int) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
func (i Int) build(lctx LLVMCtx) llvm.Value {
|
||||
v := lctx.B.CreateAlloca(llvm.Int64Type(), "")
|
||||
lctx.B.CreateStore(llvm.ConstInt(llvm.Int64Type(), uint64(i), false), v)
|
||||
return v
|
||||
@ -157,10 +83,7 @@ func (i Int) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
|
||||
func (i Int) equal(e equaler) bool {
|
||||
ii, ok := e.(Int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return ii == i
|
||||
return ok && ii == i
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -168,21 +91,6 @@ func (i Int) equal(e equaler) bool {
|
||||
// String represents a string value
|
||||
type String string
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (s String) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("String has no InType")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (s String) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (s String) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (s String) equal(e equaler) bool {
|
||||
ss, ok := e.(String)
|
||||
if !ok {
|
||||
@ -197,27 +105,9 @@ func (s String) equal(e equaler) bool {
|
||||
// name
|
||||
type Identifier string
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (id Identifier) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (id Identifier) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (id Identifier) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (id Identifier) equal(e equaler) bool {
|
||||
idid, ok := e.(Identifier)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return idid == id
|
||||
return ok && idid == id
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -232,27 +122,9 @@ func (m Macro) String() string {
|
||||
return "%" + string(m)
|
||||
}
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (m Macro) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("Macro has no InType")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (m Macro) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
panic("Macro has no OutType")
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (m Macro) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
panic("Macro has no Val")
|
||||
}
|
||||
|
||||
func (m Macro) equal(e equaler) bool {
|
||||
mm, ok := e.(Macro)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return m == mm
|
||||
return ok && m == mm
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -262,39 +134,12 @@ func (m Macro) equal(e equaler) bool {
|
||||
type Tuple []Expr
|
||||
|
||||
func (tup Tuple) String() string {
|
||||
strs := make([]string, len(tup))
|
||||
for i := range tup {
|
||||
strs[i] = fmt.Sprint(tup[i].Actual)
|
||||
}
|
||||
return "(" + strings.Join(strs, ", ") + ")"
|
||||
}
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (tup Tuple) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (tup Tuple) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (tup Tuple) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
panic("TODO")
|
||||
return "(" + exprsJoin(tup) + ")"
|
||||
}
|
||||
|
||||
func (tup Tuple) equal(e equaler) bool {
|
||||
tuptup, ok := e.(Tuple)
|
||||
if !ok || len(tuptup) != len(tup) {
|
||||
return false
|
||||
}
|
||||
for i := range tup {
|
||||
if !tup[i].equal(tuptup[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return ok && exprsEqual(tup, tuptup)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -303,89 +148,39 @@ func (tup Tuple) equal(e equaler) bool {
|
||||
// used as the input to the pipe, and the output of the pipe is the output of
|
||||
// the statement
|
||||
type Statement struct {
|
||||
// TODO change to Op and Arg
|
||||
In Expr
|
||||
To Expr
|
||||
Op, Arg Expr
|
||||
}
|
||||
|
||||
func (s Statement) String() string {
|
||||
return fmt.Sprintf("(%v > %s)", s.In.Actual, s.To.Actual)
|
||||
}
|
||||
|
||||
func (s Statement) maybeMacro(ctx *Ctx) (Expr, bool) {
|
||||
m, ok := s.To.Actual.(Macro)
|
||||
if !ok {
|
||||
return Expr{}, false
|
||||
}
|
||||
|
||||
fn := ctx.GetMacro(m)
|
||||
if fn == nil {
|
||||
return Expr{}, false
|
||||
}
|
||||
newe, err := fn(s.In)
|
||||
if err != nil {
|
||||
// TODO proper error
|
||||
panic(err)
|
||||
}
|
||||
return newe, true
|
||||
}
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (s Statement) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
if newe, ok := s.maybeMacro(ctx); ok {
|
||||
return newe.LLVMInType(ctx)
|
||||
}
|
||||
// TODO futures
|
||||
panic("unknown Statement.To")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (s Statement) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
if newe, ok := s.maybeMacro(ctx); ok {
|
||||
return newe.LLVMOutType(ctx)
|
||||
}
|
||||
panic("unknown Statement.To")
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
func (s Statement) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
if newe, ok := s.maybeMacro(ctx); ok {
|
||||
return newe.LLVMVal(ctx, lctx)
|
||||
}
|
||||
panic("unknown Statement.To")
|
||||
return fmt.Sprintf("(%v %s)", s.Op.Actual, s.Arg.Actual)
|
||||
}
|
||||
|
||||
func (s Statement) equal(e equaler) bool {
|
||||
ss, ok := e.(Statement)
|
||||
return ok && s.In.equal(ss.In) && s.To.equal(ss.To)
|
||||
return ok && s.Op.equal(ss.Op) && s.Arg.equal(ss.Arg)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Block represents a set of statements which share a scope, i.e. If one
|
||||
// statement binds a variable the rest of the statements in the block can use
|
||||
// that variable, including sub-blocks within this one.
|
||||
type Block []Expr
|
||||
// that variable
|
||||
type Block struct {
|
||||
In []Expr
|
||||
Stmts []Expr
|
||||
Out []Expr
|
||||
}
|
||||
|
||||
func (b Block) String() string {
|
||||
strs := make([]string, len(b))
|
||||
for i := range b {
|
||||
strs[i] = b[i].Actual.(Statement).String()
|
||||
}
|
||||
return fmt.Sprintf("{ %s }", strings.Join(strs, " "))
|
||||
return fmt.Sprintf(
|
||||
"{[%s][%s][%s]}",
|
||||
exprsJoin(b.In),
|
||||
exprsJoin(b.Stmts),
|
||||
exprsJoin(b.Out),
|
||||
)
|
||||
}
|
||||
|
||||
// LLVMInType implements the Actual interface method
|
||||
func (b Block) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// LLVMOutType implements the Actual interface method
|
||||
func (b Block) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return b[len(b)-1].LLVMOutType(ctx)
|
||||
}
|
||||
|
||||
// LLVMVal implements the Actual interface method
|
||||
/*
|
||||
func (b Block) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
name := randStr() // TODO make this based on token
|
||||
// TODO make these based on actual statements
|
||||
@ -404,16 +199,12 @@ func (b Block) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
lctx.B.CreateRet(v)
|
||||
return fn
|
||||
}
|
||||
*/
|
||||
|
||||
func (b Block) equal(e equaler) bool {
|
||||
bb, ok := e.(Block)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for i := range b {
|
||||
if !b[i].equal(bb[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return ok &&
|
||||
exprsEqual(b.In, bb.In) &&
|
||||
exprsEqual(b.Stmts, bb.Stmts) &&
|
||||
exprsEqual(b.Out, bb.Out)
|
||||
}
|
||||
|
@ -1,45 +1,23 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"llvm.org/llvm/bindings/go/llvm"
|
||||
)
|
||||
|
||||
type addActual []Expr
|
||||
|
||||
func (aa addActual) LLVMInType(ctx *Ctx) llvm.Type {
|
||||
panic("addActual has no InType")
|
||||
}
|
||||
|
||||
func (aa addActual) LLVMOutType(ctx *Ctx) llvm.Type {
|
||||
return aa[0].LLVMOutType(ctx)
|
||||
}
|
||||
|
||||
func (aa addActual) LLVMVal(ctx *Ctx, lctx LLVMCtx) llvm.Value {
|
||||
a := lctx.B.CreateLoad(aa[0].LLVMVal(ctx, lctx), "")
|
||||
for i := range aa[1:] {
|
||||
b := lctx.B.CreateLoad(aa[i+1].LLVMVal(ctx, lctx), "")
|
||||
a = lctx.B.CreateAdd(a, b, "")
|
||||
}
|
||||
return a
|
||||
}
|
||||
import "llvm.org/llvm/bindings/go/llvm"
|
||||
|
||||
// RootCtx describes what's available to *all* contexts, and is what all
|
||||
// contexts should have as the root parent in the tree
|
||||
var RootCtx = &Ctx{
|
||||
Macros: map[Macro]func(Expr) (Expr, error){
|
||||
"add": func(e Expr) (Expr, error) {
|
||||
tup, ok := e.Actual.(Tuple)
|
||||
if !ok {
|
||||
// TODO proper error
|
||||
return Expr{}, errors.New("add only accepts a tuple")
|
||||
Macros: map[Macro]MacroFn{
|
||||
"add": func(ctx *Ctx, lctx LLVMCtx, e Expr) (llvm.Value, bool) {
|
||||
tup := e.Actual.(Tuple)
|
||||
buildInt := func(e Expr) llvm.Value {
|
||||
return lctx.B.CreateLoad(e.Actual.(Int).build(lctx), "")
|
||||
}
|
||||
// TODO check that it's a tuple of integers too
|
||||
return Expr{
|
||||
Actual: addActual(tup),
|
||||
Token: e.Token,
|
||||
}, nil
|
||||
|
||||
a := buildInt(tup[0])
|
||||
for i := range tup[1:] {
|
||||
b := buildInt(tup[i+1])
|
||||
a = lctx.B.CreateAdd(a, b, "")
|
||||
}
|
||||
return a, true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
26
expr/util.go
26
expr/util.go
@ -2,7 +2,9 @@ package expr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func randStr() string {
|
||||
@ -12,3 +14,27 @@ func randStr() string {
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func exprsJoin(ee []Expr) string {
|
||||
strs := make([]string, len(ee))
|
||||
for i := range ee {
|
||||
strs[i] = fmt.Sprint(ee[i].Actual)
|
||||
}
|
||||
return strings.Join(strs, ", ")
|
||||
}
|
||||
|
||||
func exprsEqual(ee1, ee2 []Expr) bool {
|
||||
if len(ee1) != len(ee2) {
|
||||
return false
|
||||
}
|
||||
for i := range ee1 {
|
||||
if !ee1[i].equal(ee2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func panicf(msg string, args ...interface{}) {
|
||||
panic(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
13
main.go
13
main.go
@ -33,17 +33,20 @@ func main() {
|
||||
c := expr.Expr{Actual: expr.Int(3)}
|
||||
tup := expr.Expr{Actual: expr.Tuple([]expr.Expr{a, b, c})}
|
||||
addMacro := expr.Expr{Actual: expr.Macro("add")}
|
||||
stmt := expr.Expr{Actual: expr.Statement{In: tup, To: addMacro}}
|
||||
block := expr.Block([]expr.Expr{stmt})
|
||||
fn := block.LLVMVal(expr.RootCtx, lctx)
|
||||
stmt := expr.Expr{Actual: expr.Statement{Op: addMacro, Arg: tup}}
|
||||
|
||||
//block := expr.Block([]expr.Expr{stmt})
|
||||
//fn := block.LLVMVal(expr.RootCtx, lctx)
|
||||
|
||||
// create main and call our function
|
||||
mainFn := llvm.AddFunction(lctx.M, "main", llvm.FunctionType(llvm.Int64Type(), []llvm.Type{}, false))
|
||||
mainBlock := llvm.AddBasicBlock(mainFn, "entry")
|
||||
lctx.B.SetInsertPoint(mainBlock, mainBlock.FirstInstruction())
|
||||
expr.BuildStmt(expr.RootCtx, lctx, stmt)
|
||||
lctx.B.CreateRet(expr.RootCtx.LastVal)
|
||||
|
||||
ret := lctx.B.CreateCall(fn, []llvm.Value{}, "")
|
||||
lctx.B.CreateRet(ret)
|
||||
//ret := lctx.B.CreateCall(fn, []llvm.Value{}, "")
|
||||
//lctx.B.CreateRet(ret)
|
||||
|
||||
// verify it's all good
|
||||
if err := llvm.VerifyModule(lctx.M, llvm.ReturnStatusAction); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user