go1.18.1/src/cmd/compile/internal/typecheck/universe.go
var builtinFuncs = [...]struct {
name string
op ir.Op
}{
{"append", ir.OAPPEND},
{"cap", ir.OCAP},
{"close", ir.OCLOSE},
{"complex", ir.OCOMPLEX},
{"copy", ir.OCOPY},
{"delete", ir.ODELETE},
{"imag", ir.OIMAG},
{"len", ir.OLEN},
{"make", ir.OMAKE},
{"new", ir.ONEW},
{"panic", ir.OPANIC},
{"print", ir.OPRINT},
{"println", ir.OPRINTN},
{"real", ir.OREAL},
{"recover", ir.ORECOVER},
}
"len", ir.OLEN
go1.18.1/go1.18.1/src/cmd/compile/internal/ir/node.go
type Op uint8
// Node ops.
const (
OXXX Op = iota
// names
ONAME // var or func name
// Unnamed arg or return value: f(int, string) (int, error) { etc }
// Also used for a qualified package identifier that hasn't been resolved yet.
ONONAME
OTYPE // type name
OPACK // import
OLITERAL // literal
ONIL // nil
// expressions
OADD // X + Y
OSUB // X - Y
OOR // X | Y
OXOR // X ^ Y
OADDSTR // +{List} (string addition, list elements are strings)
OADDR // &X
OANDAND // X && Y
OAPPEND // append(Args); after walk, X may contain elem type descriptor
OBYTES2STR // Type(X) (Type is string, X is a []byte)
OBYTES2STRTMP // Type(X) (Type is string, X is a []byte, ephemeral)
ORUNES2STR // Type(X) (Type is string, X is a []rune)
OSTR2BYTES // Type(X) (Type is []byte, X is a string)
OSTR2BYTESTMP // Type(X) (Type is []byte, X is a string, ephemeral)
OSTR2RUNES // Type(X) (Type is []rune, X is a string)
OSLICE2ARRPTR // Type(X) (Type is *[N]T, X is a []T)
// X = Y or (if Def=true) X := Y
// If Def, then Init includes a DCL node for X.
OAS
// Lhs = Rhs (x, y, z = a, b, c) or (if Def=true) Lhs := Rhs
// If Def, then Init includes DCL nodes for Lhs
OAS2
OAS2DOTTYPE // Lhs = Rhs (x, ok = I.(int))
OAS2FUNC // Lhs = Rhs (x, y = f())
OAS2MAPR // Lhs = Rhs (x, ok = m["foo"])
OAS2RECV // Lhs = Rhs (x, ok = <-c)
OASOP // X AsOp= Y (x += y)
OCALL // X(Args) (function call, method call or type conversion)
// OCALLFUNC, OCALLMETH, and OCALLINTER have the same structure.
// Prior to walk, they are: X(Args), where Args is all regular arguments.
// After walk, if any argument whose evaluation might requires temporary variable,
// that temporary variable will be pushed to Init, Args will contains an updated
// set of arguments. KeepAlive is all OVARLIVE nodes that are attached to OCALLxxx.
OCALLFUNC // X(Args) (function call f(args))
OCALLMETH // X(Args) (direct method call x.Method(args))
OCALLINTER // X(Args) (interface method call x.Method(args))
OCAP // cap(X)
OCLOSE // close(X)
OCLOSURE // func Type { Func.Closure.Body } (func literal)
OCOMPLIT // Type{List} (composite literal, not yet lowered to specific form)
OMAPLIT // Type{List} (composite literal, Type is map)
OSTRUCTLIT // Type{List} (composite literal, Type is struct)
OARRAYLIT // Type{List} (composite literal, Type is array)
OSLICELIT // Type{List} (composite literal, Type is slice), Len is slice length.
OPTRLIT // &X (X is composite literal)
OCONV // Type(X) (type conversion)
OCONVIFACE // Type(X) (type conversion, to interface)
OCONVIDATA // Builds a data word to store X in an interface. Equivalent to IDATA(CONVIFACE(X)). Is an ir.ConvExpr.
OCONVNOP // Type(X) (type conversion, no effect)
OCOPY // copy(X, Y)
ODCL // var X (declares X of type X.Type)
// Used during parsing but don't last.
ODCLFUNC // func f() or func (r) f()
ODCLCONST // const pi = 3.14
ODCLTYPE // type Int int or type Int = int
ODELETE // delete(Args)
ODOT // X.Sel (X is of struct type)
ODOTPTR // X.Sel (X is of pointer to struct type)
ODOTMETH // X.Sel (X is non-interface, Sel is method name)
ODOTINTER // X.Sel (X is interface, Sel is method name)
OXDOT // X.Sel (before rewrite to one of the preceding)
ODOTTYPE // X.Ntype or X.Type (.Ntype during parsing, .Type once resolved); after walk, Itab contains address of interface type descriptor and Itab.X contains address of concrete type descriptor
ODOTTYPE2 // X.Ntype or X.Type (.Ntype during parsing, .Type once resolved; on rhs of OAS2DOTTYPE); after walk, Itab contains address of interface type descriptor
OEQ // X == Y
ONE // X != Y
OLT // X < Y
OLE // X <= Y
OGE // X >= Y
OGT // X > Y
ODEREF // *X
OINDEX // X[Index] (index of array or slice)
OINDEXMAP // X[Index] (index of map)
OKEY // Key:Value (key:value in struct/array/map literal)
OSTRUCTKEY // Field:Value (key:value in struct literal, after type checking)
OLEN // len(X)
OMAKE // make(Args) (before type checking converts to one of the following)
OMAKECHAN // make(Type[, Len]) (type is chan)
OMAKEMAP // make(Type[, Len]) (type is map)
OMAKESLICE // make(Type[, Len[, Cap]]) (type is slice)
OMAKESLICECOPY // makeslicecopy(Type, Len, Cap) (type is slice; Len is length and Cap is the copied from slice)
// OMAKESLICECOPY is created by the order pass and corresponds to:
// s = make(Type, Len); copy(s, Cap)
//
// Bounded can be set on the node when Len == len(Cap) is known at compile time.
//
// This node is created so the walk pass can optimize this pattern which would
// otherwise be hard to detect after the order pass.
OMUL // X * Y
ODIV // X / Y
OMOD // X % Y
OLSH // X << Y
ORSH // X >> Y
OAND // X & Y
OANDNOT // X &^ Y
ONEW // new(X); corresponds to calls to new in source code
ONOT // !X
OBITNOT // ^X
OPLUS // +X
ONEG // -X
OOROR // X || Y
OPANIC // panic(X)
OPRINT // print(List)
OPRINTN // println(List)
OPAREN // (X)
OSEND // Chan <- Value
OSLICE // X[Low : High] (X is untypechecked or slice)
OSLICEARR // X[Low : High] (X is pointer to array)
OSLICESTR // X[Low : High] (X is string)
OSLICE3 // X[Low : High : Max] (X is untypedchecked or slice)
OSLICE3ARR // X[Low : High : Max] (X is pointer to array)
OSLICEHEADER // sliceheader{Ptr, Len, Cap} (Ptr is unsafe.Pointer, Len is length, Cap is capacity)
ORECOVER // recover()
ORECOVERFP // recover(Args) w/ explicit FP argument
ORECV // <-X
ORUNESTR // Type(X) (Type is string, X is rune)
OSELRECV2 // like OAS2: Lhs = Rhs where len(Lhs)=2, len(Rhs)=1, Rhs[0].Op = ORECV (appears as .Var of OCASE)
OIOTA // iota
OREAL // real(X)
OIMAG // imag(X)
OCOMPLEX // complex(X, Y)
OALIGNOF // unsafe.Alignof(X)
OOFFSETOF // unsafe.Offsetof(X)
OSIZEOF // unsafe.Sizeof(X)
OUNSAFEADD // unsafe.Add(X, Y)
OUNSAFESLICE // unsafe.Slice(X, Y)
OMETHEXPR // X(Args) (method expression T.Method(args), first argument is the method receiver)
OMETHVALUE // X.Sel (method expression t.Method, not called)
// statements
OBLOCK // { List } (block of code)
OBREAK // break [Label]
// OCASE: case List: Body (List==nil means default)
// For OTYPESW, List is a OTYPE node for the specified type (or OLITERAL
// for nil) or an ODYNAMICTYPE indicating a runtime type for generics.
// If a type-switch variable is specified, Var is an
// ONAME for the version of the type-switch variable with the specified
// type.
OCASE
OCONTINUE // continue [Label]
ODEFER // defer Call
OFALL // fallthrough
OFOR // for Init; Cond; Post { Body }
// OFORUNTIL is like OFOR, but the test (Cond) is applied after the body:
// Init
// top: { Body } // Execute the body at least once
// cont: Post
// if Cond { // And then test the loop condition
// List // Before looping to top, execute List
// goto top
// }
// OFORUNTIL is created by walk. There's no way to write this in Go code.
OFORUNTIL
OGOTO // goto Label
OIF // if Init; Cond { Then } else { Else }
OLABEL // Label:
OGO // go Call
ORANGE // for Key, Value = range X { Body }
ORETURN // return Results
OSELECT // select { Cases }
OSWITCH // switch Init; Expr { Cases }
// OTYPESW: X := Y.(type) (appears as .Tag of OSWITCH)
// X is nil if there is no type-switch variable
OTYPESW
OFUNCINST // instantiation of a generic function
// types
OTCHAN // chan int
OTMAP // map[string]int
OTSTRUCT // struct{}
OTINTER // interface{}
// OTFUNC: func() - Recv is receiver field, Params is list of param fields, Results is
// list of result fields.
OTFUNC
OTARRAY // [8]int or [...]int
OTSLICE // []int
// misc
// intermediate representation of an inlined call. Uses Init (assignments
// for the captured variables, parameters, retvars, & INLMARK op),
// Body (body of the inlined function), and ReturnVars (list of
// return values)
OINLCALL // intermediary representation of an inlined call.
OEFACE // itable and data words of an empty-interface value.
OITAB // itable word of an interface value.
OIDATA // data word of an interface value in X
OSPTR // base pointer of a slice or string.
OCFUNC // reference to c function pointer (not go func value)
OCHECKNIL // emit code to ensure pointer/interface not nil
OVARDEF // variable is about to be fully initialized
OVARKILL // variable is dead
OVARLIVE // variable is alive
ORESULT // result of a function call; Xoffset is stack offset
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
OLINKSYMOFFSET // offset within a name
// opcodes for generics
ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter)
ODYNAMICDOTTYPE2 // x, ok = i.(T) where T is a type parameter (or derived from a type parameter)
ODYNAMICTYPE // a type node for type switches (represents a dynamic target type for a type switch)
// arch-specific opcodes
OTAILCALL // tail call to another function
OGETG // runtime.getg() (read g pointer)
OGETCALLERPC // runtime.getcallerpc() (continuation PC in caller frame)
OGETCALLERSP // runtime.getcallersp() (stack pointer in caller frame)
OEND
)
go1.18.1/src/cmd/compile/internal/typecheck/typecheck.go
// typecheck1 should ONLY be called from typecheck.
func typecheck1(n ir.Node, top int) ir.Node {
if n, ok := n.(*ir.Name); ok {
typecheckdef(n)
}
switch n.Op() {
default:
ir.Dump("typecheck", n)
base.Fatalf("typecheck %v", n.Op())
panic("unreachable")
case ir.OLITERAL:
......
case ir.OCAP, ir.OLEN:
n := n.(*ir.UnaryExpr)
return tcLenCap(n)
...
}
// tcLenCap typechecks an OLEN or OCAP node.
func tcLenCap(n *ir.UnaryExpr) ir.Node {
n.X = Expr(n.X)
n.X = DefaultLit(n.X, nil)
n.X = implicitstar(n.X)
l := n.X
t := l.Type()
if t == nil {
n.SetType(nil)
return n
}
var ok bool
if n.Op() == ir.OLEN {
ok = okforlen[t.Kind()]
} else {
ok = okforcap[t.Kind()]
}
if !ok {
base.Errorf("invalid argument %L for %v", l, n.Op())
n.SetType(nil)
return n
}
n.SetType(types.Types[types.TINT])
return n
}
go1.18.1/src/cmd/compile/internal/ir/expr.go
// A UnaryExpr is a unary expression Op X,
// or Op(X) for a builtin function that does not end up being a call.
type UnaryExpr struct {
miniExpr
X Node
}
miniExpr
// A miniExpr is a miniNode with extra fields common to expressions.
// TODO(rsc): Once we are sure about the contents, compact the bools
// into a bit field and leave extra bits available for implementations
// embedding miniExpr. Right now there are ~60 unused bits sitting here.
type miniExpr struct {
miniNode
typ *types.Type
init Nodes // TODO(rsc): Don't require every Node to have an init
flags bitset8
}
const (
miniExprNonNil = 1 << iota
miniExprTransient
miniExprBounded
miniExprImplicit // for use by implementations; not supported by every Expr
miniExprCheckPtr
)
func (*miniExpr) isExpr() {}
func (n *miniExpr) Type() *types.Type { return n.typ }
func (n *miniExpr) SetType(x *types.Type) { n.typ = x }
func (n *miniExpr) NonNil() bool { return n.flags&miniExprNonNil != 0 }
func (n *miniExpr) MarkNonNil() { n.flags |= miniExprNonNil }
func (n *miniExpr) Transient() bool { return n.flags&miniExprTransient != 0 }
func (n *miniExpr) SetTransient(b bool) { n.flags.set(miniExprTransient, b) }
func (n *miniExpr) Bounded() bool { return n.flags&miniExprBounded != 0 }
func (n *miniExpr) SetBounded(b bool) { n.flags.set(miniExprBounded, b) }
func (n *miniExpr) Init() Nodes { return n.init }
func (n *miniExpr) PtrInit() *Nodes { return &n.init }
func (n *miniExpr) SetInit(x Nodes) { n.init = x }
go1.18.1/src/cmd/compile/internal/ir/node.go
// A Node is the abstract interface to an IR node.
type Node interface {
// Formatting
Format(s fmt.State, verb rune)
// Source position.
Pos() src.XPos
SetPos(x src.XPos)
// For making copies. For Copy and SepCopy.
copy() Node
doChildren(func(Node) bool) bool
editChildren(func(Node) Node)
// Abstract graph structure, for generic traversals.
Op() Op
Init() Nodes
// Fields specific to certain Ops only.
Type() *types.Type
SetType(t *types.Type)
Name() *Name
Sym() *types.Sym
Val() constant.Value
SetVal(v constant.Value)
// Storage for analysis passes.
Esc() uint16
SetEsc(x uint16)
Diag() bool
SetDiag(x bool)
// Typecheck values:
// 0 means the node is not typechecked
// 1 means the node is completely typechecked
// 2 means typechecking of the node is in progress
// 3 means the node has its type from types2, but may need transformation
Typecheck() uint8
SetTypecheck(x uint8)
NonNil() bool
MarkNonNil()
}
okforlen [types.NTYPE]bool
go1.18.1/src/cmd/compile/internal/typecheck/universe.go
okforlen[types.TARRAY] = true
okforlen[types.TCHAN] = true
okforlen[types.TMAP] = true
okforlen[types.TSLICE] = true
okforlen[types.TSTRING] = true
go1.18.1/src/cmd/compile/internal/types/type.go
// 39
// Kind describes a kind of type.
type Kind uint8
const NTYPE Kind
go1.18.1/src/cmd/compile/internal/ssagen/ssa.go
// expr converts the expression n to ssa, adds it to s and returns the ssa result.
func (s *state) expr(n ir.Node) *ssa.Value {
return s.exprCheckPtr(n, true)
}
func (s *state) exprCheckPtr(n ir.Node, checkPtrOK bool) *ssa.Value {
if ir.HasUniquePos(n) {
// ONAMEs and named OLITERALs have the line number
// of the decl, not the use. See issue 14742.
s.pushLine(n.Pos())
defer s.popLine()
}
s.stmtList(n.Init())
switch n.Op() {
case ir.OBYTES2STRTMP:
n := n.(*ir.ConvExpr)
slice := s.expr(n.X)
ptr := s.newValue1(ssa.OpSlicePtr, s.f.Config.Types.BytePtr, slice)
len := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], slice)
return s.newValue2(ssa.OpStringMake, n.Type(), ptr, len)
......
case ir.OLEN, ir.OCAP:
n := n.(*ir.UnaryExpr)
switch {
case n.X.Type().IsSlice():
op := ssa.OpSliceLen
if n.Op() == ir.OCAP {
op = ssa.OpSliceCap
}
return s.newValue1(op, types.Types[types.TINT], s.expr(n.X))
case n.X.Type().IsString(): // string; not reachable for OCAP
return s.newValue1(ssa.OpStringLen, types.Types[types.TINT], s.expr(n.X))
case n.X.Type().IsMap(), n.X.Type().IsChan():
return s.referenceTypeBuiltin(n, s.expr(n.X))
default: // array
return s.constInt(types.Types[types.TINT], n.X.Type().NumElem())
}
}
func (s *state) constInt(t *types.Type, c int64) *ssa.Value {
if s.config.PtrSize == 8 {
return s.constInt64(t, c)
}
if int64(int32(c)) != c {
s.Fatalf("integer constant too big %d", c)
}
return s.constInt32(t, int32(c))
}
// newValue1 adds a new value with one argument to the current block.
func (s *state) newValue1(op ssa.Op, t *types.Type, arg *ssa.Value) *ssa.Value {
return s.curBlock.NewValue1(s.peekPos(), op, t, arg)
}
array:
default: // array
return s.constInt(types.Types[types.TINT], n.X.Type().NumElem())
}
go1.18.1/src/cmd/compile/internal/types/type.go
func (t *Type) NumElem() int64 {
t.wantEtype(TARRAY)
return t.extra.(*Array).Bound
}
// Array contains Type fields specific to array types.
type Array struct {
Elem *Type // element type
Bound int64 // number of elements; <0 if unknown yet
}
// A Type represents a Go type.
//
// There may be multiple unnamed types with identical structure. However, there must
// be a unique Type object for each unique named (defined) type. After noding, a
// package-level type can be looked up by building its unique symbol sym (sym =
// package.Lookup(name)) and checking sym.Def. If sym.Def is non-nil, the type
// already exists at package scope and is available at sym.Def.(*ir.Name).Type().
// Local types (which may have the same name as a package-level type) are
// distinguished by the value of vargen.
type Type struct {
// extra contains extra etype-specific fields.
// As an optimization, those etype-specific structs which contain exactly
// one pointer-shaped field are stored as values rather than pointers when possible.
//
// TMAP: *Map
// TFORW: *Forward
// TFUNC: *Func
// TSTRUCT: *Struct
// TINTER: *Interface
// TFUNCARGS: FuncArgs
// TCHANARGS: ChanArgs
// TCHAN: *Chan
// TPTR: Ptr
// TARRAY: *Array
// TSLICE: Slice
// TSSA: string
// TTYPEPARAM: *Typeparam
// TUNION: *Union
extra interface{}
// width is the width of this Type in bytes.
width int64 // valid if Align > 0
// list of base methods (excluding embedding)
methods Fields
// list of all methods (including embedding)
allMethods Fields
// canonical OTYPE node for a named type (should be an ir.Name node with same sym)
nod Object
// the underlying type (type literal or predeclared type) for a defined type
underlying *Type
// Cache of composite types, with this type being the element type.
cache struct {
ptr *Type // *T, or nil
slice *Type // []T, or nil
}
sym *Sym // symbol containing name, for named types
vargen int32 // unique name for OTYPE/ONAME
kind Kind // kind of type
align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed)
flags bitset8
// For defined (named) generic types, a pointer to the list of type params
// (in order) of this type that need to be instantiated. For instantiated
// generic types, this is the targs used to instantiate them. These targs
// may be typeparams (for re-instantiated types such as Value[T2]) or
// concrete types (for fully instantiated types such as Value[int]).
// rparams is only set for named types that are generic or are fully
// instantiated from a generic type, and is otherwise set to nil.
// TODO(danscales): choose a better name.
rparams *[]*Type
// For an instantiated generic type, the base generic type.
// This backpointer is useful, because the base type is the type that has
// the method bodies.
origType *Type
}
go1.18.1/src/cmd/compile/internal/ssa/value.go
// A Value represents a value in the SSA representation of the program.
// The ID and Type fields must not be modified. The remainder may be modified
// if they preserve the value of the Value (e.g. changing a (mul 2 x) to an (add x x)).
type Value struct {
// A unique identifier for the value. For performance we allocate these IDs
// densely starting at 1. There is no guarantee that there won't be occasional holes, though.
ID ID
// The operation that computes this value. See op.go.
Op Op
// The type of this value. Normally this will be a Go type, but there
// are a few other pseudo-types, see ../types/type.go.
Type *types.Type
// Auxiliary info for this value. The type of this information depends on the opcode and type.
// AuxInt is used for integer values, Aux is used for other values.
// Floats are stored in AuxInt using math.Float64bits(f).
// Unused portions of AuxInt are filled by sign-extending the used portion,
// even if the represented value is unsigned.
// Users of AuxInt which interpret AuxInt as unsigned (e.g. shifts) must be careful.
// Use Value.AuxUnsigned to get the zero-extended value of AuxInt.
AuxInt int64
Aux Aux
// Arguments of this value
Args []*Value
// Containing basic block
Block *Block
// Source position
Pos src.XPos
// Use count. Each appearance in Value.Args and Block.Controls counts once.
Uses int32
// wasm: Value stays on the WebAssembly stack. This value will not get a "register" (WebAssembly variable)
// nor a slot on Go stack, and the generation of this value is delayed to its use time.
OnWasmStack bool
// Is this value in the per-function constant cache? If so, remove from cache before changing it or recycling it.
InCache bool
// Storage for the first three args
argstorage [3]*Value
}
go1.18.1/src/cmd/compile/internal/ssa/func.go
// NewValue1 returns a new value in the block with one argument and zero aux values.
func (b *Block) NewValue1(pos src.XPos, op Op, t *types.Type, arg *Value) *Value {
v := b.Func.newValue(op, t, b, pos)
v.AuxInt = 0
v.Args = v.argstorage[:1]
v.argstorage[0] = arg
arg.Uses++
return v
}
// newValue allocates a new Value with the given fields and places it at the end of b.Values.
func (f *Func) newValue(op Op, t *types.Type, b *Block, pos src.XPos) *Value {
var v *Value
if f.freeValues != nil {
v = f.freeValues
f.freeValues = v.argstorage[0]
v.argstorage[0] = nil
} else {
ID := f.vid.get()
if int(ID) < len(f.Cache.values) {
v = &f.Cache.values[ID]
v.ID = ID
} else {
v = &Value{ID: ID}
}
}
v.Op = op
v.Type = t
v.Block = b
if notStmtBoundary(op) {
pos = pos.WithNotStmt()
}
v.Pos = pos
b.Values = append(b.Values, v)
return v
}
其他:
go1.18.1/src/cmd/compile/README.md
Generic SSA
cmd/compile/internal/gc
(converting to SSA)cmd/compile/internal/ssa
(SSA passes and rules)
In this phase, the AST is converted into Static Single Assignment (SSA) form, a lower-level intermediate representation with specific properties that make it easier to implement optimizations and to eventually generate machine code from it.
静态单赋值形式 (static single assignment form,通常简写为 SSA form 或是 SSA )
在编译器的设计中,静态单赋值形式(static single assignment form,通常简写为SSA form或是SSA)是中介码(IR,intermediate representation)的特性,每个变数仅被赋值一次。在原始的IR中,已存在的变数可被分割成许多不同的版本,在许多教科书当中通常会将旧的变数名称加上一个下标而成为新的变数名称,以至于标明每个变数及其不同版本。在SSA,UD链(use-define chain,赋值代表define,使用变数代表use)是非常明确,而且每个仅包含单一元素。
CPS(Continuation Passing Style, 后续传递风格), 是指将程序的控制流显示地用 Continuation 来传递,而不是像 direct style 那样明确指出下一条语句的编码风格。符合 CPS 的函数需要一个额外的参数:一个显式的Continuation (通常为一个只有一个参数的函数)。一个CPS函数通过以返回值为参数,调用Continuation的方式来返回函数值。当我们调 用一个CPS函数式,被调用的函数需要一个额外的
return procedure
来供被调用的函数使用。用CPS的方式来表达代码可 以使普通程序中很多不明确的内容变得明确。例如,函数的返回值看起来就像是对Continuation的调用,参数的求值顺序也 变得明确,同时,尾调用(tail call)也被直接传递给调用者(caller)。
参考:
How does Go calculate len()..? – tpaschalis – software, systems
The Go Programming Language Specification - The Go Programming Language
CPS(Continuation-Passing-Style, 续体传递风格) - 云+社区 - 腾讯云
静态单赋值形式_百度百科
Continuation Passing Style | HE Tao