mirror of
https://github.com/ii64/sonic.git
synced 2026-06-21 00:46:43 +08:00
fix: use stackmap of shadow func as jit func's (#127)
* fix: use stackmap of shadow func as jit func's * fix: use LoadWithFaker in decoder * fix: LoadWithFaker support go115 * add 'runtime.' prefix on jit funcname to prevent preempt * add parallel GC tests * remove no_stack_pointer() Co-authored-by: duanyi.aster <duanyi.aster@bytedance.com> Co-authored-by: liuqiang <liuqiang.06@bytedance.com>
This commit is contained in:
parent
3a25fcac4f
commit
442ce696fb
26 changed files with 7204 additions and 5553 deletions
|
|
@ -19,5 +19,6 @@ header:
|
||||||
- 'internal/native/avx2/native_subr_amd64.go' # auto-generated by asm2asm
|
- 'internal/native/avx2/native_subr_amd64.go' # auto-generated by asm2asm
|
||||||
- 'internal/resolver/asm.s' # empty file
|
- 'internal/resolver/asm.s' # empty file
|
||||||
- 'internal/rt/asm.s' # empty file
|
- 'internal/rt/asm.s' # empty file
|
||||||
|
- 'internal/loader/asm.s' # empty file
|
||||||
|
|
||||||
comment: on-failure
|
comment: on-failure
|
||||||
|
|
@ -17,12 +17,47 @@
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`testing`
|
`testing`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
`sync`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestGC_Encode(t *testing.T) {
|
||||||
|
root, err := NewSearcher(_TwitterJson).GetByPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
root.LoadAll()
|
||||||
|
_, err = root.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
N := 10000
|
||||||
|
for i:=0; i<N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func (wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
root, err := NewSearcher(_TwitterJson).GetByPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
root.Load()
|
||||||
|
_, err = root.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}(wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncodeValue(t *testing.T) {
|
func TestEncodeValue(t *testing.T) {
|
||||||
type Case struct {
|
type Case struct {
|
||||||
node Node
|
node Node
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,49 @@ package ast
|
||||||
import (
|
import (
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`testing`
|
`testing`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
`sync`
|
||||||
|
|
||||||
jsoniter `github.com/json-iterator/go`
|
jsoniter `github.com/json-iterator/go`
|
||||||
`github.com/stretchr/testify/assert`
|
`github.com/stretchr/testify/assert`
|
||||||
`github.com/tidwall/gjson`
|
`github.com/tidwall/gjson`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
go func () {
|
||||||
|
println("Begin GC looping...")
|
||||||
|
for {
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
println("stop GC looping!")
|
||||||
|
}()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGC_Parse(t *testing.T) {
|
||||||
|
_, _, err := Loads(_TwitterJson)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
N := 1000
|
||||||
|
for i:=0; i<N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func (wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
_, _, err := Loads(_TwitterJson)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}(wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func runDecoderTest(t *testing.T, src string, expect interface{}) {
|
func runDecoderTest(t *testing.T, src string, expect interface{}) {
|
||||||
vv, err := NewParser(src).Parse()
|
vv, err := NewParser(src).Parse()
|
||||||
if err != 0 { panic(err) }
|
if err != 0 { panic(err) }
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,38 @@ package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`testing`
|
`testing`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
`sync`
|
||||||
|
|
||||||
jsoniter `github.com/json-iterator/go`
|
jsoniter `github.com/json-iterator/go`
|
||||||
`github.com/stretchr/testify/assert`
|
`github.com/stretchr/testify/assert`
|
||||||
`github.com/tidwall/gjson`
|
`github.com/tidwall/gjson`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestGC_Search(t *testing.T) {
|
||||||
|
_, err := NewSearcher(_TwitterJson).GetByPath("statuses", 0, "id")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
N := 10000
|
||||||
|
for i:=0; i<N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func (wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
_, err := NewSearcher(_TwitterJson).GetByPath("statuses", 0, "id")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}(wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestExportError(t *testing.T) {
|
func TestExportError(t *testing.T) {
|
||||||
data := `{"a":]`
|
data := `{"a":]`
|
||||||
p := NewSearcher(data)
|
p := NewSearcher(data)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ func newAssembler(p _Program) *_Assembler {
|
||||||
/** Assembler Interface **/
|
/** Assembler Interface **/
|
||||||
|
|
||||||
func (self *_Assembler) Load() _Decoder {
|
func (self *_Assembler) Load() _Decoder {
|
||||||
return ptodec(self.BaseAssembler.Load("json_decoder", _FP_size, _FP_args))
|
return ptodec(self.BaseAssembler.LoadWithFaker("json_decoder", _FP_size, _FP_args, _Decoder_Shadow))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Assembler) Init(p _Program) *_Assembler {
|
func (self *_Assembler) Init(p _Program) *_Assembler {
|
||||||
|
|
@ -289,6 +289,7 @@ func (self *_Assembler) instrs() {
|
||||||
for i, v := range self.p {
|
for i, v := range self.p {
|
||||||
self.Mark(i)
|
self.Mark(i)
|
||||||
self.instr(&v)
|
self.instr(&v)
|
||||||
|
self.debug_instr(i, &v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
68
decoder/debug.go
Normal file
68
decoder/debug.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 ByteDance Inc.
|
||||||
|
*
|
||||||
|
* 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 decoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
`strings`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/internal/jit`
|
||||||
|
)
|
||||||
|
|
||||||
|
//WARN: MUST set false after release
|
||||||
|
var debugGC = false
|
||||||
|
|
||||||
|
var (
|
||||||
|
_Instr_End _Instr = newInsOp(_OP_nil_1)
|
||||||
|
|
||||||
|
_F_gc = jit.Func(runtime.GC)
|
||||||
|
_F_force_gc = jit.Func(debug.FreeOSMemory)
|
||||||
|
_F_println = jit.Func(println_wrapper)
|
||||||
|
)
|
||||||
|
|
||||||
|
func println_wrapper(i int, op1 int, op2 int){
|
||||||
|
println(i, " Intrs ", op1, _OpNames[op1], "next: ", op2, _OpNames[op2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) print_gc(i int, p1 *_Instr, p2 *_Instr) {
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(p2.op())), jit.Ptr(_SP, 16))// MOVQ $(p2.op()), 16(SP)
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(p1.op())), jit.Ptr(_SP, 8)) // MOVQ $(p1.op()), 8(SP)
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(i)), jit.Ptr(_SP, 0)) // MOVQ $(i), (SP)
|
||||||
|
self.call_go(_F_println)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) force_gc() {
|
||||||
|
self.call_go(_F_gc)
|
||||||
|
self.call_go(_F_force_gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) debug_instr(i int, v *_Instr) {
|
||||||
|
if debugGC {
|
||||||
|
if (i+1 == len(self.p)) {
|
||||||
|
self.print_gc(i, v, &_Instr_End)
|
||||||
|
} else {
|
||||||
|
next := &(self.p[i+1])
|
||||||
|
self.print_gc(i, v, next)
|
||||||
|
name := _OpNames[next.op()]
|
||||||
|
if strings.Contains(name, "save") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.force_gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,9 @@ package decoder
|
||||||
import (
|
import (
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`testing`
|
`testing`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
`sync`
|
||||||
|
|
||||||
`github.com/davecgh/go-spew/spew`
|
`github.com/davecgh/go-spew/spew`
|
||||||
gojson `github.com/goccy/go-json`
|
gojson `github.com/goccy/go-json`
|
||||||
|
|
@ -27,6 +30,48 @@ import (
|
||||||
`github.com/stretchr/testify/require`
|
`github.com/stretchr/testify/require`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
go func () {
|
||||||
|
println("Begin GC looping...")
|
||||||
|
for {
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
println("stop GC looping!")
|
||||||
|
}()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGC(t *testing.T) {
|
||||||
|
var w interface{}
|
||||||
|
out, err := decode(TwitterJson, &w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if out != len(TwitterJson) {
|
||||||
|
t.Fatal(out)
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
N := 10000
|
||||||
|
for i:=0; i<N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func (wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
var w interface{}
|
||||||
|
out, err := decode(TwitterJson, &w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if out != len(TwitterJson) {
|
||||||
|
t.Fatal(out)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}(wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
var _BindingValue TwitterStruct
|
var _BindingValue TwitterStruct
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`errors`
|
||||||
|
`runtime`
|
||||||
`sync`
|
`sync`
|
||||||
`unsafe`
|
`unsafe`
|
||||||
|
|
||||||
|
|
@ -58,6 +60,23 @@ type _Decoder func(
|
||||||
fv uint64,
|
fv uint64,
|
||||||
) (int, error)
|
) (int, error)
|
||||||
|
|
||||||
|
var errCallShadow = errors.New("DON'T CALL THIS!")
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
// Faker func of _Decoder, used to export its stackmap as _Decoder's
|
||||||
|
func _Decoder_Shadow(rb *[]byte, vp unsafe.Pointer, sb *_Stack, fv uint64) error {
|
||||||
|
// align to assembler_amd64.go: _FP_offs
|
||||||
|
var stacks [_FP_offs]byte
|
||||||
|
runtime.KeepAlive(stacks)
|
||||||
|
|
||||||
|
// must keep rb, vp and sb noticeable to GC
|
||||||
|
runtime.KeepAlive(sb)
|
||||||
|
runtime.KeepAlive(rb)
|
||||||
|
runtime.KeepAlive(vp)
|
||||||
|
|
||||||
|
return errCallShadow
|
||||||
|
}
|
||||||
|
|
||||||
func newStack() *_Stack {
|
func newStack() *_Stack {
|
||||||
if ret := stackPool.Get(); ret == nil {
|
if ret := stackPool.Get(); ret == nil {
|
||||||
return new(_Stack)
|
return new(_Stack)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import (
|
||||||
`math`
|
`math`
|
||||||
`reflect`
|
`reflect`
|
||||||
`regexp`
|
`regexp`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
`strconv`
|
`strconv`
|
||||||
`testing`
|
`testing`
|
||||||
`unsafe`
|
`unsafe`
|
||||||
|
|
@ -32,6 +34,18 @@ import (
|
||||||
`github.com/bytedance/sonic/encoder`
|
`github.com/bytedance/sonic/encoder`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
go func () {
|
||||||
|
println("Begin GC looping...")
|
||||||
|
for {
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
println("stop GC looping!")
|
||||||
|
}()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
type Optionals struct {
|
type Optionals struct {
|
||||||
Sr string `json:"sr"`
|
Sr string `json:"sr"`
|
||||||
So string `json:"so,omitempty"`
|
So string `json:"so,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -123,16 +123,16 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ST = jit.Reg("BX")
|
_ST = jit.Reg("BX")
|
||||||
_RP = jit.Reg("DI")
|
_RP = jit.Reg("DI")
|
||||||
_RL = jit.Reg("SI")
|
_RL = jit.Reg("SI")
|
||||||
_RC = jit.Reg("DX")
|
_RC = jit.Reg("DX")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_LR = jit.Reg("R9")
|
_LR = jit.Reg("R9")
|
||||||
_ET = jit.Reg("R10")
|
_ET = jit.Reg("R10")
|
||||||
_EP = jit.Reg("R11")
|
_EP = jit.Reg("R11")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -179,7 +179,7 @@ func newAssembler(p _Program) *_Assembler {
|
||||||
/** Assembler Interface **/
|
/** Assembler Interface **/
|
||||||
|
|
||||||
func (self *_Assembler) Load() _Encoder {
|
func (self *_Assembler) Load() _Encoder {
|
||||||
return ptoenc(self.BaseAssembler.Load("json_encoder", _FP_size, _FP_args))
|
return ptoenc(self.BaseAssembler.LoadWithFaker("json_encoder", _FP_size, _FP_args, _Encoder_Shadow))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Assembler) Init(p _Program) *_Assembler {
|
func (self *_Assembler) Init(p _Program) *_Assembler {
|
||||||
|
|
@ -260,6 +260,7 @@ func (self *_Assembler) instrs() {
|
||||||
for i, v := range self.p {
|
for i, v := range self.p {
|
||||||
self.Mark(i)
|
self.Mark(i)
|
||||||
self.instr(&v)
|
self.instr(&v)
|
||||||
|
self.debug_instr(i, &v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
68
encoder/debug.go
Normal file
68
encoder/debug.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 ByteDance Inc.
|
||||||
|
*
|
||||||
|
* 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 encoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
`strings`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/internal/jit`
|
||||||
|
)
|
||||||
|
|
||||||
|
//WARN: MUST set false after release
|
||||||
|
var debugGC = false
|
||||||
|
|
||||||
|
var (
|
||||||
|
_Instr_End _Instr = newInsOp(_OP_null)
|
||||||
|
|
||||||
|
_F_gc = jit.Func(runtime.GC)
|
||||||
|
_F_force_gc = jit.Func(debug.FreeOSMemory)
|
||||||
|
_F_println = jit.Func(println_wrapper)
|
||||||
|
)
|
||||||
|
|
||||||
|
func println_wrapper(i int, op1 int, op2 int){
|
||||||
|
println(i, " Intrs ", op1, _OpNames[op1], "next: ", op2, _OpNames[op2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) print_gc(i int, p1 *_Instr, p2 *_Instr) {
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(p2.op())), jit.Ptr(_SP, 16))// MOVQ $(p2.op()), 16(SP)
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(p1.op())), jit.Ptr(_SP, 8)) // MOVQ $(p1.op()), 8(SP)
|
||||||
|
self.Emit("MOVQ", jit.Imm(int64(i)), jit.Ptr(_SP, 0)) // MOVQ $(i), (SP)
|
||||||
|
self.call_go(_F_println)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) force_gc() {
|
||||||
|
self.call_go(_F_gc)
|
||||||
|
self.call_go(_F_force_gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) debug_instr(i int, v *_Instr) {
|
||||||
|
if debugGC {
|
||||||
|
if (i+1 == len(self.p)) {
|
||||||
|
self.print_gc(i, v, &_Instr_End)
|
||||||
|
} else {
|
||||||
|
next := &(self.p[i+1])
|
||||||
|
self.print_gc(i, v, next)
|
||||||
|
name := _OpNames[next.op()]
|
||||||
|
if strings.Contains(name, "save") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.force_gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,10 @@ package encoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
`strconv`
|
`strconv`
|
||||||
|
`sync`
|
||||||
`testing`
|
`testing`
|
||||||
|
|
||||||
gojson `github.com/goccy/go-json`
|
gojson `github.com/goccy/go-json`
|
||||||
|
|
@ -26,6 +29,44 @@ import (
|
||||||
`github.com/stretchr/testify/require`
|
`github.com/stretchr/testify/require`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
go func () {
|
||||||
|
println("Begin GC looping...")
|
||||||
|
for {
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
println("stop GC looping!")
|
||||||
|
}()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGC(t *testing.T) {
|
||||||
|
out, err := Encode(_GenericValue, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n := len(out)
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
N := 10000
|
||||||
|
for i:=0; i<N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func (wg *sync.WaitGroup, size int) {
|
||||||
|
defer wg.Done()
|
||||||
|
out, err := Encode(_GenericValue, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(out) != size {
|
||||||
|
t.Fatal(len(out), size)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}(wg, n)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func runEncoderTest(t *testing.T, fn func(string)string, exp string, arg string) {
|
func runEncoderTest(t *testing.T, fn func(string)string, exp string, arg string) {
|
||||||
require.Equal(t, exp, fn(arg))
|
require.Equal(t, exp, fn(arg))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package encoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`bytes`
|
`bytes`
|
||||||
|
`errors`
|
||||||
|
`runtime`
|
||||||
`sync`
|
`sync`
|
||||||
`unsafe`
|
`unsafe`
|
||||||
|
|
||||||
|
|
@ -56,6 +58,24 @@ type _Encoder func(
|
||||||
fv uint64,
|
fv uint64,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
var errCallShadow = errors.New("DON'T CALL THIS!")
|
||||||
|
|
||||||
|
// Faker func of _Encoder, used to export its stackmap as _Encoder's
|
||||||
|
//go:nosplit
|
||||||
|
func _Encoder_Shadow(rb *[]byte, vp unsafe.Pointer, sb *_Stack, fv uint64) error {
|
||||||
|
// align to assembler_amd64.go: _FP_offs
|
||||||
|
var frames [_FP_offs]byte
|
||||||
|
runtime.KeepAlive(frames)
|
||||||
|
|
||||||
|
// must keep sb noticeable to GC
|
||||||
|
runtime.KeepAlive(sb)
|
||||||
|
runtime.KeepAlive(rb)
|
||||||
|
runtime.KeepAlive(vp)
|
||||||
|
|
||||||
|
return errCallShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func newBytes() []byte {
|
func newBytes() []byte {
|
||||||
if ret := bytesPool.Get(); ret != nil {
|
if ret := bytesPool.Get(); ret != nil {
|
||||||
return ret.([]byte)
|
return ret.([]byte)
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,11 @@ func (self *BaseAssembler) Load(fn string, fp int, args int) loader.Function {
|
||||||
return loader.Loader(self.c).Load(fn, fp, args)
|
return loader.Loader(self.c).Load(fn, fp, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BaseAssembler) LoadWithFaker(fn string, fp int, args int, faker interface{}) loader.Function {
|
||||||
|
self.build()
|
||||||
|
return loader.Loader(self.c).LoadWithFaker(fn, fp, args, faker)
|
||||||
|
}
|
||||||
|
|
||||||
/** Assembler Stages **/
|
/** Assembler Stages **/
|
||||||
|
|
||||||
func (self *BaseAssembler) init() {
|
func (self *BaseAssembler) init() {
|
||||||
|
|
|
||||||
0
internal/loader/asm.s
Normal file
0
internal/loader/asm.s
Normal file
|
|
@ -18,7 +18,8 @@ package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`sync`
|
`sync`
|
||||||
_ `unsafe`
|
`unsafe`
|
||||||
|
`reflect`
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:linkname lastmoduledatap runtime.lastmoduledatap
|
//go:linkname lastmoduledatap runtime.lastmoduledatap
|
||||||
|
|
@ -28,8 +29,24 @@ var lastmoduledatap *_ModuleData
|
||||||
//go:linkname moduledataverify1 runtime.moduledataverify1
|
//go:linkname moduledataverify1 runtime.moduledataverify1
|
||||||
func moduledataverify1(_ *_ModuleData)
|
func moduledataverify1(_ *_ModuleData)
|
||||||
|
|
||||||
//go:nosplit
|
// PCDATA and FUNCDATA table indexes.
|
||||||
func no_pointers_stackmap() uintptr
|
//
|
||||||
|
// See funcdata.h and $GROOT/src/cmd/internal/objabi/funcdata.go.
|
||||||
|
const (
|
||||||
|
_FUNCDATA_ArgsPointerMaps = 0
|
||||||
|
_FUNCDATA_LocalsPointerMaps = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcInfo struct {
|
||||||
|
*_Func
|
||||||
|
datap *_ModuleData
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname findfunc runtime.findfunc
|
||||||
|
func findfunc(pc uintptr) funcInfo
|
||||||
|
|
||||||
|
//go:linkname funcdata runtime.funcdata
|
||||||
|
func funcdata(f funcInfo, i uint8) unsafe.Pointer
|
||||||
|
|
||||||
var (
|
var (
|
||||||
modLock sync.Mutex
|
modLock sync.Mutex
|
||||||
|
|
@ -64,3 +81,12 @@ func registerModule(mod *_ModuleData) {
|
||||||
lastmoduledatap = mod
|
lastmoduledatap = mod
|
||||||
modLock.Unlock()
|
modLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stackMap(f interface{}) (args uintptr, locals uintptr) {
|
||||||
|
fv := reflect.ValueOf(f)
|
||||||
|
if fv.Kind() != reflect.Func {
|
||||||
|
panic("f must be reflect.Func kind!")
|
||||||
|
}
|
||||||
|
fi := findfunc(fv.Pointer())
|
||||||
|
return uintptr(funcdata(fi, uint8(_FUNCDATA_ArgsPointerMaps))), uintptr(funcdata(fi, uint8(_FUNCDATA_LocalsPointerMaps)))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ var findFuncTab = &_FindFuncBucket {
|
||||||
idx: 1,
|
idx: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerFunction(name string, pc uintptr, fp int, args int, size uintptr) {
|
func registerFunction(name string, pc uintptr, fp int, args int, size uintptr, argptrs uintptr, localptrs uintptr) {
|
||||||
minpc := pc
|
minpc := pc
|
||||||
maxpc := pc + size
|
maxpc := pc + size
|
||||||
|
|
||||||
|
|
@ -127,8 +127,8 @@ func registerFunction(name string, pc uintptr, fp int, args int, size uintptr) {
|
||||||
args : int32(args),
|
args : int32(args),
|
||||||
pcsp : int32(pcsp),
|
pcsp : int32(pcsp),
|
||||||
nfuncdata : 2,
|
nfuncdata : 2,
|
||||||
argptrs : no_pointers_stackmap(),
|
argptrs : argptrs,
|
||||||
localptrs : no_pointers_stackmap(),
|
localptrs : localptrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* align the func to 8 bytes */
|
/* align the func to 8 bytes */
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ func makePCtab(fp int) []byte {
|
||||||
return append([]byte{0}, encodeVariant((fp + 1) << 1)...)
|
return append([]byte{0}, encodeVariant((fp + 1) << 1)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerFunction(name string, pc uintptr, fp int, args int, size uintptr) {
|
func registerFunction(name string, pc uintptr, fp int, args int, size uintptr, argptrs uintptr, localptrs uintptr) {
|
||||||
minpc := pc
|
minpc := pc
|
||||||
maxpc := pc + size
|
maxpc := pc + size
|
||||||
|
|
||||||
|
|
@ -137,8 +137,8 @@ func registerFunction(name string, pc uintptr, fp int, args int, size uintptr) {
|
||||||
args : int32(args),
|
args : int32(args),
|
||||||
pcsp : 1,
|
pcsp : 1,
|
||||||
nfuncdata : 2,
|
nfuncdata : 2,
|
||||||
argptrs : no_pointers_stackmap(),
|
argptrs : argptrs,
|
||||||
localptrs : no_pointers_stackmap(),
|
localptrs : localptrs,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/* function table */
|
/* function table */
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,15 @@ const (
|
||||||
type Loader []byte
|
type Loader []byte
|
||||||
type Function unsafe.Pointer
|
type Function unsafe.Pointer
|
||||||
|
|
||||||
func (self Loader) Load(fn string, fp int, args int) (f Function) {
|
func (self Loader) LoadWithFaker(fn string, fp int, args int, faker interface{}) (f Function) {
|
||||||
p := os.Getpagesize()
|
p := os.Getpagesize()
|
||||||
n := (((len(self) - 1) / p) + 1) * p
|
n := (((len(self) - 1) / p) + 1) * p
|
||||||
|
|
||||||
/* register the function */
|
/* register the function */
|
||||||
m := mmap(n)
|
m := mmap(n)
|
||||||
v := fmt.Sprintf("__%s_%x", fn, m)
|
v := fmt.Sprintf("runtime.__%s_%x", fn, m)
|
||||||
registerFunction(v, m, fp, args, uintptr(len(self)))
|
argsptr, localsptr := stackMap(faker)
|
||||||
|
registerFunction(v, m, fp, args, uintptr(len(self)), argsptr, localsptr)
|
||||||
|
|
||||||
/* reference as a slice */
|
/* reference as a slice */
|
||||||
s := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader {
|
s := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader {
|
||||||
|
|
@ -55,6 +56,10 @@ func (self Loader) Load(fn string, fp int, args int) (f Function) {
|
||||||
return Function(&m)
|
return Function(&m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self Loader) Load(fn string, fp int, args int) (f Function) {
|
||||||
|
return self.LoadWithFaker(fn, fp, args, func(){})
|
||||||
|
}
|
||||||
|
|
||||||
func mmap(nb int) uintptr {
|
func mmap(nb int) uintptr {
|
||||||
if m, _, e := syscall.RawSyscall6(syscall.SYS_MMAP, 0, uintptr(nb), _RW, _AP, 0, 0); e != 0 {
|
if m, _, e := syscall.RawSyscall6(syscall.SYS_MMAP, 0, uintptr(nb), _RW, _AP, 0, 0); e != 0 {
|
||||||
panic(e)
|
panic(e)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`errors`
|
||||||
|
`fmt`
|
||||||
|
`reflect`
|
||||||
`runtime`
|
`runtime`
|
||||||
|
`runtime/debug`
|
||||||
`testing`
|
`testing`
|
||||||
`unsafe`
|
`unsafe`
|
||||||
|
|
||||||
|
|
@ -36,3 +40,73 @@ func TestLoader_Load(t *testing.T) {
|
||||||
assert.Equal(t, 1234, v0)
|
assert.Equal(t, 1234, v0)
|
||||||
println(runtime.FuncForPC(*(*uintptr)(fn)).Name())
|
println(runtime.FuncForPC(*(*uintptr)(fn)).Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func faker1(in string) error {
|
||||||
|
runtime.KeepAlive(in)
|
||||||
|
return errors.New("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func faker2(in string) error {
|
||||||
|
runtime.KeepAlive(in)
|
||||||
|
return errors.New("2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func faker4(in string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackMap(t *testing.T) {
|
||||||
|
args1, locals1 := stackMap(faker1)
|
||||||
|
fi1 := findfunc(reflect.ValueOf(faker1).Pointer())
|
||||||
|
fmt.Printf("func1: %#v, args: %x, locals: %x\n", fi1, args1, locals1)
|
||||||
|
|
||||||
|
args2, locals2 := stackMap(faker2)
|
||||||
|
fi2 := findfunc(reflect.ValueOf(faker2).Pointer())
|
||||||
|
fmt.Printf("func2: %#v, args: %x, locals: %x\n", fi2, args2, locals2)
|
||||||
|
|
||||||
|
args4, locals4 := stackMap(faker4)
|
||||||
|
fi4 := findfunc(reflect.ValueOf(faker4).Pointer())
|
||||||
|
fmt.Printf("func4: %#v, args: %x, locals: %x\n", fi4, args4, locals4)
|
||||||
|
|
||||||
|
if reflect.DeepEqual(fi1, fi2) || reflect.DeepEqual(fi1, fi2) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if args1 != args2 || locals1 != locals2 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if args1 == args4 || locals1 == locals4 || args2 == args4 || locals2 == locals4 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcWrap(f func(i *int)) int {
|
||||||
|
var ret int
|
||||||
|
var x int = 0
|
||||||
|
runtime.SetFinalizer(&x, func(xp *int){
|
||||||
|
fmt.Printf("x got dropped: %x\n", unsafe.Pointer(xp))
|
||||||
|
})
|
||||||
|
f(&x)
|
||||||
|
ret = x
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadWithStackMap(t *testing.T) {
|
||||||
|
var f = func(i *int) {
|
||||||
|
*i = 1234
|
||||||
|
}
|
||||||
|
v1 := funcWrap(f)
|
||||||
|
|
||||||
|
bc := []byte {
|
||||||
|
0x48, 0x8b, 0x44, 0x24, 0x08, // MOVQ 8(%rsp), %rax
|
||||||
|
0x48, 0xc7, 0x00, 0xd2, 0x04, 0x00, 0x00, // MOVQ $1234, (%rax)
|
||||||
|
0xc3, // RET
|
||||||
|
}
|
||||||
|
fn := Loader(bc).LoadWithFaker("test", 0, 8, f)
|
||||||
|
f2 := (*(*func(*int))(unsafe.Pointer(&fn)))
|
||||||
|
v2 := funcWrap(f2)
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
println(v1, v2)
|
||||||
|
assert.Equal(t, v1, v2)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright 2021 ByteDance Inc.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "go_asm.h"
|
|
||||||
#include "funcdata.h"
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
TEXT ·no_pointers_stackmap(SB), NOSPLIT, $0 - 8
|
|
||||||
NO_LOCAL_POINTERS
|
|
||||||
LEAQ runtime·no_pointers_stackmap(SB), AX
|
|
||||||
MOVQ AX, ret+0(FP)
|
|
||||||
RET
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,32 +3,27 @@
|
||||||
|
|
||||||
package avx
|
package avx
|
||||||
|
|
||||||
import (
|
|
||||||
`unsafe`
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
//go:noescape
|
//go:noescape
|
||||||
//goland:noinspection ALL
|
//goland:noinspection ALL
|
||||||
func ___asm2asm_compiled_code__DO_NOT_CALL_THIS_SYMBOL___()
|
func __native_entry__() uintptr
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_func__base = ___asm2asm_compiled_code__DO_NOT_CALL_THIS_SYMBOL___
|
_subr__f64toa = __native_entry__() + 630
|
||||||
_subr__f64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 542
|
_subr__i64toa = __native_entry__() + 3642
|
||||||
_subr__i64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 3551
|
_subr__lspace = __native_entry__() + 301
|
||||||
_subr__lspace = **(**uintptr)(unsafe.Pointer(&_func__base)) + 238
|
_subr__lzero = __native_entry__() + 13
|
||||||
_subr__lzero = **(**uintptr)(unsafe.Pointer(&_func__base)) + 0
|
_subr__quote = __native_entry__() + 4955
|
||||||
_subr__quote = **(**uintptr)(unsafe.Pointer(&_func__base)) + 4864
|
_subr__skip_array = __native_entry__() + 17298
|
||||||
_subr__skip_array = **(**uintptr)(unsafe.Pointer(&_func__base)) + 16010
|
_subr__skip_object = __native_entry__() + 17333
|
||||||
_subr__skip_object = **(**uintptr)(unsafe.Pointer(&_func__base)) + 16045
|
_subr__skip_one = __native_entry__() + 15505
|
||||||
_subr__skip_one = **(**uintptr)(unsafe.Pointer(&_func__base)) + 14217
|
_subr__u64toa = __native_entry__() + 3735
|
||||||
_subr__u64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 3644
|
_subr__unquote = __native_entry__() + 5888
|
||||||
_subr__unquote = **(**uintptr)(unsafe.Pointer(&_func__base)) + 5885
|
_subr__value = __native_entry__() + 10928
|
||||||
_subr__value = **(**uintptr)(unsafe.Pointer(&_func__base)) + 9700
|
_subr__vnumber = __native_entry__() + 13704
|
||||||
_subr__vnumber = **(**uintptr)(unsafe.Pointer(&_func__base)) + 12411
|
_subr__vsigned = __native_entry__() + 14977
|
||||||
_subr__vsigned = **(**uintptr)(unsafe.Pointer(&_func__base)) + 13667
|
_subr__vstring = __native_entry__() + 12691
|
||||||
_subr__vstring = **(**uintptr)(unsafe.Pointer(&_func__base)) + 11458
|
_subr__vunsigned = __native_entry__() + 15236
|
||||||
_subr__vunsigned = **(**uintptr)(unsafe.Pointer(&_func__base)) + 13944
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,32 +3,27 @@
|
||||||
|
|
||||||
package avx2
|
package avx2
|
||||||
|
|
||||||
import (
|
|
||||||
`unsafe`
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
//go:noescape
|
//go:noescape
|
||||||
//goland:noinspection ALL
|
//goland:noinspection ALL
|
||||||
func ___asm2asm_compiled_code__DO_NOT_CALL_THIS_SYMBOL___()
|
func __native_entry__() uintptr
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_func__base = ___asm2asm_compiled_code__DO_NOT_CALL_THIS_SYMBOL___
|
_subr__f64toa = __native_entry__() + 903
|
||||||
_subr__f64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 790
|
_subr__i64toa = __native_entry__() + 3915
|
||||||
_subr__i64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 3799
|
_subr__lspace = __native_entry__() + 429
|
||||||
_subr__lspace = **(**uintptr)(unsafe.Pointer(&_func__base)) + 366
|
_subr__lzero = __native_entry__() + 13
|
||||||
_subr__lzero = **(**uintptr)(unsafe.Pointer(&_func__base)) + 0
|
_subr__quote = __native_entry__() + 5328
|
||||||
_subr__quote = **(**uintptr)(unsafe.Pointer(&_func__base)) + 5212
|
_subr__skip_array = __native_entry__() + 20361
|
||||||
_subr__skip_array = **(**uintptr)(unsafe.Pointer(&_func__base)) + 17881
|
_subr__skip_object = __native_entry__() + 20396
|
||||||
_subr__skip_object = **(**uintptr)(unsafe.Pointer(&_func__base)) + 17916
|
_subr__skip_one = __native_entry__() + 17472
|
||||||
_subr__skip_one = **(**uintptr)(unsafe.Pointer(&_func__base)) + 16135
|
_subr__u64toa = __native_entry__() + 4008
|
||||||
_subr__u64toa = **(**uintptr)(unsafe.Pointer(&_func__base)) + 3892
|
_subr__unquote = __native_entry__() + 7125
|
||||||
_subr__unquote = **(**uintptr)(unsafe.Pointer(&_func__base)) + 7049
|
_subr__value = __native_entry__() + 13020
|
||||||
_subr__value = **(**uintptr)(unsafe.Pointer(&_func__base)) + 11489
|
_subr__vnumber = __native_entry__() + 15671
|
||||||
_subr__vnumber = **(**uintptr)(unsafe.Pointer(&_func__base)) + 14329
|
_subr__vsigned = __native_entry__() + 16944
|
||||||
_subr__vsigned = **(**uintptr)(unsafe.Pointer(&_func__base)) + 15585
|
_subr__vstring = __native_entry__() + 14794
|
||||||
_subr__vstring = **(**uintptr)(unsafe.Pointer(&_func__base)) + 13492
|
_subr__vunsigned = __native_entry__() + 17203
|
||||||
_subr__vunsigned = **(**uintptr)(unsafe.Pointer(&_func__base)) + 15862
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit daab6520b48bc30586f7468676c990b5c1f781bd
|
Subproject commit a9988b2b8191ac9b8bc879ff8db18c650753a067
|
||||||
Loading…
Reference in a new issue