Skip to content

Commit

Permalink
feat(fastgo): a new fastcodec generator
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaost committed Aug 7, 2024
1 parent 81e5f66 commit b06bda9
Show file tree
Hide file tree
Showing 14 changed files with 1,503 additions and 8 deletions.
10 changes: 4 additions & 6 deletions args/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,13 @@ func (a *Arguments) MakeLogFunc() backend.LogFunc {
if !a.Quiet {
if a.Verbose {
logger := log.New(os.Stderr, "[INFO] ", 0)
logs.Info = func(v ...interface{}) {
logger.Println(v...)
}
logs.Info = logger.Println
logs.Infof = logger.Printf
}

logger := log.New(os.Stderr, "[WARN] ", 0)
logs.Warn = func(v ...interface{}) {
logger.Println(v...)
}
logs.Warn = logger.Println
logs.Warnf = logger.Printf
logs.MultiWarn = func(ws []string) {
for _, w := range ws {
logger.Println(w)
Expand Down
4 changes: 4 additions & 0 deletions generator/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ import "github.com/cloudwego/thriftgo/plugin"
// LogFunc defines a set of log functions.
type LogFunc struct {
Info func(v ...interface{})
Infof func(fmt string, v ...interface{})
Warn func(v ...interface{})
Warnf func(fmt string, v ...interface{})
MultiWarn func(warns []string)
}

// DummyLogFunc returns a set of log functions that does nothing.
func DummyLogFunc() LogFunc {
return LogFunc{
Info: func(v ...interface{}) {},
Infof: func(fmt string, v ...interface{}) {},
Warn: func(v ...interface{}) {},
Warnf: func(fmt string, v ...interface{}) {},
MultiWarn: func(warns []string) {},
}
}
Expand Down
156 changes: 156 additions & 0 deletions generator/fastgo/bitset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fastgo

// bitsetCodeGen ...
// it's used by generate required fields bitset
type bitsetCodeGen struct {
varname string
typename string
varbits uint

i uint
m map[interface{}]uint
}

// newBitsetCodeGen ...
// varname - definition name of a bitset
// typename - it generates `var $varname [N]$typename`
func newBitsetCodeGen(varname, typename string) *bitsetCodeGen {
ret := &bitsetCodeGen{
varname: varname,
typename: typename,
m: map[interface{}]uint{},
}
switch typename {
case "byte", "uint8":
ret.varbits = 8
case "uint16":
ret.varbits = 16
case "uint32":
ret.varbits = 32
case "uint64":
ret.varbits = 64
default:
panic(typename)
}
return ret
}

// Add adds a `v` to bitsetCodeGen. `v` must be uniq.
// it will be used by `GenSetbit` and `GenIfNotSet`
func (g *bitsetCodeGen) Add(v interface{}) {
_, ok := g.m[v]
if ok {
panic("duplicated")
}
g.m[v] = g.i
g.i++
}

// Len ...
func (g *bitsetCodeGen) Len() int {
return len(g.m)
}

// GenVar generates the definition of a bitset
// if generates nothing if Add not called
func (g *bitsetCodeGen) GenVar(w *codewriter) {
if g.i == 0 {
return
}
bits := g.varbits
if g.i <= bits {
w.f("var %s %s", g.varname, g.typename)
return
}
w.f("var %s [%d]%s", g.varname, (g.i+bits-1)/bits, g.typename)
}

func (g *bitsetCodeGen) bitvalue(i uint) uint64 {
i = i % g.varbits
return 1 << uint64(i)
}

func (g *bitsetCodeGen) bitsvalue(n uint) uint64 {
if n > g.varbits {
panic(n)
}
ret := uint64(0)
for i := uint(0); i < n; i++ {
ret |= 1 << uint64(i)
}
return ret
}

// GenSetbit generates setbit code for v, vmust be added to bitsetCodeGen
func (g *bitsetCodeGen) GenSetbit(w *codewriter, v interface{}) {
i, ok := g.m[v]
if !ok {
panic("[BUG] unknown v?")
}
if g.i <= g.varbits {
w.f("%s |= 0x%x", g.varname, g.bitvalue(i))
} else {
w.f("%s[%d] |= 0x%x", g.varname, i/g.varbits, g.bitvalue(i))
}
}

// GenIfNotSet generates `if` code for each v
func (g *bitsetCodeGen) GenIfNotSet(w *codewriter, f func(w *codewriter, v interface{})) {
if len(g.m) == 0 {
return
}
m := make(map[uint]interface{})
for k, v := range g.m {
m[v] = k
}
if g.i <= g.varbits {
if g.i > g.varbits/2 {
w.f("if %s != 0x%x {", g.varname, g.bitsvalue(g.i))
defer w.f("}")
}
for i := uint(0); i < g.i; i++ {
w.f("if %s & 0x%x == 0 {", g.varname, g.bitvalue(i))
f(w, m[i])
w.f("}")
}
return
}
i := uint(0)
for i+g.varbits < g.i {
w.f("if %s[%d] != 0x%x {", g.varname, i/g.varbits, g.bitsvalue(g.varbits))
end := i + g.varbits
for ; i < end; i++ {
w.f("if %s[%d] & 0x%x == 0 {", g.varname, i/g.varbits, g.bitvalue(i))
f(w, m[i])
w.f("}")
}
w.f("}")
}
if i < g.i {
if g.i%g.varbits > g.varbits/2 {
w.f("if %s[%d] != 0x%x {", g.varname, i/g.varbits, g.bitsvalue(g.i%g.varbits))
defer w.f("}")
}
for ; i < g.i; i++ {
w.f("if %s[%d] & 0x%x == 0 {", g.varname, i/g.varbits, g.bitvalue(i))
f(w, m[i])
w.f("}")
}
}
}
106 changes: 106 additions & 0 deletions generator/fastgo/bitset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fastgo

import (
"fmt"
"strings"
"testing"
)

func TestBitsetCodeGen(t *testing.T) {
g := newBitsetCodeGen("bitset", "uint8")

w := newCodewriter()

// case: less or equal than 64 elements
g.Add(1)
g.Add(2)
g.GenVar(w)
srcEqual(t, w.String(), "var bitset uint8")
w.Reset()

g.GenSetbit(w, 2)
srcEqual(t, w.String(), "bitset |= 0x2")
w.Reset()

g.GenIfNotSet(w, func(w *codewriter, id interface{}) {
w.f("_ = %d", id)
})
srcEqual(t, w.String(), `if bitset & 0x1 == 0 { _ = 1 }
if bitset & 0x2 == 0 { _ = 2 }`)
w.Reset()

g.Add(3)
g.Add(4)
g.Add(5)
g.Add(6)
g.Add(7)
g.Add(8) // case: g.i > g.varbits/2
g.GenIfNotSet(w, func(w *codewriter, id interface{}) {
w.f("_ = %d", id)
})
srcEqual(t, w.String(), `if bitset != 0xff {
if bitset & 0x1 == 0 { _ = 1 }
if bitset & 0x2 == 0 { _ = 2 }
if bitset & 0x4 == 0 { _ = 3 }
if bitset & 0x8 == 0 { _ = 4 }
if bitset & 0x10 == 0 { _ = 5 }
if bitset & 0x20 == 0 { _ = 6 }
if bitset & 0x40 == 0 { _ = 7 }
if bitset & 0x80 == 0 { _ = 8 }
}`)
w.Reset()

// case: more than varbits elements
g = newBitsetCodeGen("bitset", "uint8")
for i := 0; i < 17; i++ {
g.Add(i + 100)
}
g.GenVar(w)
srcEqual(t, w.String(), "var bitset [3]uint8")
w.Reset()

g.GenSetbit(w, 100)
srcEqual(t, w.String(), "bitset[0] |= 0x1")
w.Reset()

g.GenSetbit(w, 115)
srcEqual(t, w.String(), "bitset[1] |= 0x80")
w.Reset()

g.GenSetbit(w, 116)
srcEqual(t, w.String(), "bitset[2] |= 0x1")
w.Reset()

g.GenIfNotSet(w, func(w *codewriter, id interface{}) {
w.f("_ = %d", id)
})
sb := &strings.Builder{}
fmt.Fprintln(sb, "if bitset[0] != 0xff {")
for i := 0; i < 8; i++ {
fmt.Fprintf(sb, "if bitset[0]&0x%x == 0 { _ = %d }\n", 1<<i, 100+i)
}
fmt.Fprintln(sb, "}")
fmt.Fprintln(sb, "if bitset[1] != 0xff {")
for i := 0; i < 8; i++ {
fmt.Fprintf(sb, "if bitset[1]&0x%x == 0 { _ = %d }\n", 1<<i, 108+i)
}
fmt.Fprintln(sb, "}")
fmt.Fprintln(sb, "if bitset[2]&0x1 == 0 { _ = 116 }")
srcEqual(t, w.String(), sb.String())
}
95 changes: 95 additions & 0 deletions generator/fastgo/codewriter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fastgo

import (
"bytes"
"fmt"
"path"
"strings"
)

type codewriter struct {
*bytes.Buffer

pkgs map[string]string // import -> alias
}

func newCodewriter() *codewriter {
return &codewriter{
Buffer: &bytes.Buffer{},
pkgs: make(map[string]string),
}
}

func (w *codewriter) UsePkg(s, a string) {
if path.Base(s) == a {
w.pkgs[s] = ""
} else {
w.pkgs[s] = a
}
}

func (w *codewriter) Imports() string {
pp0 := make([]string, 0, len(w.pkgs))
pp1 := make([]string, 0, len(w.pkgs)) // for cloudwego
for pkg, _ := range w.pkgs { // grouping
if strings.HasPrefix(pkg, cloudwegoRepoPrefix) {
pp1 = append(pp1, pkg)
} else {
pp0 = append(pp0, pkg)
}
}

// check if need an empty line between groups
if len(pp0) != 0 && len(pp1) > 0 {
pp0 = append(pp0, "")
}

// no imports?
pp0 = append(pp0, pp1...)
if len(pp0) == 0 {
return ""
}

// only imports one pkg?
if len(pp0) == 1 {
return fmt.Sprintf("import %s %q", w.pkgs[pp0[0]], pp0[0])
}

// more than one imports
s := &strings.Builder{}
fmt.Fprintln(s, "import (")
for _, p := range pp0 {
if p == "" {
fmt.Fprintln(s, "")
} else {
fmt.Fprintf(s, "%s %q\n", w.pkgs[p], p)
}
}
fmt.Fprintln(s, ")")
return s.String()
}

func (w *codewriter) f(format string, a ...interface{}) {
fmt.Fprintf(w, format, a...)

// always newline for each call
if len(format) == 0 || format[len(format)-1] != '\n' {
w.WriteByte('\n')
}
}
Loading

0 comments on commit b06bda9

Please sign in to comment.